2028 lines
67 KiB
JavaScript
2028 lines
67 KiB
JavaScript
//SHARED
|
||
|
||
//helper to destroyChart on change
|
||
function destroyChart(instance) {
|
||
if (instance) instance.destroy();
|
||
}
|
||
|
||
// Система уведомлений
|
||
function showNotification(message, type = "success") {
|
||
const notification = document.getElementById("notification");
|
||
const notificationText = document.getElementById("notificationText");
|
||
if (!notification || !notificationText) return;
|
||
|
||
notificationText.textContent = message;
|
||
notification.className = `fixed top-4 right-4 z-50 animate-fade-in`;
|
||
|
||
if (type === "error") {
|
||
notification.innerHTML = `
|
||
<div class="bg-red-500 text-white px-6 py-4 rounded-lg shadow-lg">
|
||
<div class="flex items-center">
|
||
<i class="fas fa-exclamation-triangle mr-2"></i>
|
||
<span>${message}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
} else {
|
||
notification.innerHTML = `
|
||
<div class="bg-green-500 text-white px-6 py-4 rounded-lg shadow-lg">
|
||
<div class="flex items-center">
|
||
<i class="fas fa-check-circle mr-2"></i>
|
||
<span>${message}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
notification.classList.remove("hidden");
|
||
|
||
setTimeout(() => {
|
||
notification.classList.add("hidden");
|
||
}, 3000);
|
||
}
|
||
|
||
// Управление модальными окнами с исправленной прокруткой
|
||
function showModal(modalId) {
|
||
const modal = document.getElementById(modalId);
|
||
if (modal) {
|
||
modal.classList.add("show");
|
||
// Блокируем прокрутку body
|
||
document.body.classList.add("modal-open");
|
||
}
|
||
}
|
||
|
||
function hideModal(modalId) {
|
||
const modal = document.getElementById(modalId);
|
||
if (modal) {
|
||
modal.classList.remove("show");
|
||
// Разблокируем прокрутку body
|
||
document.body.classList.remove("modal-open");
|
||
}
|
||
}
|
||
// Настройка вкладок администратора
|
||
function setupAdminTabs() {
|
||
if (appState.adminTabsInitialized) return;
|
||
appState.adminTabsInitialized = true;
|
||
|
||
const tabButtons = document.querySelectorAll(".admin-tab-btn");
|
||
const tabContents = document.querySelectorAll(".admin-tab-content");
|
||
|
||
tabButtons.forEach((button) => {
|
||
button.addEventListener("click", () => {
|
||
const tabId = button.dataset.tab;
|
||
|
||
// Переключение активной вкладки
|
||
tabButtons.forEach((btn) => {
|
||
btn.className =
|
||
"admin-tab-btn px-6 py-3 font-medium text-gray-600 hover:text-blue-600 transition-colors border-b-2 border-transparent hover:border-blue-500";
|
||
});
|
||
button.className =
|
||
"admin-tab-btn px-6 py-3 font-medium transition-colors border-b-2 border-blue-500 text-blue-600";
|
||
|
||
// Показ/скрытие содержимого
|
||
tabContents.forEach((content) => {
|
||
content.classList.add("hidden");
|
||
});
|
||
document.getElementById(tabId + "Tab").classList.remove("hidden");
|
||
|
||
if (tabId === "dashboard") updateDashboard();
|
||
// Optionally, reload todos for TODO tab
|
||
if (tabId === "todo") loadTodos();
|
||
|
||
// Загрузка данных при переключении
|
||
// switch (tabId) {
|
||
// case "dashboard":
|
||
// updateDashboard();
|
||
// break;
|
||
// case "reports":
|
||
// loadReports();
|
||
// break;
|
||
// case "users":
|
||
// loadUsers();
|
||
// break;
|
||
// case "stores":
|
||
// loadStores();
|
||
// break;
|
||
// case "todo":
|
||
// loadTodos();
|
||
// break;
|
||
// }
|
||
});
|
||
});
|
||
}
|
||
|
||
// Закрытие модального окна при клике вне его
|
||
document.addEventListener("click", (e) => {
|
||
if (e.target.classList.contains("modal")) {
|
||
hideModal(e.target.id);
|
||
}
|
||
});
|
||
|
||
// Глобальные функции для onclick обработчиков
|
||
window.viewReport = viewReport;
|
||
window.deleteReport = deleteReport;
|
||
window.editUser = editUser;
|
||
window.deleteUser = deleteUser;
|
||
window.editStore = editStore;
|
||
window.deleteStore = deleteStore;
|
||
window.editTodo = editTodo;
|
||
window.deleteTodo = deleteTodo;
|
||
window.toggleTodo = toggleTodo;
|
||
|
||
//AUTH
|
||
|
||
// Обработчики авторизации
|
||
document.getElementById("loginForm").addEventListener("submit", async (e) => {
|
||
e.preventDefault();
|
||
|
||
const username = document.getElementById("username").value;
|
||
const password = document.getElementById("password").value;
|
||
|
||
// Call backend login
|
||
const result = await loginUser(username, password);
|
||
|
||
if (result.success) {
|
||
// Save user/token in JS (or localStorage)
|
||
appState.currentUser = result.user;
|
||
localStorage.setItem("token", result.token);
|
||
|
||
document.getElementById("loginScreen").classList.add("hidden");
|
||
|
||
if (result.user.role === "admin") {
|
||
showAdminInterface();
|
||
} else {
|
||
showUserInterface();
|
||
}
|
||
|
||
showNotification("Успешная авторизация!");
|
||
} else {
|
||
const errorDiv = document.getElementById("loginError");
|
||
// errorDiv.textContent = result.error;
|
||
errorDiv.textContent = "Неверный логин или пароль";
|
||
errorDiv.classList.remove("hidden");
|
||
}
|
||
});
|
||
|
||
// Показать интерфейс пользователя
|
||
function showUserInterface() {
|
||
document.getElementById("userInterface").classList.remove("hidden");
|
||
document.getElementById(
|
||
"userWelcome"
|
||
).textContent = `Добро пожаловать, ${appState.currentUser.username}!`;
|
||
|
||
loadUserStores();
|
||
setupFormCalculations();
|
||
}
|
||
|
||
// Показать интерфейс администратора
|
||
async function showAdminInterface() {
|
||
document.getElementById("adminInterface").classList.remove("hidden");
|
||
document.getElementById(
|
||
"adminWelcome"
|
||
).textContent = `Добро пожаловать, ${appState.currentUser.username}!`;
|
||
|
||
await Promise.all([loadUsers(), loadStores(), loadReports()]);
|
||
|
||
updateDashboard();
|
||
loadTodos();
|
||
setupAdminTabs();
|
||
|
||
// Activate first tab (dashboard)
|
||
document.querySelector('.admin-tab-btn[data-tab="dashboard"]').click();
|
||
}
|
||
|
||
// Обработчики выхода
|
||
document.getElementById("logoutBtn").addEventListener("click", logout);
|
||
document.getElementById("adminLogoutBtn").addEventListener("click", logout);
|
||
|
||
function logout() {
|
||
appState.currentUser = null;
|
||
appState.editingReportId = null;
|
||
|
||
appState.usersList = [];
|
||
appState.storesList = [];
|
||
appState.reportsList = [];
|
||
|
||
database.users = [];
|
||
database.reports = [];
|
||
database.stores = [];
|
||
|
||
// Remove all admin tab event listeners by replacing each node
|
||
document.querySelectorAll(".admin-tab-btn").forEach((btn) => {
|
||
btn.replaceWith(btn.cloneNode(true));
|
||
});
|
||
|
||
appState.adminTabsInitialized = false;
|
||
|
||
document.getElementById("loginScreen").classList.remove("hidden");
|
||
document.getElementById("userInterface").classList.add("hidden");
|
||
document.getElementById("adminInterface").classList.add("hidden");
|
||
|
||
document.getElementById("loginForm").reset();
|
||
document.getElementById("loginError").classList.add("hidden");
|
||
|
||
showNotification("Вы вышли из системы");
|
||
}
|
||
|
||
//DASHBOARD
|
||
|
||
// Обновление дашборда
|
||
function updateDashboard() {
|
||
const reports = appState.reportsList || [];
|
||
const users = appState.usersList || [];
|
||
|
||
// Расчет статистики
|
||
const totalRevenue = reports.reduce((sum, r) => sum + r.totalIncome, 0);
|
||
const totalExpenses = reports.reduce((sum, r) => sum + r.totalExpenses, 0);
|
||
const totalReports = reports.length;
|
||
const totalUsers = users.length;
|
||
|
||
// Обновление карточек
|
||
document.getElementById(
|
||
"totalRevenueCard"
|
||
).textContent = `€${totalRevenue.toFixed(2)}`;
|
||
document.getElementById(
|
||
"totalExpensesCard"
|
||
).textContent = `€${totalExpenses.toFixed(2)}`;
|
||
document.getElementById("totalReportsCard").textContent = totalReports;
|
||
document.getElementById("totalUsersCard").textContent = totalUsers;
|
||
|
||
// Создание графиков
|
||
createCharts();
|
||
}
|
||
|
||
// Создание графиков
|
||
function createCharts() {
|
||
// График доходов по дням
|
||
|
||
const reports = appState.reportsList || [];
|
||
const stores = appState.storesList || [];
|
||
|
||
const revenueCtx = document.getElementById("revenueChart");
|
||
if (revenueCtx) {
|
||
destroyChart(appState.revenueChartInstance);
|
||
const last7Days = [];
|
||
const revenueData = [];
|
||
|
||
for (let i = 6; i >= 0; i--) {
|
||
const date = new Date();
|
||
date.setDate(date.getDate() - i);
|
||
const dateStr = date.toISOString().split("T")[0];
|
||
last7Days.push(dateStr);
|
||
|
||
const dayReports = reports.filter(
|
||
(r) => (r.reportDate || r.date) === dateStr
|
||
);
|
||
|
||
const dayRevenue = dayReports.reduce(
|
||
(sum, r) => sum + (Number(r.totalIncome) || 0),
|
||
0
|
||
);
|
||
|
||
revenueData.push(dayRevenue);
|
||
}
|
||
|
||
appState.revenueChartInstance = new Chart(revenueCtx, {
|
||
type: "line",
|
||
data: {
|
||
labels: last7Days,
|
||
datasets: [
|
||
{
|
||
label: "Доходы",
|
||
data: revenueData,
|
||
borderColor: "rgb(59, 130, 246)",
|
||
backgroundColor: "rgba(59, 130, 246, 0.1)",
|
||
tension: 0.4,
|
||
},
|
||
],
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: {
|
||
display: false,
|
||
},
|
||
},
|
||
scales: {
|
||
y: {
|
||
beginAtZero: true,
|
||
ticks: {
|
||
callback: function (value) {
|
||
return "€" + value.toFixed(0);
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
});
|
||
}
|
||
|
||
// Круговая диаграмма расходов
|
||
const expensesCtx = document.getElementById("expensesChart");
|
||
if (expensesCtx) {
|
||
destroyChart(appState.expensesChartInstance);
|
||
const totalWages = reports.reduce(
|
||
(sum, r) => sum + (Number(r.totalWages) || 0),
|
||
0
|
||
);
|
||
const totalInternal = reports.reduce(
|
||
(sum, r) => sum + (Number(r.totalExpenses) || 0),
|
||
0
|
||
);
|
||
|
||
appState.expensesChartInstance = new Chart(expensesCtx, {
|
||
type: "doughnut",
|
||
data: {
|
||
labels: ["Зарплаты", "Прочие расходы"],
|
||
datasets: [
|
||
{
|
||
data: [totalWages, totalInternal],
|
||
backgroundColor: ["#F59E0B", "#EF4444"],
|
||
},
|
||
],
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: {
|
||
position: "bottom",
|
||
},
|
||
},
|
||
},
|
||
});
|
||
}
|
||
|
||
// График по магазинам
|
||
const storesCtx = document.getElementById("storesChart");
|
||
if (storesCtx) {
|
||
destroyChart(appState.storesChartInstance);
|
||
const storeData = stores.map((store) => {
|
||
const storeReports = reports.filter((r) => r.storeId === store.id);
|
||
const revenue = storeReports.reduce((sum, r) => sum + r.totalIncome, 0);
|
||
return { name: store.name, revenue };
|
||
});
|
||
console.log("storeData for bar chart:", storeData);
|
||
|
||
appState.storesChartInstance = new Chart(storesCtx, {
|
||
type: "bar",
|
||
data: {
|
||
labels: storeData.map((s) => s.name),
|
||
datasets: [
|
||
{
|
||
label: "Доходы по магазинам",
|
||
data: storeData.map((s) => s.revenue),
|
||
backgroundColor: "#10B981",
|
||
},
|
||
],
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: {
|
||
display: false,
|
||
},
|
||
},
|
||
scales: {
|
||
y: {
|
||
beginAtZero: true,
|
||
ticks: {
|
||
callback: function (value) {
|
||
return "€" + value.toFixed(0);
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
});
|
||
}
|
||
|
||
// Тренды продаж
|
||
const trendsCtx = document.getElementById("trendsChart");
|
||
if (trendsCtx) {
|
||
destroyChart(appState.trendsChartInstance);
|
||
const last30Days = [];
|
||
const profitData = [];
|
||
|
||
for (let i = 29; i >= 0; i--) {
|
||
const date = new Date();
|
||
date.setDate(date.getDate() - i);
|
||
const dateStr = date.toISOString().split("T")[0];
|
||
last30Days.push(dateStr);
|
||
|
||
const dayReports = reports.filter(
|
||
(r) => (r.reportDate || r.date) === dateStr
|
||
);
|
||
const dayProfit = dayReports.reduce(
|
||
(sum, r) =>
|
||
sum + ((Number(r.totalIncome) || 0) - (Number(r.totalExpenses) || 0)),
|
||
0
|
||
);
|
||
profitData.push(dayProfit);
|
||
}
|
||
|
||
appState.trendsChartInstance = new Chart(trendsCtx, {
|
||
type: "line",
|
||
data: {
|
||
labels: last30Days,
|
||
datasets: [
|
||
{
|
||
label: "Прибыль",
|
||
data: profitData,
|
||
borderColor: "#8B5CF6",
|
||
backgroundColor: "rgba(139, 92, 246, 0.1)",
|
||
tension: 0.4,
|
||
fill: true,
|
||
},
|
||
],
|
||
},
|
||
options: {
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: {
|
||
display: false,
|
||
},
|
||
},
|
||
scales: {
|
||
y: {
|
||
beginAtZero: true,
|
||
ticks: {
|
||
callback: function (value) {
|
||
return "€" + value.toFixed(0);
|
||
},
|
||
},
|
||
},
|
||
},
|
||
},
|
||
});
|
||
}
|
||
}
|
||
|
||
//REPORTS
|
||
|
||
async function loadReports() {
|
||
if (!appState.storesList || appState.storesList.length === 0) {
|
||
await loadStores();
|
||
}
|
||
|
||
const tbody = document.getElementById("reportsTableBody");
|
||
const filterStore = document.getElementById("filterStore");
|
||
|
||
const result = await getReports();
|
||
console.log("getReports() result:", result);
|
||
|
||
appState.reportsList = result.success ? result.reports : [];
|
||
|
||
if (!result.success) {
|
||
showNotification(result.error || "Ошибка загрузки отчетов", "error");
|
||
return;
|
||
}
|
||
const reports = result.reports;
|
||
const stores = appState.storesList || [];
|
||
|
||
filterStore.innerHTML = `
|
||
<option value="">Все магазины</option>
|
||
${stores
|
||
.map((store) => `<option value="${store.id}">${store.name}</option>`)
|
||
.join("")}
|
||
`;
|
||
|
||
tbody.innerHTML = "";
|
||
reports.forEach((report) => {
|
||
const profit =
|
||
(Number(report.totalIncome) || 0) - (Number(report.totalExpenses) || 0);
|
||
|
||
const store = stores.find((s) => Number(s.id) === Number(report.storeId));
|
||
const storeName = store ? store.name : report.storeName || report.storeId;
|
||
|
||
const row = document.createElement("tr");
|
||
row.className = "hover:bg-gray-50";
|
||
row.innerHTML = `
|
||
<td class="px-6 py-4 text-sm text-gray-900">${
|
||
report.reportDate || report.date || ""
|
||
}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">${storeName}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">€${Number(
|
||
report.totalIncome
|
||
).toFixed(2)}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">€${Number(
|
||
report.totalExpenses
|
||
).toFixed(2)}</td>
|
||
<td class="px-6 py-4 text-sm ${
|
||
profit >= 0 ? "text-green-600" : "text-red-600"
|
||
}">€${profit.toFixed(2)}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">${
|
||
report.username || report.userId
|
||
}</td>
|
||
<td class="px-6 py-4">
|
||
<span class="px-2 py-1 text-xs rounded-full ${
|
||
report.isVerified
|
||
? "bg-green-100 text-green-800"
|
||
: "bg-yellow-100 text-yellow-800"
|
||
}">
|
||
${report.isVerified ? "Проверен" : "Не проверен"}
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4 text-sm">
|
||
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="viewReport(${
|
||
report.id
|
||
})">
|
||
<i class="fas fa-eye"></i> Просмотр
|
||
</button>
|
||
<button class="text-red-600 hover:text-red-900" onclick="deleteReport(${
|
||
report.id
|
||
})">
|
||
<i class="fas fa-trash"></i> Удалить
|
||
</button>
|
||
</td>
|
||
`;
|
||
tbody.appendChild(row);
|
||
});
|
||
|
||
setupReportsFilters();
|
||
}
|
||
|
||
// Use global array for backend reports
|
||
function viewReport(reportId) {
|
||
if (!appState.reportsList) {
|
||
showNotification("Reports not loaded yet!", "error");
|
||
return;
|
||
}
|
||
const report = appState.reportsList.find((r) => r.id === reportId);
|
||
if (report) {
|
||
showReportModal(report, true);
|
||
} else {
|
||
showNotification("Report not found!", "error");
|
||
}
|
||
}
|
||
|
||
function editReport(report) {
|
||
console.log("editReport() called with:", report);
|
||
const content = document.getElementById("reportViewContent");
|
||
const buttons = document.getElementById("reportModalButtons");
|
||
|
||
const shopName = (report.storeName || report.storeId || "").replace(
|
||
/"/g,
|
||
"""
|
||
);
|
||
|
||
content.innerHTML = `
|
||
<form id="editReportForm" class="space-y-6">
|
||
<!-- Основная информация -->
|
||
<div class="report-section">
|
||
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-info-circle mr-2"></i>Основная информация</h4>
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-bold text-gray-700 mb-1">Магазин</label>
|
||
<input
|
||
type="text"
|
||
value="${shopName}"
|
||
class="form-input w-full px-3 py-2 rounded-lg bg-gray-100 text-gray-900 cursor-not-allowed"
|
||
readonly
|
||
disabled
|
||
>
|
||
<input type="hidden" id="editStoreSelect" value="${report.storeId}">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-bold text-gray-700 mb-1">Дата</label>
|
||
<input
|
||
type="date"
|
||
id="editDate"
|
||
value="${report.reportDate || report.date || ""}"
|
||
class="form-input w-full px-3 py-2 rounded-lg bg-gray-100 text-gray-900 cursor-not-allowed"
|
||
readonly
|
||
disabled
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Доходы -->
|
||
<div class="report-section income">
|
||
<h4 class="font-bold text-green-700 mb-3"><i class="fas fa-arrow-up mr-2"></i>Доходы</h4>
|
||
<div class="grid grid-cols-3 gap-4">
|
||
<div>
|
||
<label class="block text-sm font-bold text-gray-700 mb-1">Income</label>
|
||
<input type="number" id="editIncome" value="${
|
||
report.income || ""
|
||
}" step="0.01" class="form-input w-full px-3 py-2 rounded-lg">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-bold text-gray-700 mb-1">Caja inicial</label>
|
||
<input type="number" id="editCajaInicial" value="${
|
||
report.initialCash || report.cajaInicial || ""
|
||
}" step="0.01" class="form-input w-full px-3 py-2 rounded-lg">
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-bold text-gray-700 mb-1">Envelope</label>
|
||
<input type="number" id="editEnvelope" value="${
|
||
report.envelope || ""
|
||
}" step="0.01" class="form-input w-full px-3 py-2 rounded-lg">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Статус проверки -->
|
||
<div class="report-section">
|
||
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-check mr-2"></i>Статус</h4>
|
||
<label class="flex items-center">
|
||
<input type="checkbox" id="editVerified" ${
|
||
report.isVerified || report.verified ? "checked" : ""
|
||
} class="mr-2">
|
||
<span>Отчет проверен</span>
|
||
</label>
|
||
</div>
|
||
</form>
|
||
`;
|
||
|
||
buttons.innerHTML = `
|
||
<button id="saveReportBtn" class="btn-success text-white px-4 py-2 rounded-lg">
|
||
<i class="fas fa-save mr-2"></i>Сохранить
|
||
</button>
|
||
<button id="cancelEditBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||
<i class="fas fa-times mr-2"></i>Отмена
|
||
</button>
|
||
`;
|
||
|
||
document
|
||
.getElementById("saveReportBtn")
|
||
.addEventListener("click", async (e) => {
|
||
e.preventDefault();
|
||
await saveEditedReport(report.id);
|
||
});
|
||
|
||
document.getElementById("cancelEditBtn").addEventListener("click", (e) => {
|
||
e.preventDefault();
|
||
showReportModal(report, true);
|
||
});
|
||
}
|
||
|
||
async function saveEditedReport(reportId) {
|
||
// Get values from the form
|
||
const storeId = document.getElementById("editStoreSelect").value;
|
||
const reportDate = document.getElementById("editDate").value;
|
||
const incomeVal = document.getElementById("editIncome").value;
|
||
const initialCashVal = document.getElementById("editCajaInicial").value;
|
||
const envelopeVal = document.getElementById("editEnvelope").value;
|
||
const isVerified = document.getElementById("editVerified").checked ? 1 : 0;
|
||
|
||
// Build the payload dynamically
|
||
const data = {};
|
||
|
||
if (storeId) data.storeId = parseInt(storeId, 10);
|
||
if (reportDate) data.reportDate = reportDate;
|
||
if (incomeVal !== "") data.income = parseFloat(incomeVal);
|
||
if (initialCashVal !== "") data.initialCash = parseFloat(initialCashVal);
|
||
if (envelopeVal !== "") data.envelope = parseFloat(envelopeVal);
|
||
|
||
if (data.income !== undefined && data.initialCash !== undefined) {
|
||
data.totalIncome = data.income + data.initialCash;
|
||
}
|
||
data.isVerified = isVerified;
|
||
|
||
// REMOVE any NaN values
|
||
Object.keys(data).forEach((k) => {
|
||
if (typeof data[k] === "number" && isNaN(data[k])) delete data[k];
|
||
});
|
||
|
||
await updateReport(reportId, data);
|
||
}
|
||
|
||
// Показ модального окна отчета с исправленной прокруткой
|
||
function showReportModal(report, isAdmin = false) {
|
||
const modal = document.getElementById("reportViewModal");
|
||
const content = document.getElementById("reportViewContent");
|
||
const buttons = document.getElementById("reportModalButtons");
|
||
const title = document.getElementById("reportModalTitle");
|
||
|
||
title.textContent = `Отчет от ${report.reportDate || report.date} - ${
|
||
report.storeName || report.storeId || "Неизвестный магазин"
|
||
}`;
|
||
|
||
content.innerHTML = `
|
||
<div class="space-y-6">
|
||
<!-- Основная информация -->
|
||
<div class="report-section">
|
||
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-info-circle mr-2"></i>Основная информация</h4>
|
||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||
<div><strong>Дата:</strong> ${
|
||
report.date || report.reportDate || ""
|
||
}</div>
|
||
<div><strong>Магазин:</strong> ${
|
||
report.storeName || report.storeId
|
||
}</div>
|
||
<div><strong>Пользователь:</strong> ${
|
||
report.username || report.fullName || report.userId
|
||
}</div>
|
||
<div><strong>Статус:</strong>
|
||
<span class="px-2 py-1 rounded text-xs ${
|
||
report.isVerified || report.verified
|
||
? "bg-green-100 text-green-800"
|
||
: "bg-yellow-100 text-yellow-800"
|
||
}">
|
||
${
|
||
report.isVerified || report.verified
|
||
? "Проверен"
|
||
: "Не проверен"
|
||
}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- Доходы -->
|
||
<div class="report-section income">
|
||
<h4 class="font-bold text-green-700 mb-3"><i class="fas fa-arrow-up mr-2"></i>Доходы (Ingresos)</h4>
|
||
<div class="grid grid-cols-3 gap-4 text-sm">
|
||
<div><strong>Income:</strong> €${safeToFixed(report.income)}</div>
|
||
<div><strong>Caja inicial:</strong> €${safeToFixed(
|
||
report.cajaInicial || report.initialCash
|
||
)}</div>
|
||
<div><strong>Total income:</strong> €${safeToFixed(
|
||
report.totalIncome
|
||
)}</div>
|
||
</div>
|
||
</div>
|
||
<!-- Зарплаты -->
|
||
<div class="report-section wages">
|
||
<h4 class="font-bold text-yellow-700 mb-3"><i class="fas fa-users mr-2"></i>Зарплаты (Wages)</h4>
|
||
${
|
||
Array.isArray(report.wages) && report.wages.length > 0
|
||
? `
|
||
<div class="space-y-2">
|
||
${report.wages
|
||
.map(
|
||
(w) => `
|
||
<div class="flex justify-between text-sm">
|
||
<span>${w.name}</span>
|
||
<span>€${safeToFixed(w.amount)}</span>
|
||
</div>
|
||
`
|
||
)
|
||
.join("")}
|
||
<div class="border-t pt-2 font-bold">
|
||
<div class="flex justify-between">
|
||
<span>Total wages:</span>
|
||
<span>€${safeToFixed(report.totalWages)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`
|
||
: '<p class="text-gray-500 text-sm">Нет данных о зарплатах</p>'
|
||
}
|
||
</div>
|
||
<!-- Расходы -->
|
||
<div class="report-section expenses">
|
||
<h4 class="font-bold text-red-700 mb-3"><i class="fas fa-arrow-down mr-2"></i>Расходы (Expenses)</h4>
|
||
${
|
||
Array.isArray(report.expenses) && report.expenses.length > 0
|
||
? `
|
||
<div class="space-y-2">
|
||
${report.expenses
|
||
.map(
|
||
(e) => `
|
||
<div class="flex justify-between text-sm">
|
||
<span>${e.name}</span>
|
||
<span>€${safeToFixed(e.amount)}</span>
|
||
</div>
|
||
`
|
||
)
|
||
.join("")}
|
||
<div class="border-t pt-2 font-bold">
|
||
<div class="flex justify-between">
|
||
<span>Total expenses internal:</span>
|
||
<span>€${safeToFixed(report.totalExpensesInternal)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`
|
||
: '<p class="text-gray-500 text-sm">Нет данных о расходах</p>'
|
||
}
|
||
</div>
|
||
<!-- Итоговые расчеты -->
|
||
<div class="report-section">
|
||
<h4 class="font-bold text-gray-700 mb-3"><i class="fas fa-calculator mr-2"></i>Итоговые расчеты</h4>
|
||
<div class="space-y-2">
|
||
<div class="flex justify-between"><strong>Total income:</strong> <span>€${safeToFixed(
|
||
report.totalIncome
|
||
)}</span></div>
|
||
<div class="flex justify-between"><strong>Total expenses:</strong> <span>€${safeToFixed(
|
||
report.totalExpenses
|
||
)}</span></div>
|
||
<div class="flex justify-between"><strong>Envelope:</strong> <span>€${safeToFixed(
|
||
report.envelope
|
||
)}</span></div>
|
||
<div class="flex justify-between border-t pt-2 text-lg font-bold text-blue-700">
|
||
<strong>Caja final:</strong> <span>€${safeToFixed(
|
||
report.cajaFinal || report.finalCash
|
||
)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
buttons.innerHTML = "";
|
||
|
||
if (isAdmin) {
|
||
buttons.innerHTML = `
|
||
<button id="editReportBtn" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
|
||
<i class="fas fa-edit mr-2"></i>Редактировать
|
||
</button>
|
||
<button id="verifyReportBtn" class="btn-success text-white px-4 py-2 rounded-lg">
|
||
<i class="fas fa-check mr-2"></i>Подтвердить отчет
|
||
</button>
|
||
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||
<i class="fas fa-times mr-2"></i>Закрыть
|
||
</button>
|
||
`;
|
||
|
||
document.getElementById("editReportBtn").addEventListener("click", () => {
|
||
editReport(report);
|
||
});
|
||
|
||
document.getElementById("verifyReportBtn").addEventListener("click", () => {
|
||
verifyReport(report.id);
|
||
});
|
||
} else {
|
||
if (!report.isVerified && !report.verified) {
|
||
buttons.innerHTML = `
|
||
<button id="editReportUserBtn" class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600">
|
||
<i class="fas fa-edit mr-2"></i>Редактировать
|
||
</button>
|
||
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||
<i class="fas fa-times mr-2"></i>Закрыть
|
||
</button>
|
||
`;
|
||
|
||
document
|
||
.getElementById("editReportUserBtn")
|
||
.addEventListener("click", () => {
|
||
fillFormWithReport(report);
|
||
hideModal("reportViewModal");
|
||
});
|
||
} else {
|
||
buttons.innerHTML = `
|
||
<button id="closeReportModalBtn" class="bg-gray-500 text-white px-4 py-2 rounded-lg">
|
||
<i class="fas fa-times mr-2"></i>Закрыть
|
||
</button>
|
||
`;
|
||
}
|
||
}
|
||
|
||
document
|
||
.getElementById("closeReportModalBtn")
|
||
.addEventListener("click", () => {
|
||
hideModal("reportViewModal");
|
||
});
|
||
|
||
showModal("reportViewModal");
|
||
}
|
||
|
||
// Настройка фильтров отчетов
|
||
function setupReportsFilters() {
|
||
document
|
||
.getElementById("applyFilters")
|
||
.addEventListener("click", applyReportsFilters);
|
||
document
|
||
.getElementById("exportExcel")
|
||
.addEventListener("click", exportToExcel);
|
||
}
|
||
|
||
function applyReportsFilters() {
|
||
const storeFilter = document.getElementById("filterStore").value;
|
||
const dateFrom = document.getElementById("filterDateFrom").value;
|
||
const dateTo = document.getElementById("filterDateTo").value;
|
||
|
||
let filteredReports = appState.reportsList || [];
|
||
|
||
if (storeFilter) {
|
||
filteredReports = filteredReports.filter(
|
||
(r) => String(r.storeId) === String(storeFilter)
|
||
);
|
||
}
|
||
|
||
if (dateFrom) {
|
||
filteredReports = filteredReports.filter((r) => r.reportDate >= dateFrom);
|
||
}
|
||
|
||
if (dateTo) {
|
||
filteredReports = filteredReports.filter((r) => r.reportDate <= dateTo);
|
||
}
|
||
|
||
// Обновление таблицы с отфильтрованными данными
|
||
const tbody = document.getElementById("reportsTableBody");
|
||
tbody.innerHTML = "";
|
||
|
||
filteredReports.forEach((report) => {
|
||
const storeName = report.storeName || report.storeId;
|
||
const username = report.username || report.userId;
|
||
const profit =
|
||
(Number(report.totalIncome) || 0) - (Number(report.totalExpenses) || 0);
|
||
|
||
const row = document.createElement("tr");
|
||
row.className = "hover:bg-gray-50";
|
||
row.innerHTML = `
|
||
<td class="px-6 py-4 text-sm text-gray-900">${
|
||
report.reportDate || report.date || ""
|
||
}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">${storeName}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">€${report.totalIncome.toFixed(
|
||
2
|
||
)}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">€${report.totalExpenses.toFixed(
|
||
2
|
||
)}</td>
|
||
<td class="px-6 py-4 text-sm ${
|
||
profit >= 0 ? "text-green-600" : "text-red-600"
|
||
}">€${profit.toFixed(2)}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">${username}</td>
|
||
<td class="px-6 py-4">
|
||
<span class="px-2 py-1 text-xs rounded-full ${
|
||
report.isVerified
|
||
? "bg-green-100 text-green-800"
|
||
: "bg-yellow-100 text-yellow-800"
|
||
}">
|
||
${report.isVerified ? "Проверен" : "Не проверен"}
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4 text-sm">
|
||
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="viewReport(${
|
||
report.id
|
||
})">
|
||
<i class="fas fa-eye"></i> Просмотр
|
||
</button>
|
||
<button class="text-red-600 hover:text-red-900" onclick="deleteReport(${
|
||
report.id
|
||
})">
|
||
<i class="fas fa-trash"></i> Удалить
|
||
</button>
|
||
</td>
|
||
`;
|
||
tbody.appendChild(row);
|
||
});
|
||
|
||
showNotification(`Найдено ${filteredReports.length} отчетов`);
|
||
}
|
||
|
||
function deleteReport(reportId) {
|
||
showConfirmModal("Вы уверены, что хотите удалить этот отчет?", () => {
|
||
apiDeleteReport(reportId);
|
||
loadReports();
|
||
updateDashboard();
|
||
showNotification("Отчет удален!");
|
||
});
|
||
}
|
||
|
||
// Заполнение формы данными отчета (для пользователя)
|
||
function fillFormWithReport(report) {
|
||
appState.editingReportId = report.id;
|
||
|
||
document.getElementById("storeSelect").value = report.storeId;
|
||
document.getElementById("income").value = report.income;
|
||
document.getElementById("cajaInicial").value = report.cajaInicial;
|
||
document.getElementById("envelope").value = report.envelope;
|
||
|
||
// Заполнение зарплат
|
||
const wagesContainer = document.getElementById("wagesContainer");
|
||
wagesContainer.innerHTML = "";
|
||
if (report.wages && report.wages.length > 0) {
|
||
report.wages.forEach((wage) => {
|
||
const row = document.createElement("div");
|
||
row.className = "wage-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
|
||
row.innerHTML = `
|
||
<input type="text" placeholder="Имя сотрудника" value="${wage.name}" class="wage-name form-input px-3 py-2 rounded-lg">
|
||
<input type="number" step="0.01" placeholder="Сумма €" value="${wage.amount}" class="wage-amount form-input px-3 py-2 rounded-lg">
|
||
<button type="button" class="remove-wage bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
`;
|
||
wagesContainer.appendChild(row);
|
||
});
|
||
} else {
|
||
addWageRow();
|
||
}
|
||
|
||
// Заполнение расходов
|
||
const expensesContainer = document.getElementById("expensesContainer");
|
||
expensesContainer.innerHTML = "";
|
||
if (report.expenses && report.expenses.length > 0) {
|
||
report.expenses.forEach((expense) => {
|
||
const row = document.createElement("div");
|
||
row.className = "expense-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
|
||
row.innerHTML = `
|
||
<input type="text" placeholder="Название расхода" value="${expense.name}" class="expense-name form-input px-3 py-2 rounded-lg">
|
||
<input type="number" step="0.01" placeholder="Сумма €" value="${expense.amount}" class="expense-amount form-input px-3 py-2 rounded-lg">
|
||
<button type="button" class="remove-expense bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
`;
|
||
expensesContainer.appendChild(row);
|
||
});
|
||
} else {
|
||
addExpenseRow();
|
||
}
|
||
|
||
updateTotals();
|
||
}
|
||
|
||
// Экспорт в Excel
|
||
function exportToExcel() {
|
||
// Use backend data
|
||
const reports = appState.reportsList || [];
|
||
const stores = appState.storesList || [];
|
||
const users = appState.usersList || [];
|
||
|
||
if (!reports.length) {
|
||
showNotification("Нет отчетов для экспорта", "info");
|
||
return;
|
||
}
|
||
|
||
const data = reports.map((report) => {
|
||
// Find store and user by id
|
||
const store =
|
||
stores.find((s) => Number(s.id) === Number(report.storeId)) || {};
|
||
const user =
|
||
users.find((u) => Number(u.id) === Number(report.userId)) || {};
|
||
|
||
const profit =
|
||
(Number(report.totalIncome) || 0) - (Number(report.totalExpenses) || 0);
|
||
|
||
return {
|
||
Дата: report.reportDate || report.date || "",
|
||
Магазин: store.name || report.storeName || "Неизвестно",
|
||
Доход: `€${Number(report.totalIncome || 0).toFixed(2)}`,
|
||
Расходы: `€${Number(report.totalExpenses || 0).toFixed(2)}`,
|
||
Прибыль: `€${profit.toFixed(2)}`,
|
||
Пользователь: user.username || report.username || "Неизвестно",
|
||
Статус: report.isVerified
|
||
? "Проверен"
|
||
: report.verified
|
||
? "Проверен"
|
||
: "Не проверен",
|
||
};
|
||
});
|
||
|
||
if (!data.length) {
|
||
showNotification("Нет отчетов для экспорта", "info");
|
||
return;
|
||
}
|
||
|
||
// Create CSV
|
||
const headers = Object.keys(data[0]);
|
||
const csvContent = [
|
||
headers.join(","),
|
||
...data.map((row) => headers.map((header) => `"${row[header]}"`).join(",")),
|
||
].join("\n");
|
||
|
||
// Download file
|
||
const blob = new Blob([csvContent], {
|
||
type: "text/csv;charset=utf-8;",
|
||
});
|
||
const link = document.createElement("a");
|
||
const url = URL.createObjectURL(blob);
|
||
link.setAttribute("href", url);
|
||
link.setAttribute(
|
||
"download",
|
||
`cash_reports_${new Date().toISOString().split("T")[0]}.csv`
|
||
);
|
||
link.style.visibility = "hidden";
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
|
||
showNotification("Отчет экспортирован!");
|
||
}
|
||
|
||
//REPORTS FORM LOGIC
|
||
|
||
// Загрузка магазинов для пользователя
|
||
function loadUserStores() {
|
||
const select = document.getElementById("storeSelect");
|
||
if (!select) return;
|
||
select.innerHTML =
|
||
'<option value="" disabled selected hidden>Выберите магазин</option>';
|
||
|
||
// For admin: show all
|
||
if (appState.currentUser.role === "admin") {
|
||
(appState.storesList || []).forEach((store) => {
|
||
const option = document.createElement("option");
|
||
option.value = store.id;
|
||
option.textContent = store.name;
|
||
select.appendChild(option);
|
||
});
|
||
} else {
|
||
// For employee: only their own stores
|
||
(appState.currentUser.stores || []).forEach((storeObj) => {
|
||
// storeObj could be an object ({id, name, ...}) or just an ID; check backend API
|
||
let store = storeObj;
|
||
if (typeof storeObj === "number") {
|
||
// If backend sends just IDs, look up in appState.storesList
|
||
store = (appState.storesList || []).find((s) => s.id === storeObj);
|
||
}
|
||
if (store) {
|
||
const option = document.createElement("option");
|
||
option.value = store.id;
|
||
option.textContent = store.name;
|
||
select.appendChild(option);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// Настройка автоматических расчетов в форме
|
||
function setupFormCalculations() {
|
||
const incomeInput = document.getElementById("income");
|
||
const cajaInicialInput = document.getElementById("cajaInicial");
|
||
const envelopeInput = document.getElementById("envelope");
|
||
|
||
function updateCalculations() {
|
||
const income = parseFloat(incomeInput.value) || 0;
|
||
const cajaInicial = parseFloat(cajaInicialInput.value) || 0;
|
||
const envelope = parseFloat(envelopeInput.value) || 0;
|
||
|
||
const totalIncome = income + cajaInicial;
|
||
document.getElementById("totalIncome").value = totalIncome.toFixed(2);
|
||
document.getElementById("displayTotalIncome").value =
|
||
totalIncome.toFixed(2);
|
||
|
||
const totalWages = calculateTotalWages();
|
||
const totalExpensesInternal = calculateTotalExpenses();
|
||
const totalExpenses = totalWages + totalExpensesInternal;
|
||
|
||
document.getElementById("totalExpenses").value = totalExpenses.toFixed(2);
|
||
|
||
const cajaFinal = totalIncome - totalExpenses - envelope;
|
||
document.getElementById("cajaFinal").textContent = cajaFinal.toFixed(2);
|
||
}
|
||
|
||
incomeInput.addEventListener("input", updateCalculations);
|
||
cajaInicialInput.addEventListener("input", updateCalculations);
|
||
envelopeInput.addEventListener("input", updateCalculations);
|
||
|
||
setupDynamicRows();
|
||
}
|
||
|
||
// Настройка динамических строк
|
||
function setupDynamicRows() {
|
||
document.getElementById("addWage").addEventListener("click", () => {
|
||
addWageRow();
|
||
});
|
||
|
||
document.getElementById("addExpense").addEventListener("click", () => {
|
||
addExpenseRow();
|
||
});
|
||
|
||
// Обновление при изменении значений
|
||
document.addEventListener("input", (e) => {
|
||
if (
|
||
e.target.classList.contains("wage-amount") ||
|
||
e.target.classList.contains("expense-amount")
|
||
) {
|
||
updateTotals();
|
||
}
|
||
});
|
||
|
||
// Удаление строк
|
||
document.addEventListener("click", (e) => {
|
||
if (e.target.classList.contains("remove-wage")) {
|
||
e.target.closest(".wage-row").remove();
|
||
updateTotals();
|
||
} else if (e.target.classList.contains("remove-expense")) {
|
||
e.target.closest(".expense-row").remove();
|
||
updateTotals();
|
||
}
|
||
});
|
||
}
|
||
|
||
function addWageRow() {
|
||
const container = document.getElementById("wagesContainer");
|
||
const row = document.createElement("div");
|
||
row.className = "wage-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
|
||
row.innerHTML = `
|
||
<input type="text" placeholder="Имя сотрудника" class="wage-name form-input px-3 py-2 rounded-lg">
|
||
<input type="number" step="0.01" placeholder="Сумма €" class="wage-amount form-input px-3 py-2 rounded-lg">
|
||
<button type="button" class="remove-wage bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
`;
|
||
container.appendChild(row);
|
||
}
|
||
|
||
function addExpenseRow() {
|
||
const container = document.getElementById("expensesContainer");
|
||
const row = document.createElement("div");
|
||
row.className = "expense-row grid grid-cols-1 md:grid-cols-3 gap-4 mb-3";
|
||
row.innerHTML = `
|
||
<input type="text" placeholder="Название расхода" class="expense-name form-input px-3 py-2 rounded-lg">
|
||
<input type="number" step="0.01" placeholder="Сумма €" class="expense-amount form-input px-3 py-2 rounded-lg">
|
||
<button type="button" class="remove-expense bg-red-500 text-white px-3 py-2 rounded-lg hover:bg-red-600 transition-colors">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
`;
|
||
container.appendChild(row);
|
||
}
|
||
|
||
function calculateTotalWages() {
|
||
const amounts = document.querySelectorAll(".wage-amount");
|
||
let total = 0;
|
||
amounts.forEach((amount) => {
|
||
total += parseFloat(amount.value) || 0;
|
||
});
|
||
return total;
|
||
}
|
||
|
||
function calculateTotalExpenses() {
|
||
const amounts = document.querySelectorAll(".expense-amount");
|
||
let total = 0;
|
||
amounts.forEach((amount) => {
|
||
total += parseFloat(amount.value) || 0;
|
||
});
|
||
return total;
|
||
}
|
||
|
||
function updateTotals() {
|
||
const totalWages = calculateTotalWages();
|
||
const totalExpensesInternal = calculateTotalExpenses();
|
||
|
||
document.getElementById("totalWages").textContent = totalWages.toFixed(2);
|
||
document.getElementById("totalExpensesInternal").textContent =
|
||
totalExpensesInternal.toFixed(2);
|
||
|
||
const totalExpenses = totalWages + totalExpensesInternal;
|
||
document.getElementById("totalExpenses").value = totalExpenses.toFixed(2);
|
||
|
||
// Обновить Caja Final
|
||
const income = parseFloat(document.getElementById("income").value) || 0;
|
||
const cajaInicial =
|
||
parseFloat(document.getElementById("cajaInicial").value) || 0;
|
||
const envelope = parseFloat(document.getElementById("envelope").value) || 0;
|
||
const totalIncome = income + cajaInicial;
|
||
const cajaFinal = totalIncome - totalExpenses - envelope;
|
||
|
||
document.getElementById("cajaFinal").textContent = cajaFinal.toFixed(2);
|
||
}
|
||
|
||
function collectWages() {
|
||
const wages = [];
|
||
document.querySelectorAll(".wage-row").forEach((row) => {
|
||
const name = row.querySelector(".wage-name").value;
|
||
const amount = parseFloat(row.querySelector(".wage-amount").value) || 0;
|
||
if (name && amount > 0) wages.push({ name, amount });
|
||
});
|
||
return wages;
|
||
}
|
||
function collectExpenses() {
|
||
const expenses = [];
|
||
document.querySelectorAll(".expense-row").forEach((row) => {
|
||
const name = row.querySelector(".expense-name").value;
|
||
const amount = parseFloat(row.querySelector(".expense-amount").value) || 0;
|
||
if (name && amount > 0) expenses.push({ name, amount });
|
||
});
|
||
return expenses;
|
||
}
|
||
|
||
function safeToFixed(value, digits = 2) {
|
||
return (Number(value) || 0).toFixed(digits);
|
||
}
|
||
|
||
document.getElementById("reportForm").addEventListener("submit", async (e) => {
|
||
e.preventDefault();
|
||
|
||
const formData = {
|
||
storeId: parseInt(document.getElementById("storeSelect").value),
|
||
reportDate: new Date().toISOString().split("T")[0], // Or get from input if user chooses date
|
||
income: parseFloat(document.getElementById("income").value),
|
||
initialCash: parseFloat(document.getElementById("cajaInicial").value),
|
||
totalIncome: parseFloat(document.getElementById("totalIncome").value),
|
||
wages: JSON.stringify(collectWages()),
|
||
expenses: JSON.stringify(collectExpenses()),
|
||
totalWages: calculateTotalWages(),
|
||
totalExpenses: calculateTotalExpenses() + calculateTotalWages(),
|
||
envelope: parseFloat(document.getElementById("envelope").value),
|
||
finalCash:
|
||
parseFloat(document.getElementById("totalIncome").value) -
|
||
(calculateTotalWages() + calculateTotalExpenses()) -
|
||
parseFloat(document.getElementById("envelope").value),
|
||
};
|
||
console.log("Sending report:", formData);
|
||
const result = await createReport(formData);
|
||
if (result.success) {
|
||
showNotification("Отчет успешно создан!");
|
||
await loadReports();
|
||
document.getElementById("reportForm").reset();
|
||
} else {
|
||
showNotification(result.error || "Ошибка создания отчета", "error");
|
||
}
|
||
});
|
||
|
||
// Отчет за сегодня для пользователя
|
||
document.getElementById("todayReportBtn").addEventListener("click", () => {
|
||
const today = new Date().toISOString().split("T")[0];
|
||
const todayReport = database.reports.find(
|
||
(r) => r.date === today && r.userId === appState.currentUser.id
|
||
);
|
||
|
||
if (todayReport) {
|
||
showReportModal(todayReport, false); // false = не админ режим
|
||
} else {
|
||
showNotification("Отчет за сегодня не найден", "error");
|
||
}
|
||
});
|
||
|
||
//USERS
|
||
|
||
async function loadUsers() {
|
||
console.log("Loading users...");
|
||
const tbody = document.getElementById("usersTableBody");
|
||
tbody.innerHTML = "";
|
||
|
||
const result = await getAllUsers();
|
||
|
||
// console.log("getAllUsers result:", result);
|
||
|
||
if (!result.success) {
|
||
showNotification(result.error, "error");
|
||
return;
|
||
}
|
||
|
||
const users = result.users;
|
||
appState.usersList = users;
|
||
|
||
users.forEach((user) => {
|
||
const userStores =
|
||
user.stores
|
||
.map((storeId) => {
|
||
const store = (appState.storesList || []).find(
|
||
(s) => s.id === storeId
|
||
);
|
||
return store ? store.name : "Нет доступа";
|
||
})
|
||
.join(", ") || "Нет доступа";
|
||
|
||
const row = document.createElement("tr");
|
||
row.className = "hover:bg-gray-50";
|
||
row.innerHTML = `
|
||
<td class="px-6 py-4 text-sm text-gray-900">${user.id}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">${user.username}</td>
|
||
<td class="px-6 py-4 text-sm">
|
||
<span class="px-2 py-1 text-xs rounded-full ${
|
||
user.role === "admin"
|
||
? "bg-purple-100 text-purple-800"
|
||
: "bg-blue-100 text-blue-800"
|
||
}">
|
||
${user.role === "admin" ? "Администратор" : "Сотрудник"}
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">${userStores}</td>
|
||
<td class="px-6 py-4 text-sm">
|
||
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="editUser(${
|
||
user.id
|
||
})">
|
||
<i class="fas fa-edit"></i> Редактировать
|
||
</button>
|
||
<button class="text-red-600 hover:text-red-900" onclick="deleteUser(${
|
||
user.id
|
||
})">
|
||
<i class="fas fa-trash"></i> Удалить
|
||
</button>
|
||
</td>
|
||
`;
|
||
tbody.appendChild(row);
|
||
});
|
||
|
||
if (users.length === 0) {
|
||
showNotification("Нет пользователей для отображения", "info");
|
||
}
|
||
}
|
||
|
||
// Редактирование пользователя
|
||
function editUser(userId) {
|
||
appState.editingUserId = userId;
|
||
const user = appState.usersList.find((u) => u.id === userId);
|
||
showUserEditModal(user);
|
||
}
|
||
|
||
// Показ модального окна редактирования пользователя
|
||
function showUserEditModal(user) {
|
||
const modal = document.getElementById("userEditModal");
|
||
const title = document.getElementById("userModalTitle");
|
||
const form = document.getElementById("userEditForm");
|
||
|
||
// If appState.editingUserId, it's an edit; else, it's add
|
||
title.textContent = appState.editingUserId
|
||
? "Редактирование пользователя"
|
||
: "Добавление пользователя";
|
||
|
||
document.getElementById("userLogin").value = (user && user.username) || "";
|
||
const loginInput = document.getElementById("userLogin");
|
||
loginInput.value = (user && user.username) || "";
|
||
if (appState.editingUserId) {
|
||
loginInput.disabled = true;
|
||
} else {
|
||
loginInput.disabled = false;
|
||
}
|
||
|
||
document.getElementById("userPassword").value = "";
|
||
document.getElementById("userRole").value = (user && user.role) || "employee";
|
||
|
||
// Загрузка чекбоксов магазинов
|
||
const storesContainer = document.getElementById("userStoresAccess");
|
||
storesContainer.innerHTML = "";
|
||
|
||
(appState.storesList || []).forEach((store) => {
|
||
const isChecked = user && user.stores && user.stores.includes(store.id);
|
||
const checkbox = document.createElement("label");
|
||
checkbox.className = "flex items-center";
|
||
checkbox.innerHTML = `
|
||
<input type="checkbox" value="${store.id}" ${
|
||
isChecked ? "checked" : ""
|
||
} class="mr-2">
|
||
<span>${store.name}</span>
|
||
`;
|
||
storesContainer.appendChild(checkbox);
|
||
});
|
||
|
||
showModal("userEditModal");
|
||
}
|
||
|
||
//save user for create and update
|
||
async function saveUser() {
|
||
const login = document.getElementById("userLogin").value.trim();
|
||
const password = document.getElementById("userPassword").value;
|
||
const role = document.getElementById("userRole").value;
|
||
const selectedStores = Array.from(
|
||
document.querySelectorAll(
|
||
'#userStoresAccess input[type="checkbox"]:checked'
|
||
)
|
||
).map((cb) => parseInt(cb.value));
|
||
|
||
if (!login) {
|
||
showNotification("Заполните логин!", "error");
|
||
return;
|
||
}
|
||
|
||
// Always build userData
|
||
const userData = {
|
||
username: login,
|
||
role: role,
|
||
storeIds: selectedStores,
|
||
};
|
||
if (password) userData.password = password;
|
||
|
||
// Determine CREATE or EDIT
|
||
let result;
|
||
if (!appState.editingUserId) {
|
||
if (!password) {
|
||
showNotification("Укажите пароль для нового пользователя!", "error");
|
||
return;
|
||
}
|
||
// CREATE
|
||
result = await createUser(userData);
|
||
if (result.success) {
|
||
showNotification("Пользователь добавлен!");
|
||
}
|
||
} else {
|
||
// EDIT
|
||
result = await updateUser(appState.editingUserId, userData);
|
||
if (result.success) {
|
||
showNotification("Пользователь обновлен!");
|
||
}
|
||
appState.editingUserId = null;
|
||
}
|
||
|
||
// After save: UI update or error
|
||
if (result && result.success) {
|
||
hideModal("userEditModal");
|
||
loadUsers();
|
||
updateDashboard();
|
||
} else if (result) {
|
||
showNotification(result.error || "Ошибка операции", "error");
|
||
}
|
||
}
|
||
|
||
//UI trigger: delete user with modal
|
||
function deleteUser(userId) {
|
||
showConfirmModal("Вы уверены, что хотите удалить этого пользователя?", () =>
|
||
apiDeleteUser(userId)
|
||
);
|
||
}
|
||
|
||
// Добавление пользователя
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
const addUserBtn = document.getElementById("addUserBtn");
|
||
if (addUserBtn) {
|
||
addUserBtn.addEventListener("click", () => {
|
||
appState.editingUserId = null;
|
||
showUserEditModal();
|
||
});
|
||
}
|
||
});
|
||
|
||
// Сохранение пользователя
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
const saveUserBtn = document.getElementById("saveUserBtn");
|
||
const cancelUserBtn = document.getElementById("cancelUserBtn");
|
||
|
||
if (saveUserBtn) {
|
||
saveUserBtn.addEventListener("click", saveUser);
|
||
}
|
||
|
||
if (cancelUserBtn) {
|
||
cancelUserBtn.addEventListener("click", () => {
|
||
hideModal("userEditModal");
|
||
});
|
||
}
|
||
});
|
||
|
||
//SHOPS
|
||
|
||
// Загрузка магазинов
|
||
async function loadStores() {
|
||
const tbody = document.getElementById("storesTableBody");
|
||
tbody.innerHTML = "";
|
||
|
||
const token = localStorage.getItem("token");
|
||
try {
|
||
const response = await fetch(`${API_BASE_URL}/stores`, {
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
"Content-Type": "application/json",
|
||
},
|
||
});
|
||
const data = await response.json();
|
||
if (!response.ok || !data.stores) {
|
||
showNotification(data.error || "Ошибка загрузки магазинов", "error");
|
||
return;
|
||
}
|
||
const stores = data.stores;
|
||
// appState.usersList = users;
|
||
appState.storesList = stores;
|
||
|
||
stores.forEach((store) => {
|
||
const row = document.createElement("tr");
|
||
row.className = "hover:bg-gray-50";
|
||
row.innerHTML = `
|
||
<td class="px-6 py-4 text-sm text-gray-900">${store.id}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">${store.name}</td>
|
||
<td class="px-6 py-4 text-sm text-gray-900">${
|
||
store.reportsCount || 0
|
||
}</td>
|
||
<td class="px-6 py-4 text-sm">
|
||
<button class="text-blue-600 hover:text-blue-900 mr-2" onclick="editStore(${
|
||
store.id
|
||
})">
|
||
<i class="fas fa-edit"></i> Редактировать
|
||
</button>
|
||
<button class="text-red-600 hover:text-red-900" onclick="deleteStore(${
|
||
store.id
|
||
})">
|
||
<i class="fas fa-trash"></i> Удалить
|
||
</button>
|
||
</td>
|
||
`;
|
||
tbody.appendChild(row);
|
||
});
|
||
|
||
if (stores.length === 0) {
|
||
showNotification("Нет магазинов для отображения", "info");
|
||
}
|
||
} catch (err) {
|
||
showNotification("Нет соединения с сервером", "error");
|
||
}
|
||
}
|
||
|
||
// Редактирование магазина
|
||
function editStore(storeId) {
|
||
appState.editingStoreId = storeId;
|
||
showStoreEditModal();
|
||
}
|
||
|
||
// Показ модального окна редактирования магазина
|
||
function showStoreEditModal() {
|
||
const modal = document.getElementById("storeEditModal");
|
||
const title = document.getElementById("storeModalTitle");
|
||
const form = document.getElementById("storeEditForm");
|
||
|
||
const store = appState.storesList.find(
|
||
(s) => s.id === appState.editingStoreId
|
||
);
|
||
|
||
if (!store) {
|
||
showNotification("Магазин не найден!", "error");
|
||
return;
|
||
}
|
||
|
||
title.textContent = "Редактирование магазина";
|
||
document.getElementById("storeName").value = store.name || "";
|
||
showModal("storeEditModal");
|
||
}
|
||
|
||
async function saveStore() {
|
||
const name = document.getElementById("storeName").value.trim();
|
||
if (!name) {
|
||
showNotification("Заполните название магазина!", "error");
|
||
return;
|
||
}
|
||
|
||
let result;
|
||
if (appState.editingStoreId == null) {
|
||
// Add
|
||
result = await createStore({ name });
|
||
if (result.success) {
|
||
showNotification("Магазин добавлен!");
|
||
hideModal("storeEditModal");
|
||
await loadStores();
|
||
if (typeof loadUsers === "function") loadUsers();
|
||
if (typeof loadUserStores === "function") loadUserStores();
|
||
if (typeof updateDashboard === "function") updateDashboard();
|
||
}
|
||
} else {
|
||
// Edit
|
||
result = await updateStore(appState.editingStoreId, { name });
|
||
if (result.success) {
|
||
showNotification("Магазин обновлен!");
|
||
hideModal("storeEditModal");
|
||
await loadStores();
|
||
await loadReports();
|
||
if (typeof loadUsers === "function") loadUsers();
|
||
if (typeof loadUserStores === "function") loadUserStores();
|
||
if (typeof updateDashboard === "function") updateDashboard();
|
||
}
|
||
}
|
||
|
||
if (result && !result.success) {
|
||
if (
|
||
result.status === 409 ||
|
||
(result.error && result.error.includes("already exists"))
|
||
) {
|
||
showNotification("Магазин с таким названием уже существует!", "error");
|
||
} else {
|
||
showNotification(result.error || "Ошибка сохранения магазина", "error");
|
||
}
|
||
return;
|
||
}
|
||
|
||
hideModal("storeEditModal");
|
||
appState.editingStoreId = null;
|
||
await loadStores();
|
||
if (typeof updateDashboard === "function") updateDashboard();
|
||
}
|
||
|
||
// Удаление магазина
|
||
function deleteStore(storeId) {
|
||
const store = appState.storesList.find((s) => s.id === storeId);
|
||
let message = `Вы уверены, что хотите удалить этот магазин "${store.name}"?`;
|
||
|
||
if (store.reportsCount && store.reportsCount > 0) {
|
||
message += `\n\nОбратите внимание! У этого магазина ${store.reportsCount} есть связынные с ним отчеты. Они также будут удалены.`;
|
||
}
|
||
|
||
showConfirmModal(message, () => handleDeleteStore(storeId));
|
||
}
|
||
|
||
async function handleDeleteStore(storeId) {
|
||
const result = await deleteStoreApi(storeId);
|
||
if (result.success) {
|
||
showNotification("Shop and related reports deleted!");
|
||
await loadStores();
|
||
if (typeof loadReports === "function") loadReports();
|
||
if (typeof updateDashboard === "function") updateDashboard();
|
||
if (typeof loadUsers === "function") loadUsers();
|
||
} else {
|
||
showNotification(result.error || "Failed to delete shop", "error");
|
||
}
|
||
}
|
||
|
||
// Добавление магазина
|
||
document.getElementById("addStoreBtn").onclick = () => {
|
||
appState.editingStoreId = null;
|
||
document.getElementById("storeModalTitle").textContent =
|
||
"Добавление магазина";
|
||
document.getElementById("storeName").value = "";
|
||
showModal("storeEditModal");
|
||
};
|
||
|
||
// Сохранение магазина
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
const saveStoreBtn = document.getElementById("saveStoreBtn");
|
||
const cancelStoreBtn = document.getElementById("cancelStoreBtn");
|
||
if (saveStoreBtn) saveStoreBtn.onclick = saveStore;
|
||
if (cancelStoreBtn)
|
||
cancelStoreBtn.onclick = () => {
|
||
hideModal("storeEditModal");
|
||
appState.editingStoreId = null;
|
||
document.getElementById("storeEditForm").reset();
|
||
};
|
||
});
|
||
|
||
//TODO
|
||
|
||
// Загрузка TODO
|
||
function loadTodos() {
|
||
const container = document.getElementById("todoList");
|
||
container.innerHTML = "";
|
||
|
||
database.todos.forEach((todo) => {
|
||
const todoItem = document.createElement("div");
|
||
todoItem.className = `p-4 border rounded-lg ${
|
||
todo.completed
|
||
? "bg-green-50 border-green-200"
|
||
: "bg-white border-gray-200"
|
||
}`;
|
||
|
||
const priorityColors = {
|
||
low: "bg-blue-100 text-blue-800",
|
||
medium: "bg-yellow-100 text-yellow-800",
|
||
high: "bg-red-100 text-red-800",
|
||
};
|
||
|
||
const priorityText = {
|
||
low: "Низкий",
|
||
medium: "Средний",
|
||
high: "Высокий",
|
||
};
|
||
|
||
todoItem.innerHTML = `
|
||
<div class="flex items-start justify-between">
|
||
<div class="flex items-start space-x-3 flex-1">
|
||
<input type="checkbox" ${
|
||
todo.completed ? "checked" : ""
|
||
}
|
||
onchange="toggleTodo(${todo.id})"
|
||
class="mt-1">
|
||
<div class="flex-1">
|
||
<h4 class="font-medium ${
|
||
todo.completed
|
||
? "line-through text-gray-500"
|
||
: "text-gray-900"
|
||
}">${todo.title}</h4>
|
||
<p class="text-sm text-gray-600 mt-1">${
|
||
todo.description
|
||
}</p>
|
||
<div class="flex items-center space-x-2 mt-2">
|
||
<span class="px-2 py-1 text-xs rounded-full ${
|
||
priorityColors[todo.priority]
|
||
}">
|
||
${priorityText[todo.priority]}
|
||
</span>
|
||
<span class="text-xs text-gray-500">${
|
||
todo.createdAt
|
||
}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="flex space-x-2">
|
||
<button onclick="editTodo(${
|
||
todo.id
|
||
})" class="text-blue-600 hover:text-blue-900">
|
||
<i class="fas fa-edit"></i>
|
||
</button>
|
||
<button onclick="deleteTodo(${
|
||
todo.id
|
||
})" class="text-red-600 hover:text-red-900">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
container.appendChild(todoItem);
|
||
});
|
||
}
|
||
|
||
// Переключение статуса TODO
|
||
function toggleTodo(todoId) {
|
||
const todoIndex = database.todos.findIndex((t) => t.id === todoId);
|
||
database.todos[todoIndex].completed = !database.todos[todoIndex].completed;
|
||
loadTodos();
|
||
showNotification("Статус задачи обновлен!");
|
||
}
|
||
|
||
// Редактирование TODO
|
||
function editTodo(todoId) {
|
||
appState.editingTodoId = todoId;
|
||
showTodoEditModal();
|
||
}
|
||
|
||
// Показ модального окна редактирования TODO
|
||
function showTodoEditModal() {
|
||
const modal = document.getElementById("todoEditModal");
|
||
const title = document.getElementById("todoModalTitle");
|
||
const form = document.getElementById("todoEditForm");
|
||
|
||
if (appState.editingTodoId) {
|
||
const todo = database.todos.find((t) => t.id === appState.editingTodoId);
|
||
title.textContent = "Редактирование задачи";
|
||
document.getElementById("todoTitle").value = todo.title;
|
||
document.getElementById("todoDescription").value = todo.description;
|
||
document.getElementById("todoPriority").value = todo.priority;
|
||
} else {
|
||
title.textContent = "Добавление задачи";
|
||
form.reset();
|
||
}
|
||
|
||
showModal("todoEditModal");
|
||
}
|
||
|
||
function saveTodo() {
|
||
const title = document.getElementById("todoTitle").value;
|
||
const description = document.getElementById("todoDescription").value;
|
||
const priority = document.getElementById("todoPriority").value;
|
||
|
||
if (!title) {
|
||
showNotification("Заполните заголовок!", "error");
|
||
return;
|
||
}
|
||
|
||
if (appState.editingTodoId) {
|
||
// Редактирование
|
||
const todoIndex = database.todos.findIndex(
|
||
(t) => t.id === appState.editingTodoId
|
||
);
|
||
database.todos[todoIndex].title = title;
|
||
database.todos[todoIndex].description = description;
|
||
database.todos[todoIndex].priority = priority;
|
||
showNotification("Задача обновлена!");
|
||
} else {
|
||
// Добавление
|
||
const newId = Math.max(...database.todos.map((t) => t.id)) + 1;
|
||
database.todos.push({
|
||
id: newId,
|
||
title: title,
|
||
description: description,
|
||
priority: priority,
|
||
completed: false,
|
||
createdAt: new Date().toISOString().split("T")[0],
|
||
});
|
||
showNotification("Задача добавлена!");
|
||
}
|
||
|
||
hideModal("todoEditModal");
|
||
loadTodos();
|
||
}
|
||
|
||
// Удаление TODO
|
||
function deleteTodo(todoId) {
|
||
if (confirm("Вы уверены, что хотите удалить эту задачу?")) {
|
||
database.todos = database.todos.filter((t) => t.id !== todoId);
|
||
loadTodos();
|
||
showNotification("Задача удалена!");
|
||
}
|
||
}
|
||
|
||
// Добавление TODO
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
const addTodoBtn = document.getElementById("addTodoBtn");
|
||
if (addTodoBtn) {
|
||
addTodoBtn.addEventListener("click", () => {
|
||
appState.editingTodoId = null;
|
||
showTodoEditModal();
|
||
});
|
||
}
|
||
});
|
||
|
||
// Сохранение TODO
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
const saveTodoBtn = document.getElementById("saveTodoBtn");
|
||
const cancelTodoBtn = document.getElementById("cancelTodoBtn");
|
||
|
||
if (saveTodoBtn) {
|
||
saveTodoBtn.addEventListener("click", saveTodo);
|
||
}
|
||
|
||
if (cancelTodoBtn) {
|
||
cancelTodoBtn.addEventListener("click", () => {
|
||
hideModal("todoEditModal");
|
||
});
|
||
}
|
||
});
|
||
|
||
// ####################MOCK###########
|
||
// База данных (симуляция)
|
||
let database = {
|
||
users: [
|
||
{
|
||
id: 1,
|
||
username: "admin",
|
||
password: "admin123",
|
||
role: "admin",
|
||
stores: [],
|
||
},
|
||
{
|
||
id: 2,
|
||
username: "employee",
|
||
password: "password123",
|
||
role: "employee",
|
||
stores: [1, 2],
|
||
},
|
||
{
|
||
id: 3,
|
||
username: "cashier1",
|
||
password: "password123",
|
||
role: "employee",
|
||
stores: [1, 2],
|
||
},
|
||
{
|
||
id: 4,
|
||
username: "cashier2",
|
||
password: "password123",
|
||
role: "employee",
|
||
stores: [3],
|
||
},
|
||
],
|
||
stores: [
|
||
{ id: 1, name: "Магазин 1" },
|
||
{ id: 2, name: "Магазин 2" },
|
||
{ id: 3, name: "Магазин 3" },
|
||
{ id: 4, name: "Магазин 4" },
|
||
],
|
||
reports: [],
|
||
todos: [
|
||
{
|
||
id: 1,
|
||
title: "Исправить модальные окна",
|
||
description: "Исправить прокрутку и видимость кнопок в модальных окнах",
|
||
completed: true,
|
||
priority: "high",
|
||
createdAt: "2024-01-15",
|
||
},
|
||
{
|
||
id: 2,
|
||
title: "Добавить экспорт в PDF",
|
||
description: "Реализовать функцию экспорта отчетов в PDF формат",
|
||
completed: false,
|
||
priority: "medium",
|
||
createdAt: "2024-01-16",
|
||
},
|
||
{
|
||
id: 3,
|
||
title: "Улучшить графики",
|
||
description: "Добавить больше интерактивности в графики Dashboard",
|
||
completed: false,
|
||
priority: "low",
|
||
createdAt: "2024-01-17",
|
||
},
|
||
],
|
||
};
|
||
|
||
// Генерация тестовых данных для отчетов
|
||
function generateTestData() {
|
||
const reports = [];
|
||
const today = new Date();
|
||
|
||
for (let i = 0; i < 30; i++) {
|
||
const date = new Date(today);
|
||
date.setDate(date.getDate() - i);
|
||
|
||
const storeId = Math.floor(Math.random() * 4) + 1;
|
||
const userId = Math.floor(Math.random() * 3) + 2; // employee пользователи
|
||
|
||
const income = Math.floor(Math.random() * 2000) + 500;
|
||
const cajaInicial = Math.floor(Math.random() * 300) + 100;
|
||
const totalIncome = income + cajaInicial;
|
||
|
||
const wages = Math.floor(Math.random() * 400) + 100;
|
||
const expenses = Math.floor(Math.random() * 200) + 50;
|
||
const totalExpenses = wages + expenses;
|
||
|
||
const envelope = Math.floor(Math.random() * 200) + 100;
|
||
const cajaFinal = totalIncome - totalExpenses - envelope;
|
||
|
||
reports.push({
|
||
id: i + 1,
|
||
date: date.toISOString().split("T")[0],
|
||
storeId: storeId,
|
||
userId: userId,
|
||
income: income,
|
||
cajaInicial: cajaInicial,
|
||
totalIncome: totalIncome,
|
||
wages: [
|
||
{
|
||
name: "Сотрудник " + (Math.floor(Math.random() * 3) + 1),
|
||
amount: wages,
|
||
},
|
||
],
|
||
expenses: [
|
||
{
|
||
name: "Расходы " + (Math.floor(Math.random() * 3) + 1),
|
||
amount: expenses,
|
||
},
|
||
],
|
||
totalWages: wages,
|
||
totalExpensesInternal: expenses,
|
||
totalExpenses: totalExpenses,
|
||
envelope: envelope,
|
||
cajaFinal: cajaFinal,
|
||
verified: Math.random() > 0.3,
|
||
createdAt: date.toISOString(),
|
||
});
|
||
}
|
||
|
||
database.reports = reports;
|
||
}
|
||
|
||
// Инициализация тестовых данных
|
||
generateTestData();
|
||
|
||
// ####################################
|