diff --git a/ACCESS_RIGHTS.md b/ACCESS_RIGHTS.md deleted file mode 100644 index dd994ff..0000000 --- a/ACCESS_RIGHTS.md +++ /dev/null @@ -1,217 +0,0 @@ -# 🔐 ПРАВА ДОСТУПА - МАТРИЦЫ РАЗРЕШЕНИЙ - -**Версия**: v4.1.17 -**Дата**: 2026-01-14 -**Обновление**: Уточнены права доступа User и Guest - ---- - -## 🔐 МАТРИЦА ПРАВ ДОСТУПА - РЕДАКТИРОВАНИЕ - -| Поле | Admin | User | Guest | -|------|-------|------|-------| -| **MAT-1** календарь | ✅ | ❌ | ❌ | -| **MAT-1** подтверждение | ✅ | ✅ | ❌ | -| **MAT-2** календарь | ✅ | ❌ | ❌ | -| **MAT-2** подтверждение | ✅ | ✅ | ❌ | -| **PAKETT** календарь | ✅ | ❌ | ❌ | -| **Töölehti** toggle | ✅ | ✅ | ❌ | -| **LÕIKUS** toggle | ✅ | ✅ | ❌ | -| **KLAAS** toggle | ✅ | ✅ | ❌ | -| **VALMIS** toggle | ✅* | ✅* | ❌ | -| **VÄLJAS** toggle | ✅* | ✅* | ❌ | - -*Блокируется при наличии проблем/ошибок - ---- - -## 👁️ МАТРИЦА ПРАВ ДОСТУПА - ПРОСМОТР - -| Поле | Admin | User | Guest | -|------|-------|------|-------| -| **MAT-1** дата | ✅ | ✅ | ✅ | -| **MAT-1** кнопка ✓ | ✅ | ✅ | ❌ | -| **MAT-2** дата | ✅ | ✅ | ✅ | -| **MAT-2** кнопка ✓ | ✅ | ✅ | ❌ | -| **PAKETT** дата | ✅ | ✅ | ✅ | -| **Töölehti** дата+статус | ✅ | ✅ | ✅ | -| **LÕIKUS** дата | ✅ | ✅ | ✅ | -| **KLAAS** дата | ✅ | ✅ | ✅ | -| **VALMIS** дата | ✅ | ✅ | ✅ | -| **VÄLJAS** дата | ✅ | ✅ | ✅ | - ---- - -## 👤 РОЛИ ПОЛЬЗОВАТЕЛЕЙ - -### **Admin** (admin, aknaproff) - -#### Редактирование: -- ✅ **MAT-1**: выбор даты через календарь -- ✅ **MAT-1**: подтверждение (кнопка ✓) -- ✅ **MAT-2**: выбор даты через календарь -- ✅ **MAT-2**: подтверждение (кнопка ✓) -- ✅ **PAKETT**: выбор даты через календарь -- ✅ **Töölehti**: полный контроль 3-шагового цикла -- ✅ **LÕIKUS**: toggle даты -- ✅ **KLAAS**: toggle даты -- ✅ **VALMIS**: toggle даты (если нет блокировки) -- ✅ **VÄLJAS**: toggle даты (если нет блокировки) -- ✅ Добавление записей -- ✅ Редактирование записей -- ✅ Удаление записей -- ✅ Изменение проблем и ошибок - -#### Просмотр: -- ✅ Все поля видны полностью - ---- - -### **User** (kasutaja) - -#### Редактирование: -- ❌ **MAT-1**: НЕТ доступа к календарю -- ✅ **MAT-1**: ЕСТЬ доступ к подтверждению (кнопка ✓) -- ❌ **MAT-2**: НЕТ доступа к календарю -- ✅ **MAT-2**: ЕСТЬ доступ к подтверждению (кнопка ✓) -- ❌ **PAKETT**: нет доступа к календарю -- ✅ **Töölehti**: полный контроль 3-шагового цикла -- ✅ **LÕIKUS**: toggle даты -- ✅ **KLAAS**: toggle даты -- ✅ **VALMIS**: toggle даты (если нет блокировки) -- ✅ **VÄLJAS**: toggle даты (если нет блокировки) -- ❌ Добавление/редактирование/удаление записей - -#### Просмотр: -- ✅ **MAT-1, MAT-2**: видит дату + кнопку подтверждения -- ✅ **PAKETT**: видит дату -- ✅ **Töölehti, LÕIKUS, KLAAS, VALMIS, VÄLJAS**: видит дату - ---- - -### **Guest** (неавторизованный пользователь) - -#### Редактирование: -- ❌ **Все поля**: нет доступа к редактированию - -#### Просмотр: -- ✅ **MAT-1, MAT-2**: видит только дату (БЕЗ кнопки подтверждения) -- ✅ **PAKETT**: видит дату -- ✅ **Töölehti, LÕIKUS, KLAAS, VALMIS, VÄLJAS**: видит дату - ---- - -## 📊 ДЕТАЛЬНОЕ ОПИСАНИЕ - -### **MAT-1 (Material)** -- **Admin**: открывает календарь + подтверждает -- **User**: только подтверждает (кнопка ✓) -- **Guest**: только просмотр даты - -### **MAT-2 (Material2)** -- **Admin**: открывает календарь + подтверждает -- **User**: только подтверждает (кнопка ✓) -- **Guest**: только просмотр даты -- **Блокировка**: если MAT-1 пустая - -### **PAKETT (Package)** -- **Admin**: открывает календарь -- **User**: только просмотр -- **Guest**: только просмотр - -### **Töölehti (Worksheets)** -- **Admin**: полный контроль 3-цикла -- **User**: полный контроль 3-цикла -- **Guest**: только просмотр - -### **LÕIKUS (Cutting)** -- **Admin**: toggle (добавить/удалить дату) -- **User**: toggle (добавить/удалить дату) -- **Guest**: только просмотр - -### **KLAAS (Glazing)** -- **Admin**: toggle (добавить/удалить дату) -- **User**: toggle (добавить/удалить дату) -- **Guest**: только просмотр - -### **VALMIS (Ready)** -- **Admin**: toggle (если нет проблем/ошибок) -- **User**: toggle (если нет проблем/ошибок) -- **Guest**: только просмотр -- **Блокировка**: при наличии проблем или ошибок - -### **VÄLJAS (Issued)** -- **Admin**: toggle (если нет проблем/ошибок) -- **User**: toggle (если нет проблем/ошибок) -- **Guest**: только просмотр -- **Блокировка**: при наличии проблем или ошибок - ---- - -## 🔑 ТЕХНИЧЕСКИЕ ДЕТАЛИ - -### **Backend проверка прав:** -```typescript -// optionalAuthMiddleware - разрешает Guest просмотр -// authMiddleware - требует авторизацию - -// Календари (MAT-1, MAT-2, PAKETT) -if (userRole !== 'admin') { - return c.json({ error: 'Only admin can change dates' }, 403) -} - -// Подтверждение (MAT-1, MAT-2) -// Разрешено всем авторизованным (Admin + User) - -// Toggle (Töölehti, LÕIKUS, KLAAS, VALMIS, VÄLJAS) -// Разрешено всем авторизованным (Admin + User) -``` - -### **Frontend проверка прав:** -```javascript -// canEditRecords() - проверяет наличие токена и роль -// Для Admin + User = true -// Для Guest = false - -// MAT-1/MAT-2 календарь -if (currentUser.role !== 'admin') { - // Скрыть календарь -} - -// MAT-1/MAT-2 подтверждение -if (!token) { - // Скрыть кнопку ✓ -} - -// Toggle поля -if (!canEditRecords()) { - // Отключить клик -} -``` - ---- - -## ✅ СВОДКА - -### **Что может Admin:** -- 📅 Выбирать даты через календарь (MAT-1, MAT-2, PAKETT) -- ✓ Подтверждать материалы -- 🔄 Управлять всеми toggle-полями -- ➕ Добавлять/редактировать/удалять записи - -### **Что может User:** -- ❌ НЕ может выбирать даты через календарь -- ✓ МОЖЕТ подтверждать материалы (MAT-1, MAT-2) -- 🔄 МОЖЕТ управлять toggle-полями (Töölehti, LÕIKUS, KLAAS, VALMIS, VÄLJAS) -- ❌ НЕ может добавлять/редактировать/удалять записи - -### **Что может Guest:** -- 👁️ Только просмотр всех дат -- ❌ НЕ видит кнопки подтверждения -- ❌ НЕ может редактировать ничего - ---- - -**Статус**: ✅ Права доступа корректны -**Версия**: v4.1.17 -**Дата**: 2026-01-14 diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 2e6eb63..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,232 +0,0 @@ -# AKNAPROFF Tootmine - CHANGELOG - -## v4.1.10 - 2025-12-31 (HOTFIX - КРИТИЧЕСКИЕ ИСПРАВЛЕНИЯ) - -### 🔥 Критические исправления - -#### 1. Исправлена потеря данных при рестарте Docker -- **Проблема:** Миграции пересоздавали БД при каждом запуске -- **Симптомы:** - - Существующая БД с 38 записями терялась - - При обновлении страницы старые данные появлялись на секунду -- **Решение:** - - Добавлен флаг `SKIP_MIGRATIONS=true` в `docker-entrypoint.sh` - - Добавлена переменная `SKIP_MIGRATIONS: "true"` в `docker-compose.yml` - - Миграции теперь пропускаются по умолчанию - - Используется готовая БД из `data/` директории -- **Файлы:** `docker-entrypoint.sh`, `docker-compose.yml` - -#### 2. Исправлена ошибка 500 при добавлении записи -- **Проблема:** `POST /api/records` возвращал 500 Internal Server Error -- **Симптомы:** - ``` - POST http://komo.aknaproff.ee:8180/api/records - [HTTP/1.1 500 Internal Server Error] - Save record error: "Failed to create record" - ``` -- **Причина:** Таблица `production_records` требует поля `created_by` и `updated_by`, но они не передавались -- **Решение:** - - Добавлены поля `created_by` и `updated_by` в INSERT запрос - - Добавлено поле `updated_by` в UPDATE запрос - - Значения берутся из `userId` (из JWT токена) -- **Файлы:** `src/index.tsx`, `dist/_worker.js` - -#### 3. Исправлена неработающая кнопка "Lisa uus rida" -- **Проблема:** Admin не мог добавить новую запись -- **Причина:** Ошибка 500 в backend (см. пункт 2) -- **Решение:** Исправлен backend код -- **Статус:** ✅ Теперь работает - -### 📝 Изменённые файлы - -#### Backend -- **src/index.tsx** - - `POST /api/records`: добавлены поля `created_by, updated_by` в INSERT - - `PUT /api/records/:id`: добавлено поле `updated_by` в UPDATE - -- **dist/_worker.js** - - Пересобран с исправлениями - -#### Docker -- **docker-entrypoint.sh** - - Добавлен флаг `SKIP_MIGRATIONS` (по умолчанию `true`) - - Добавлена логика проверки `SKIP_MIGRATIONS` - - При `SKIP_MIGRATIONS=true` миграции пропускаются - -- **docker-compose.yml** - - Добавлена переменная `SKIP_MIGRATIONS: "true"` - -#### Frontend -- **public/original.html** - - Cache version обновлена: `app.js?v=4.1.10` - -### ⚠️ Breaking Changes -Нет breaking changes. Полная обратная совместимость с v4.1.9. - -### 🔄 Миграция с v4.1.9 -1. Остановить контейнер: `docker-compose down` -2. **КРИТИЧНО:** Сделать бэкап: `cp -r data data.backup.$(date +%Y%m%d_%H%M%S)` -3. Заменить файлы (НЕ трогать `data/`) -4. Проверить `SKIP_MIGRATIONS: "true"` в `docker-compose.yml` -5. Запустить: `docker-compose up -d --build` -6. Проверить данные: 38 записей должны остаться - -### ✅ Тесты -- [x] Данные сохраняются между рестартами -- [x] Миграции пропускаются (логи: "Skipping migrations") -- [x] POST /api/records работает (200 OK) -- [x] Кнопка "Lisa uus rida" работает -- [x] created_by/updated_by записываются в БД -- [x] Admin может добавлять записи -- [x] User НЕ может добавлять записи -- [x] User НЕ может редактировать заметки -- [x] User может редактировать проблемы - -### 📦 Архивы -- `aknaproff_production_v4.1.10_arm.tar.gz` (278 KB) -- `aknaproff_production_v4.1.10_arm.zip` (318 KB) - ---- - -## v4.1.9 - 2025-12-30 - -### 🔒 Исправлены права доступа - -#### 1. User теперь может только просматривать заметки (read-only) -- **Проблема:** User мог редактировать заметки -- **Решение:** - - Frontend: `openNotesModal()` использует `canEditRecords()` (только admin) - - Frontend: кнопка "Salvesta" скрыта для user - - Backend: `PATCH /api/records/:id/notes` → Admin only (403 для user) -- **Статус:** ✅ Исправлено - -#### 2. User может редактировать проблемы -- **Backend:** `PATCH /api/records/:id/problems` → Admin + User -- **Frontend:** UI показывает возможность редактирования -- **Статус:** ✅ Работает - -#### 3. Кнопка "Lisa uus rida" скрыта для User и Guest -- **Frontend:** Кнопка скрыта через `role-admin` CSS класс -- **Backend:** Проверка роли перед созданием записи -- **Статус:** ✅ Работает - -### 📊 Матрица прав доступа - -| Функция | Admin | User | Guest | -|----------------------|-------|------|-------| -| Просмотр записей | ✅ | ✅ | ✅ | -| Добавить запись | ✅ | ❌ | ❌ | -| Редактировать запись | ✅ | ❌ | ❌ | -| Удалить запись | ✅ | ❌ | ❌ | -| Просмотр заметок | ✅ | ✅ | ✅ | -| Редактировать заметки| ✅ | ❌ | ❌ | -| Просмотр проблем | ✅ | ✅ | ✅ | -| Редактировать проблемы| ✅ | ✅ | ❌ | - -### 📝 Изменённые файлы -- `public/static/app.js`: изменены `openNotesModal()`, `saveNotes()` -- `src/index.tsx`: добавлены проверки роли в API -- `public/original.html`: cache v4.1.9 - ---- - -## v4.1.8 - 2025-12-30 - -### 🐛 Исправления - -#### 1. Исправлены права пользователей -- **Проблема:** User не мог редактировать Problems -- **Решение:** Изменён `openProblemsModal()` на использование `canEditProblems()` - -#### 2. Скрыта кнопка "Lisa uus rida" для non-admin -- **Frontend:** Кнопка скрыта для User и Guest ролей -- **CSS:** Использован класс `.role-admin` - -#### 3. Добавлены колонки problems -- **База данных:** - - `problems` TEXT DEFAULT NULL - - `problems_date` DATE DEFAULT NULL -- **Миграции:** Применены в локальной БД - -#### 4. Исправлены названия полей audit_log -- **Было:** `field_name`, `action_type` -- **Стало:** `field`, `action` -- **Файлы:** `src/index.tsx` - -### 📦 Изменения БД -```sql -ALTER TABLE production_records ADD COLUMN problems TEXT DEFAULT NULL; -ALTER TABLE production_records ADD COLUMN problems_date DATE DEFAULT NULL; -``` - ---- - -## v4.1.7 - 2025-12-30 - -### 🔐 Исправлена аутентификация - -#### 1. Исправлена система входа (bcrypt → SHA-256) -- **Проблема:** Login failed - несовместимость хэшей -- **Было:** Бэкап использовал bcrypt ($2a$) -- **Стало:** Все пароли SHA-256 -- **Решение:** Обновлены все password_hash в БД - -#### 2. Удалён дубликат пользователя -- **Удалён:** user `tootmine` (дубликат kasutaja) -- **Причина:** Один человек, два аккаунта -- **Статус:** ✅ Удалён - -#### 3. Добавлена колонка deleted_at -- **Таблица:** `users` -- **Тип:** `DATETIME DEFAULT NULL` -- **Причина:** Код использовал `WHERE deleted_at IS NULL`, но колонки не было -- **Статус:** ✅ Добавлена - -### 📊 База данных -- **Записей:** 38 production records -- **Пользователей:** 3 active - - `admin` / `demo123` (admin) - - `aknaproff` / `demo123` (admin) - - `kasutaja` / `tootmine` (user) - -### 🔒 Учётные данные -Все пароли теперь SHA-256: -``` -admin: d3ad9315b7be5dd53b31a273b3b3aba5defe700808305aa16a3062b76658a791 -aknaproff: d3ad9315b7be5dd53b31a273b3b3aba5defe700808305aa16a3062b76658a791 -kasutaja: a1026b7bd143f7190248bc79901e9a357a408e208f2d8e4d38fccf184754f35f -``` - ---- - -## v4.1.6 и ранее - -См. файлы: -- `CHANGES_v4.1.6.md` -- `CHANGES_v4.1.5.md` -- `CHANGES_v4.1.4.md` -- `CHANGES_v4.1.3.md` -- `CHANGES_v4.1.2.md` -- `CHANGES_v4.1.0.md` - ---- - -## Легенда - -- 🔥 Критическое исправление -- 🐛 Исправление бага -- ✨ Новая функция -- 🔒 Безопасность -- 📝 Документация -- 🔧 Конфигурация -- 📊 База данных -- 🎨 UI/UX -- ⚡ Производительность -- 🔄 Рефакторинг - ---- - -**Текущая версия:** v4.1.10 -**Дата:** 2025-12-31 -**Статус:** Production Ready ✅ -**Архитектура:** ARM64/AMD64 (Synology Compatible) diff --git a/DATE_FIELDS_LOGIC.md b/DATE_FIELDS_LOGIC.md deleted file mode 100644 index 31ff002..0000000 --- a/DATE_FIELDS_LOGIC.md +++ /dev/null @@ -1,381 +0,0 @@ -# 📋 ЛОГИКА ДАТ И ЧЕКБОКСОВ - ПОЛНОЕ РУКОВОДСТВО - -**Версия**: v4.1.17 -**Дата**: 2026-01-14 -**Обновление**: Исправлены права доступа (см. ACCESS_RIGHTS.md) - ---- - -## ⚠️ **ВАЖНО: ПРАВА ДОСТУПА** - -**Детальная информация о правах доступа находится в файле:** -📄 **[ACCESS_RIGHTS.md](./ACCESS_RIGHTS.md)** - -**Краткая сводка:** -- **Admin**: полный доступ (календари + toggle) -- **User**: подтверждение MAT-1/MAT-2 + toggle всех полей -- **Guest**: только просмотр - ---- - -## 🔄 **TÖÖLEHTI (WORKSHEETS) - 3-ШАГОВЫЙ ЦИКЛ** - -### **Как работает:** - -#### **ШАГ 1: Пусто → Серый фон с датой** -- **Состояние**: Пустая ячейка `-` -- **Действие**: Клик по ячейке -- **Результат**: - - Появляется **текущая дата** - - Фон **СЕРЫЙ** (не подтверждено) - - `worksheets_date` = TODAY - - `worksheets_confirmed` = 0 - -#### **ШАГ 2: Серый → Зеленый фон (подтверждение)** -- **Состояние**: Дата с серым фоном -- **Действие**: Клик по ячейке -- **Результат**: - - Дата **остается** - - Фон становится **ЗЕЛЕНЫЙ** (подтверждено) - - `worksheets_date` = **сохраняется** - - `worksheets_confirmed` = 1 - -#### **ШАГ 3: Зеленый → Пусто (сброс)** -- **Состояние**: Дата с зеленым фоном -- **Действие**: Клик по ячейке -- **Результат**: - - Дата **удаляется** - - Ячейка становится **пустой** `-` - - `worksheets_date` = NULL - - `worksheets_confirmed` = 0 - -### **Визуальная схема:** -``` -┌─────────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────┐ -│ ПУСТО │ Клик 1 │ СЕРЫЙ ФОН │ Клик 2 │ ЗЕЛЕНЫЙ ФОН │ Клик 3 │ ПУСТО │ -│ - │ ──────> │ 14.01.2026 │ ──────> │ 14.01.2026 │ ──────> │ - │ -│ │ │ (не подтверждено)│ │ (подтверждено) │ │ │ -└─────────────┘ └──────────────────┘ └──────────────────┘ └─────────────┘ - ^ │ - │ │ - └─────────────────────────────────────────────────────────────────────────────────────┘ - Цикл повторяется -``` - ---- - -## 🟢 **MAT-1 (MATERIAL) - КАЛЕНДАРЬ + ПОДТВЕРЖДЕНИЕ** - -### **Функционал:** -- **Календарь**: Клик по ячейке → открывается календарь -- **Выбор даты**: Выбрать дату → сохраняется `material_date` -- **Кнопка подтверждения** ✓: Клик → зеленая рамка - - `material_confirmed` = 1 - - Ячейка с зеленой рамкой - -### **Кто может изменять:** -- ✅ Admin -- ✅ Public User (все) - -### **База данных:** -- `status_checkboxes.material_date` (TEXT) -- `status_checkboxes.material_confirmed` (INTEGER, 0 или 1) - ---- - -## 🔒 **MAT-2 (MATERIAL2) - ЗАВИСИТ ОТ MAT-1** - -### **Блокировка:** -- **Заблокировано**: Если MAT-1 пустая -- **Показывается**: 🔒 с подсказкой "MAT-1 peab olema täidetud" -- **Разблокировано**: Когда MAT-1 заполнена - -### **Функционал (когда разблокирована):** -- **Календарь**: Клик → открывается календарь -- **Выбор даты**: Выбрать дату → сохраняется `material2_date` -- **Кнопка подтверждения** ✓: Клик → зеленая рамка - - `material2_confirmed` = 1 - -### **Кто может изменять:** -- ✅ Admin -- ✅ Public User (если MAT-1 заполнена) - -### **База данных:** -- `status_checkboxes.material2_date` (TEXT) -- `status_checkboxes.material2_confirmed` (INTEGER, 0 или 1) - ---- - -## 📦 **PAKETT (PACKAGE) - КАЛЕНДАРЬ** - -### **Функционал:** -- **Календарь**: Клик → открывается календарь -- **Выбор даты**: Выбрать дату → сохраняется `package_date` -- **Нет подтверждения**: Только дата (без кнопки ✓) - -### **Кто может изменять:** -- ✅ Admin -- ✅ Public User (все) - -### **База данных:** -- `status_checkboxes.package_date` (TEXT) - ---- - -## 🔴 **LÕIKUS (CUTTING) - ТОЛЬКО ADMIN** - -### **Функционал:** -- **Toggle**: Клик → дата появляется/исчезает -- **Автоматическая дата**: Устанавливается на **сегодня** -- **Ошибка-флаг** 🚫: `cutting_error` - - Если установлена → красная метка - - Блокирует **VALMIS** и **VÄLJAS** - -### **Кто может изменять:** -- 🔴 **ТОЛЬКО Admin** - -### **База данных:** -- `status_checkboxes.cutting_date` (TEXT) -- `status_checkboxes.cutting_error` (INTEGER, 0 или 1) - ---- - -## 🔴 **KLAAS (GLAZING) - ТОЛЬКО ADMIN** - -### **Функционал:** -- **Toggle**: Клик → дата появляется/исчезает -- **Автоматическая дата**: Устанавливается на **сегодня** -- **Ошибка-флаг** 🚫: `glazing_error` - - Если установлена → красная метка - - Блокирует **VALMIS** и **VÄLJAS** - -### **Кто может изменять:** -- 🔴 **ТОЛЬКО Admin** - -### **База данных:** -- `status_checkboxes.glazing_date` (TEXT) -- `status_checkboxes.glazing_error` (INTEGER, 0 или 1) - ---- - -## 🔒 **VALMIS (READY) - Admin/User Toggle + БЛОКИРОВКА ТОЛЬКО ПО ERROR ФЛАГАМ** - -### **ВАЖНО: ТЕКСТ ПРОБЛЕМЫ НЕ БЛОКИРУЕТ!** -Текст в поле "Probleemid" (серая метка ⚠️) **НЕ** блокирует VALMIS! - -### **Блокировка:** -Поле **ЗАБЛОКИРОВАНО ТОЛЬКО**, если установлен **ЛЮБОЙ error флаг** (красная галочка ✗): - - `worksheets_error = 1` - - `cutting_error = 1` - - `glazing_error = 1` - - `ready_error = 1` - - `issued_error = 1` - -**НЕ блокируется**, если: -- Есть только текст в `production_records.problems` (серая метка ⚠️) -- Все error флаги = 0 - -### **Визуальное отображение:** -- **Заблокировано**: 🔒 + сообщение "Vigade märked on seatud (punased kolmnurgad)" -- **Разблокировано**: Toggle как обычно - -### **Функционал (когда разблокирована):** -- **Toggle**: Клик → дата появляется/исчезает -- **Автоматическая дата**: Устанавливается на **сегодня** - -### **Кто может изменять:** -- ✅ **Admin + User** (изменено в v4.1.18) - -### **База данных:** -- `status_checkboxes.ready_date` (TEXT) -- `status_checkboxes.ready_error` (INTEGER, 0 или 1) - ---- - -## 🔒 **VÄLJAS (ISSUED) - Admin/User Toggle + БЛОКИРОВКА ТОЛЬКО ПО ERROR ФЛАГАМ** - -### **ВАЖНО: ТЕКСТ ПРОБЛЕМЫ НЕ БЛОКИРУЕТ!** -Текст в поле "Probleemid" (серая метка ⚠️) **НЕ** блокирует VÄLJAS! - -### **Блокировка:** -**ТА ЖЕ ЛОГИКА** что и **VALMIS**: -- Блокируется **ТОЛЬКО** если установлен **ЛЮБОЙ error флаг** (красная галочка ✗) -- **НЕ** блокируется если есть только текст в `production_records.problems` - -### **Функционал (когда разблокирована):** -- **Toggle**: Клик → дата появляется/исчезает -- **Автоматическая дата**: Устанавливается на **сегодня** - -### **Кто может изменять:** -- ✅ **Admin + User** (изменено в v4.1.18) - -### **Кто может изменять:** -- 🔴 **ТОЛЬКО Admin** - -### **База данных:** -- `status_checkboxes.issued_date` (TEXT) -- `status_checkboxes.issued_error` (INTEGER, 0 или 1) - ---- - -## 📊 **СВОДНАЯ ТАБЛИЦА** - -| Поле | Тип | Доступ | Логика | Подтверждение | Блокировка | -|------|-----|--------|--------|---------------|-----------| -| **MAT-1** | Календарь | Все | Календарь | ✓ Да | Нет | -| **MAT-2** | Календарь | Все | Календарь | ✓ Да | Если MAT-1 пуста | -| **PAKETT** | Календарь | Все | Календарь | Нет | Нет | -| **Töölehti** | 3-цикл | Admin | Серый→Зеленый→Пусто | Да | Нет | -| **LÕIKUS** | Toggle | Admin | Появляется/Исчезает | Нет | Нет | -| **KLAAS** | Toggle | Admin | Появляется/Исчезает | Нет | Нет | -| **VALMIS** | Toggle | Admin/User | Появляется/Исчезает | Нет | ТОЛЬКО если error флаги (НЕ текст проблемы) | -| **VÄLJAS** | Toggle | Admin/User | Появляется/Исчезает | Нет | ТОЛЬКО если error флаги (НЕ текст проблемы) | - ---- - -## 🔐 **ПРАВА ДОСТУПА** - -### **Admin** (admin, aknaproff): -- ✅ Все поля (MAT-1, MAT-2, PAKETT, Töölehti, LÕIKUS, KLAAS, VALMIS, VÄLJAS) -- ✅ Добавление записей -- ✅ Редактирование записей -- ✅ Удаление записей -- ✅ Изменение проблем и ошибок - -### **Public User** (kasutaja): -- ✅ MAT-1 (календарь + подтверждение) -- ✅ MAT-2 (календарь + подтверждение, если MAT-1 заполнена) -- ✅ PAKETT (календарь) -- ❌ Töölehti, LÕIKUS, KLAAS, VALMIS, VÄLJAS (только просмотр) -- ❌ Добавление/редактирование/удаление записей - ---- - -## 🎨 **ВИЗУАЛЬНЫЕ ИНДИКАТОРЫ** - -### **Цвета:** -- 🟢 **Зеленая рамка**: MAT-1/MAT-2 подтверждены -- 🟢 **Зеленый фон**: Töölehti подтверждено (confirmed=1) -- ⬜ **Серый фон**: Töölehti не подтверждено (confirmed=0) -- 🔴 **Красная метка**: Ошибка-флаг установлен -- 🔒 **Замок**: Поле заблокировано - -### **Подсказки:** -- **MAT-2 заблокирована**: "MAT-1 peab olema täidetud" -- **VALMIS/VÄLJAS заблокированы**: Текст проблемы из `production_records.problems` -- **Töölehti Шаг 1**: "Klõps 1: Lisa kuupäev" -- **Töölehti Шаг 2**: "Klõps 2: Kinnita" -- **Töölehti Шаг 3**: "Klõps 3: Tühjenda" - ---- - -## 📝 **AUDIT LOG** - -Каждое изменение даты записывается в таблицу `audit_log`: -```sql -INSERT INTO audit_log (user_id, record_id, field, old_value, new_value, action) -VALUES (?, ?, ?, ?, ?, 'toggle_status') -``` - -**Поля:** -- `user_id`: ID пользователя (1=admin, 2=aknaproff, 4=kasutaja) -- `record_id`: ID записи -- `field`: Название поля (worksheets, cutting, glazing, ready, issued) -- `old_value`: Старое значение даты -- `new_value`: Новое значение даты -- `action`: Тип действия ('toggle_status', 'worksheets_cycle') - ---- - -## ✅ **ПРОВЕРКА** - -### **Test Töölehti 3-цикл:** -```bash -# Шаг 1: Пусто → Серый -PATCH /api/records/2/worksheets-cycle -→ {"date": "2026-01-14", "confirmed": 0} - -# Шаг 2: Серый → Зеленый -PATCH /api/records/2/worksheets-cycle -→ {"date": "2026-01-14", "confirmed": 1} - -# Шаг 3: Зеленый → Пусто -PATCH /api/records/2/worksheets-cycle -→ {"date": null, "confirmed": 0} -``` - -### **Test блокировка MAT-2:** -```bash -# MAT-1 пустая → MAT-2 заблокирована -SELECT material_date FROM status_checkboxes WHERE record_id = 5 -→ NULL -# UI показывает: 🔒 "MAT-1 peab olema täidetud" -``` - -### **Test блокировка VALMIS:** -```bash -# Есть проблемы → VALMIS заблокирована -SELECT problems FROM production_records WHERE id = 10 -→ "Aken vajab parandust" -# UI показывает: 🔒 + текст проблемы -``` - ---- - -**Версия**: v4.1.16 -**Статус**: ✅ Работает корректно -**Дата**: 2026-01-14 - ---- - -## 🔒 **ВАЖНО: ПРАВИЛО БЛОКИРОВКИ VALMIS/VÄLJAS** - -### **Версия**: v4.1.19 -### **Дата изменения**: 2026-01-14 - -**КРИТИЧЕСКИ ВАЖНО**: Блокировка VALMIS/VÄLJAS работает **ТОЛЬКО** по error флагам! - -### **Блокируется**, если установлен **ЛЮБОЙ** из error флагов (красная галочка ✗): -- `worksheets_error = 1` -- `cutting_error = 1` -- `glazing_error = 1` -- `ready_error = 1` -- `issued_error = 1` - -### **НЕ БЛОКИРУЕТСЯ**, если: -- Есть **ТОЛЬКО** текст в `production_records.problems` (серая метка ⚠️) -- Все error флаги = 0 - -### **Визуальные индикаторы:** -- **VALMIS/VÄLJAS заблокированы**: 🔒 + сообщение "Vigade märked on seatud (punased kolmnurgad)" -- **Серая метка с ⚠️**: Указывает на текст проблемы в поле "Probleemid", **НЕ блокирует** VALMIS/VÄLJAS - -### **Код проверки блокировки** (backend): -```typescript -if (field === 'ready' || field === 'issued') { - const statusCheckbox = await c.env.DB.prepare( - 'SELECT worksheets_error, cutting_error, glazing_error, ready_error, issued_error FROM status_checkboxes WHERE record_id = ?' - ).bind(recordId).first() - - const hasErrorFlags = statusCheckbox && ( - statusCheckbox.worksheets_error || - statusCheckbox.cutting_error || - statusCheckbox.glazing_error || - statusCheckbox.ready_error || - statusCheckbox.issued_error - ) - - if (hasErrorFlags) { - return c.json({ - error: 'blocked', - message: 'Vigade märked on seatud (punased kolmnurgad)' - }, 403) - } -} -``` - -### **Тестирование** (результаты от 2026-01-14): -✅ **Test 1**: Только текст problems → НЕ блокируется → `{"success":true}` -✅ **Test 2**: Установлен error flag → БЛОКИРУЕТСЯ → `{"error":"blocked","message":"..."}` - ---- diff --git a/Dockerfile b/Dockerfile old mode 100755 new mode 100644 index 96d7451..a03395a --- a/Dockerfile +++ b/Dockerfile @@ -1,40 +1,58 @@ -# syntax=docker/dockerfile:1 +# Multi-stage build для оптимизации размера образа + +# Stage 1: Build +FROM node:20-alpine AS builder -# ---------- Build stage ---------- -FROM node:20-bookworm-slim AS builder WORKDIR /app -COPY package.json package-lock.json ./ -RUN npm install +# Копировать package files +COPY package*.json ./ +# Установить зависимости +RUN npm ci --only=production + +# Копировать исходники COPY . . + +# Собрать проект RUN npm run build -# ---------- Runtime stage ---------- -FROM node:20-bookworm-slim +# Stage 2: Runtime +FROM node:20-alpine + WORKDIR /app -ENV NODE_ENV=production \ - WRANGLER_SEND_METRICS=false \ - PORT=3000 \ - D1_BINDING=aknaproff-db \ - PERSIST_PATH=/data +# Установить dumb-init для правильной обработки сигналов +RUN apk add --no-cache dumb-init -# Copy everything from builder (includes node_modules, dist, migrations, etc.) -COPY --from=builder /app /app +# Создать пользователя без root +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nodejs -u 1001 -# Create data directory -RUN mkdir -p /data +# Копировать зависимости из builder +COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules +COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist +COPY --from=builder --chown=nodejs:nodejs /app/package.json ./ +COPY --from=builder --chown=nodejs:nodejs /app/wrangler.jsonc ./ +COPY --from=builder --chown=nodejs:nodejs /app/migrations ./migrations +COPY --from=builder --chown=nodejs:nodejs /app/seed.sql ./ +# Создать директорию для локальной БД +RUN mkdir -p .wrangler/state/v3/d1 && \ + chown -R nodejs:nodejs .wrangler + +# Переключиться на непривилегированного пользователя +USER nodejs + +# Открыть порт EXPOSE 3000 -# Persist D1 SQLite data between restarts -VOLUME ["/data"] +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD wget --quiet --tries=1 --spider http://localhost:3000 || exit 1 -# Start wrangler directly (no bash script needed) -CMD ["npx", "wrangler", "pages", "dev", "dist", \ - "--local", \ - "--d1=aknaproff-db", \ - "--persist-to=/data", \ - "--ip=0.0.0.0", \ - "--port=3000"] +# Запуск с dumb-init +ENTRYPOINT ["dumb-init", "--"] + +# Команда запуска +CMD ["sh", "-c", "npm run db:reset && npx wrangler pages dev dist --d1=webapp-production --local --ip 0.0.0.0 --port 3000"] diff --git a/FILES_TO_COPY.txt b/FILES_TO_COPY.txt new file mode 100644 index 0000000..fe0d8dc --- /dev/null +++ b/FILES_TO_COPY.txt @@ -0,0 +1,147 @@ +# 📦 Файлы для копирования на production сервер + +## Версия: v4.1.6 (Märkused Visual Indicators) +## Дата: 2025-11-28 + +--- + +## 🆕 Что нового в v4.1.6 + +### Визуальная индикация в поле "Märkused" (Notes) +- **Желтый фон с ℹ️**: Когда есть текст заметки +- **Иконка info-circle**: Информационная иконка "i" +- **Tooltip**: При наведении курсора показывается полный текст заметки +- **Пустое поле**: Прочерк `-` когда нет заметок + +### Предыдущие изменения (v4.1.5) +- Восстановление визуальной индикации "Probleemid" (красный с ⚠️) + +### Предыдущие изменения (v4.1.4) +- Упрощение формы логина: "Administrator Login" → "Login" + +### ⚠️ Для v4.1.3 требовалось обновление БД! + +Если обновляетесь с версии до v4.1.3, нужно применить seed.sql: +- Добавлен новый пользователь **kasutaja** (password: tootmine) +- Убрано слово "Sorteerimine" над кнопкой ID +- Уточнена система ролей + +--- + +## 🔥 Deployment + +### Вариант A: Обновление только v4.1.4 (БД уже обновлена) + +Если вы уже применили seed.sql в v4.1.3, просто обновите код: + +```bash +# Быстрый вариант +scp dist/_worker.js user@server:/path/to/webapp/dist/ +docker-compose restart + +# ИЛИ полный вариант +scp public/original.html user@server:/path/to/webapp/public/ +scp src/original-html.ts user@server:/path/to/webapp/src/ +# На сервере: npm run build && docker-compose restart +``` + +### Вариант B: Полное обновление (с БД из v4.1.3) + +Если обновляетесь впервые или нужно добавить пользователя kasutaja: + +```bash +# 1. Обновить базу данных +scp seed.sql user@server:/path/to/webapp/ +docker-compose exec aknaproff-backend sh -c " + cd /app && + npx wrangler d1 execute webapp-production --local --file=./seed.sql +" + +# Проверить +docker-compose exec aknaproff-backend sh -c " + npx wrangler d1 execute webapp-production --local \ + --command='SELECT username, full_name, role FROM users' +" + +# 2. Обновить код +scp dist/_worker.js user@server:/path/to/webapp/dist/ +docker-compose restart +``` + +### Проверка после deployment +```bash +curl -I http://localhost:8180 +# Браузер: Ctrl+Shift+R, проверить текст "Login" и "Sisesta kasutajaandmed" +``` + +--- + +## 📝 Список файлов для копирования + +### Для v4.1.6: +- `dist/_worker.js` (быстрый вариант) **← рекомендуется** +- ИЛИ `public/static/app.js` + `public/original.html` + `src/original-html.ts` (полный вариант) + +### Если также нужен пользователь kasutaja из v4.1.3: +- `seed.sql` - добавляет kasutaja (tootmine) + +--- + +## ✅ Проверка после deployment + +1. **HTTP**: `curl -I http://localhost:8180` → 200 OK + +2. **Браузер**: + - Открыть http://localhost:8180 + - Нажать **Ctrl+Shift+R** (сброс кэша) + - Войти под любым пользователем (kasutaja/tootmine, aknaproff/demo123, admin/demo123) + - **Проверить поле "Probleemid"**: + - Запись с галочками → 🔴 красный фон с ⚠️ + - Запись с текстом → ⚪ серый фон с ℹ️ + - Запись без проблем → серый фон с `-` + - Навести курсор → показывает tooltip с текстом проблемы + - **Проверить поле "Märkused"**: + - Запись с заметкой (ID 2, 4) → 🟡 желтый фон с ℹ️ + - Запись без заметки → серый фон с `-` + - Навести курсор → показывает tooltip с текстом заметки + +--- + +## 🔐 Учётные данные + +| Username | Password | Role | Доступ | +|----------|----------|------|--------| +| kasutaja | tootmine | user | Просмотр + проблемы | +| aknaproff | demo123 | admin | Полный доступ | +| admin | demo123 | admin | Полный доступ | +| guest | (без входа) | guest | Только просмотр | + +--- + +## 📊 История версий + +| Версия | Изменения | +|--------|-----------| +| v4.1.6 | Визуальная индикация Märkused (желтый с ℹ️, tooltip) | +| v4.1.5 | Восстановление визуальной индикации Probleemid (красный с ⚠️, tooltip) | +| v4.1.4 | Упрощение текста формы логина | +| v4.1.3 | Убрано "Sorteerimine", добавлен kasutaja, уточнены роли | +| v4.1.2 | Кнопка Sorteerimine в Kiir otsing | +| v4.1.1 | HOTFIX: continueAsGuest global access | +| v4.1.0 | Auth система (guest/user/admin), сортировка по ID | + +--- + +## 🔗 Документация + +- **CHANGES_v4.1.6.md** - детали v4.1.6 (Märkused визуализация) +- **CHANGES_v4.1.5.md** - детали v4.1.5 (восстановление Probleemid визуализации) +- **CHANGES_v4.1.4.md** - детали v4.1.4 (упрощение формы логина) +- **CHANGES_v4.1.3.md** - детали v4.1.3 (UI + новый пользователь) +- **CHANGES_v4.1.2.md** - детали v4.1.2 (перемещение кнопки) +- **CHANGES_v4.1.0.md** - детали v4.1.0 (auth система) +- **HOTFIX_v4.1.1.md** - hotfix continueAsGuest + +--- + +**💡 Заметка**: v4.1.6 - добавление визуальной индикации в поле Märkused (желтый с ℹ️). БД не затронута. diff --git a/HOTFIX_v4.1.11.md b/HOTFIX_v4.1.11.md deleted file mode 100644 index c8b8cde..0000000 --- a/HOTFIX_v4.1.11.md +++ /dev/null @@ -1,181 +0,0 @@ -# AKNAPROFF v4.1.11 - HOTFIX - -**Дата:** 2025-12-31 -**Тип:** HOTFIX - Критическое исправление -**Статус:** Production Ready ✅ - ---- - -## 🐛 Исправленная проблема - -### ❌ Ошибка: "NOT NULL constraint failed: production_records.price" - -**Симптомы:** -``` -POST /api/records -[HTTP/2 500 Internal Server Error] -Save record error: Failed to create record -``` - -**Консоль браузера:** -```javascript -Viga salvestamisel: Failed to create record -Save record error: Request failed with status code 500 -``` - -**Логи сервера:** -``` -Error creating record: D1_ERROR: NOT NULL constraint failed: production_records.price: SQLITE_CONSTRAINT -``` - -**Причина:** -В реальной БД колонка `price` имеет ограничение `NOT NULL`, но код передавал `null` вместо `0`. - -**Схема БД:** -```sql -CREATE TABLE production_records ( - ... - price REAL NOT NULL, -- ⚠️ NOT NULL! - ... -); -``` - -**Старый код (v4.1.10):** -```typescript -const price = data.price ? parseFloat(data.price) : null // ❌ null вызывает ошибку -``` - -**Новый код (v4.1.11):** -```typescript -const price = data.price ? parseFloat(data.price) : 0 // ✅ 0 соответствует схеме -``` - ---- - -## ✅ Решение - -### Изменённые файлы: - -**src/index.tsx:** -- POST /api/records: изменено `null` → `0` для price -- PUT /api/records/:id: изменено `null` → `0` для price - -**public/original.html:** -- Cache version: v4.1.11 - -**dist/_worker.js:** -- Пересобран с исправлениями - ---- - -## 🧪 Тестирование - -### Тест 1: Добавление записи без цены -```bash -curl -X POST http://localhost:3000/api/records \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "month":"1", - "year":"2025", - "client_name":"Test Client", - "offer_number":"TEST-001", - "work_number":"WRK-001", - "quantity":"5" - }' -``` - -**Результат v4.1.10:** ❌ 500 Internal Server Error -**Результат v4.1.11:** ✅ `{"success":true,"id":49}` - -### Тест 2: Проверка сохранённой записи -```bash -SELECT id, client_name, price FROM production_records WHERE id = 49 -``` - -**Результат:** -``` -id: 49 -client_name: "Test Client" -price: 0 ✅ -``` - -### Тест 3: Добавление записи с ценой -```bash -curl -X POST http://localhost:3000/api/records \ - -d '{"month":"1","year":"2025","client_name":"Test","offer_number":"123","work_number":"456","quantity":"1","price":"100.50"}' -``` - -**Результат:** ✅ `{"success":true,"id":50}`, price = 100.5 - ---- - -## 🔄 Миграция с v4.1.10 - -### Обновление не требует изменений БД - -Просто замените файлы: -```bash -# 1. Остановить -docker-compose down - -# 2. Заменить код -cp /path/to/new/dist/_worker.js dist/ -cp /path/to/new/src/index.tsx src/ -cp /path/to/new/public/original.html public/ - -# 3. Запустить -docker-compose up -d --build -``` - -**⚠️ НЕ трогайте data/ директорию!** - ---- - -## 📊 Сравнение версий - -| Параметр | v4.1.10 | v4.1.11 | -|----------|---------|---------| -| price default | null ❌ | 0 ✅ | -| Добавление без цены | 500 ошибка ❌ | Работает ✅ | -| Добавление с ценой | Работает ✅ | Работает ✅ | -| NOT NULL constraint | Нарушается ❌ | Соблюдается ✅ | - ---- - -## ✅ Все проверки пройдены - -- [x] Добавление записи без цены → ✅ price = 0 -- [x] Добавление записи с ценой → ✅ price сохраняется -- [x] Редактирование записи → ✅ price обновляется -- [x] NOT NULL constraint → ✅ соблюдается -- [x] Кнопка "Lisa uus rida" → ✅ работает -- [x] Права доступа → ✅ admin может добавлять -- [x] Cache version → ✅ v4.1.11 - ---- - -## 🎯 Статус - -**Версия:** v4.1.11 -**Тип:** HOTFIX -**Критичность:** Высокая (блокировала добавление записей) -**Совместимость:** Полная обратная совместимость -**Требуется миграция БД:** Нет -**Статус:** Production Ready ✅ - ---- - -## 📝 Changelog Summary - -``` -v4.1.11 (2025-12-31) - HOTFIX -- Fixed: NOT NULL constraint failed for price column -- Changed: price default value from null to 0 -- Files: src/index.tsx, dist/_worker.js, public/original.html -- Status: Production Ready -``` - ---- - -**ПРОБЛЕМА ИСПРАВЛЕНА! Добавление записей теперь работает корректно.** ✅ diff --git a/HOTFIX_v4.1.12.md b/HOTFIX_v4.1.12.md deleted file mode 100644 index 32be0a5..0000000 --- a/HOTFIX_v4.1.12.md +++ /dev/null @@ -1,211 +0,0 @@ -# AKNAPROFF v4.1.12 - HOTFIX: Редактирование записей - -**Дата:** 2025-12-31 -**Тип:** HOTFIX - Критическое исправление -**Статус:** Production Ready ✅ - ---- - -## 🐛 Исправленная проблема - -### ❌ Редактирование записей не сохранялось - -**Симптомы:** -- При редактировании записи (клик на строку) модальное окно открывается -- После изменения данных и сохранения - запись не обновляется -- Идёт GET запрос, но данные остаются прежними -- После перезагрузки страницы изменения не видны -- **В модальном окне изменение даты тоже не сохраняется** - -**Проблема:** -1. Backend НЕ обновлял таблицу `status_checkboxes` (где хранятся даты) -2. Frontend передавал `null` для price вместо `0` - ---- - -## ✅ Решение - -### 1. Backend: Добавлено обновление status_checkboxes - -**Было (v4.1.11):** -```typescript -app.put('/api/records/:id', async (c) => { - // Обновляется только production_records - await c.env.DB.prepare(` - UPDATE production_records - SET client_name = ?, ... - WHERE id = ? - `).run() - - return c.json({ success: true }) -}) -``` - -**Стало (v4.1.12):** -```typescript -app.put('/api/records/:id', async (c) => { - // Обновляется production_records - await c.env.DB.prepare(` - UPDATE production_records - SET client_name = ?, ... - WHERE id = ? - `).run() - - // ✅ Добавлено: Обновление status_checkboxes (даты) - if (data.material_date !== undefined || - data.material2_date !== undefined || - data.package_date !== undefined) { - await c.env.DB.prepare(` - UPDATE status_checkboxes - SET material_date = ?, - material2_date = ?, - package_date = ?, - updated_at = CURRENT_TIMESTAMP - WHERE record_id = ? - `).bind( - data.material_date || null, - data.material2_date || null, - data.package_date || null, - id - ).run() - } - - return c.json({ success: true }) -}) -``` - -### 2. Frontend: Исправлено значение по умолчанию для price - -**Было:** -```javascript -price: parseFloat(document.getElementById('price').value) || null // ❌ -``` - -**Стало:** -```javascript -price: parseFloat(document.getElementById('price').value) || 0 // ✅ -``` - ---- - -## 🧪 Тестирование - -### Тест 1: Обновление записи с датами -```bash -PUT /api/records/1 -{ - "client_name": "Updated Client", - "offer_number": "UPD-001", - "price": "250.50", - "material_date": "2025-01-15", - "material2_date": "2025-01-16", - "package_date": "2025-01-17" -} -``` - -**Результат v4.1.11:** ❌ Даты НЕ сохраняются -**Результат v4.1.12:** ✅ Даты сохраняются - -### Тест 2: Проверка сохранённых данных - -**production_records:** -```sql -SELECT client_name, offer_number, price FROM production_records WHERE id = 1 --- Результат: Updated Client, UPD-001, 250.5 ✅ -``` - -**status_checkboxes:** -```sql -SELECT material_date, material2_date, package_date -FROM status_checkboxes WHERE record_id = 1 --- Результат: 2025-01-15, 2025-01-16, 2025-01-17 ✅ -``` - -### Тест 3: UI - Редактирование через модальное окно -1. Открыть запись (клик на строку) -2. Изменить имя клиента -3. Изменить даты (material_date, material2_date, package_date) -4. Сохранить - -**Результат:** ✅ Все изменения сохраняются - ---- - -## 📝 Изменённые файлы - -| Файл | Изменения | -|------|-----------| -| **src/index.tsx** | Добавлен UPDATE для status_checkboxes | -| **public/static/app.js** | price: null → 0 | -| **public/original.html** | Cache v4.1.12 | -| **dist/_worker.js** | Пересобран | - ---- - -## 🔄 Миграция с v4.1.11 - -### Обновление не требует изменений БД - -```bash -# 1. Остановить -docker-compose down - -# 2. Заменить код -cp new/dist/_worker.js dist/ -cp new/src/index.tsx src/ -cp new/public/ public/ - -# 3. Запустить -docker-compose up -d --build -``` - ---- - -## 📊 Сравнение версий - -| Параметр | v4.1.11 | v4.1.12 | -|----------|---------|---------| -| Редактирование записей | Не работает ❌ | Работает ✅ | -| Сохранение дат | Не работает ❌ | Работает ✅ | -| status_checkboxes UPDATE | Нет ❌ | Есть ✅ | -| price default | null ❌ | 0 ✅ | - ---- - -## ✅ Все проверки пройдены - -- [x] Редактирование записей работает -- [x] Даты сохраняются (material_date, material2_date, package_date) -- [x] production_records обновляется -- [x] status_checkboxes обновляется -- [x] price = 0 по умолчанию -- [x] UI модальное окно работает корректно -- [x] После перезагрузки изменения видны - ---- - -## 🎯 Статус - -**Версия:** v4.1.12 -**Тип:** HOTFIX -**Критичность:** Высокая (блокировала редактирование) -**Совместимость:** Полная обратная совместимость -**Требуется миграция БД:** Нет -**Статус:** Production Ready ✅ - ---- - -## 📝 Changelog Summary - -``` -v4.1.12 (2025-12-31) - HOTFIX -- Fixed: Record editing not saving changes -- Added: status_checkboxes UPDATE in PUT endpoint -- Fixed: Date fields (material_date, material2_date, package_date) now save -- Changed: price default from null to 0 in frontend -- Status: Production Ready -``` - ---- - -**ПРОБЛЕМА ИСПРАВЛЕНА! Редактирование записей и дат теперь работает корректно.** ✅ diff --git a/HOTFIX_v4.1.15.md b/HOTFIX_v4.1.15.md deleted file mode 100644 index b34197e..0000000 --- a/HOTFIX_v4.1.15.md +++ /dev/null @@ -1,196 +0,0 @@ -# 🔧 HOTFIX v4.1.15 - DATE FIELDS DISPLAY FIX - -**Release Date**: 2026-01-14 -**Priority**: CRITICAL - Fixes invisible date fields -**Status**: ✅ Production Ready - ---- - -## 🐛 ПРОБЛЕМА - -**Симптомы:** -- Даты НЕ отображаются в полях: PAKETT, Töölehti, LÕIKUS, KLAAS, VALMIS, VÄLJAS -- Клик по ячейке не показывает дату -- API запросы проходят без ошибок -- Frontend получает `null` вместо дат - -**Причина:** -1. **Строковые "null"** вместо SQL NULL в БД -2. Backend записывал строку `"null"` вместо настоящего NULL -3. Toggle отправлял `date: null`, что интерпретировалось как "установить NULL" - ---- - -## ✅ ИСПРАВЛЕНИЯ - -### 1. Backend UPDATE для status_checkboxes (v4.1.12 → v4.1.13) -**Файл:** `src/index.tsx` - -```typescript -// ❌ БЫЛО: -data.material_date || null, -data.material2_date || null, -data.package_date || null, - -// ✅ СТАЛО: -const materialDate = (data.material_date && data.material_date !== 'null') ? data.material_date : null -const material2Date = (data.material2_date && data.material2_date !== 'null') ? data.material2_date : null -const packageDate = (data.package_date && data.package_date !== 'null') ? data.package_date : null -``` - -### 2. Backend PATCH /api/records/:id/status (v4.1.13 → v4.1.14) -**Файл:** `src/index.tsx` - -```typescript -// ❌ БЫЛО: -const newDate = date !== undefined ? date : (oldRecord?.[dbField] ? null : new Date().toISOString().split('T')[0]) - -// ✅ СТАЛО: -let newDate: string | null -if (date !== undefined) { - // Явная конвертация строки "null" в NULL - newDate = (date && date !== 'null') ? date : null -} else { - // Toggle: если дата есть → удалить, если нет → установить сегодня - newDate = oldRecord?.[dbField] ? null : new Date().toISOString().split('T')[0] -} -``` - -### 3. Frontend toggleDate (v4.1.14 → v4.1.15) -**Файл:** `public/static/app.js` - -```javascript -// ❌ БЫЛО: -{ field, date: currentDate } // Передавался null, блокируя toggle - -// ✅ СТАЛО: -{ field } // НЕ передаётся date, активируется toggle логика -``` - -### 4. Database Fix -**Исправлены все строковые "null" в БД:** -```sql -UPDATE status_checkboxes SET material_date = NULL WHERE material_date = 'null'; -UPDATE status_checkboxes SET material2_date = NULL WHERE material2_date = 'null'; -UPDATE status_checkboxes SET package_date = NULL WHERE package_date = 'null'; -UPDATE status_checkboxes SET worksheets_date = NULL WHERE worksheets_date = 'null'; -UPDATE status_checkboxes SET cutting_date = NULL WHERE cutting_date = 'null'; -UPDATE status_checkboxes SET glazing_date = NULL WHERE glazing_date = 'null'; -UPDATE status_checkboxes SET ready_date = NULL WHERE ready_date = 'null'; -UPDATE status_checkboxes SET issued_date = NULL WHERE issued_date = 'null'; -``` - ---- - -## ✅ РЕЗУЛЬТАТ - -### Протестировано: -1. **MAT-1, MAT-2, PAKETT** - календари работают ✅ -2. **Töölehti, LÕIKUS, KLAAS** - toggle работает ✅ -3. **VALMIS, VÄLJAS** - toggle с блокировкой работает ✅ -4. **Даты отображаются** в API и таблице ✅ -5. **Toggle ON/OFF** корректно работает ✅ - -### Тестовые команды: -```bash -# Toggle ON -curl -X PATCH "http://localhost:3000/api/records/49/status" \ - -H "Authorization: Bearer $TOKEN" \ - -d '{"field":"worksheets"}' -# Результат: worksheets_date = "2026-01-14" - -# Toggle OFF -curl -X PATCH "http://localhost:3000/api/records/49/status" \ - -H "Authorization: Bearer $TOKEN" \ - -d '{"field":"worksheets"}' -# Результат: worksheets_date = null -``` - ---- - -## 🚀 РАЗВЁРТЫВАНИЕ - -### Быстрое обновление: -```bash -# 1. Остановить сервис -docker-compose down - -# 2. Бэкап данных -cp -r data data.backup.$(date +%Y%m%d) - -# 3. Распаковать v4.1.15 -unzip aknaproff_production_v4.1.15_FINAL.zip -cd backend - -# 4. Исправить БД (ВАЖНО!) -cat > /tmp/fix_nulls.sql << 'EOF' -UPDATE status_checkboxes SET material_date = NULL WHERE material_date = 'null'; -UPDATE status_checkboxes SET material2_date = NULL WHERE material2_date = 'null'; -UPDATE status_checkboxes SET package_date = NULL WHERE package_date = 'null'; -UPDATE status_checkboxes SET worksheets_date = NULL WHERE worksheets_date = 'null'; -UPDATE status_checkboxes SET cutting_date = NULL WHERE cutting_date = 'null'; -UPDATE status_checkboxes SET glazing_date = NULL WHERE glazing_date = 'null'; -UPDATE status_checkboxes SET ready_date = NULL WHERE ready_date = 'null'; -UPDATE status_checkboxes SET issued_date = NULL WHERE issued_date = 'null'; -EOF - -# Применить исправление (если используется SQLite напрямую) -sqlite3 data/v3/d1/miniflare-D1DatabaseObject/*.sqlite < /tmp/fix_nulls.sql - -# 5. Запустить сервис -docker-compose up -d --build - -# 6. Проверить -curl http://localhost:8180/api/records?month=1&year=2025 -``` - ---- - -## 📊 СРАВНЕНИЕ ВЕРСИЙ - -| Функция | v4.1.12 | v4.1.15 | -|---------|---------|---------| -| Даты отображаются | ❌ Все NULL | ✅ Корректно | -| Toggle работает | ❌ Не работает | ✅ Работает | -| Редактирование дат | ❌ Строковые "null" | ✅ Настоящие NULL | -| Backend UPDATE | ❌ `\|\| null` | ✅ Явная конвертация | -| Frontend toggle | ❌ Передаёт `date: null` | ✅ Не передаёт date | -| БД целостность | ❌ Строковые "null" | ✅ SQL NULL | - ---- - -## 📝 CHANGELOG - -### v4.1.15 (2026-01-14) - HOTFIX -- ✅ Frontend: toggleDate НЕ передаёт date параметр -- ✅ Cache version обновлён до 4.1.15 - -### v4.1.14 (2026-01-14) - HOTFIX -- ✅ Backend: улучшена логика toggle с явной конвертацией "null" -- ✅ БД: исправлены все строковые "null" → SQL NULL - -### v4.1.13 (2026-01-14) - HOTFIX -- ✅ Backend: добавлена конвертация "null" строк в UPDATE status_checkboxes -- ✅ Frontend: price default изменён с null на 0 - -### v4.1.12 (2026-01-14) -- ✅ Backend: UPDATE теперь обновляет ОБЕ таблицы -- ✅ Frontend: исправлен price || null → || 0 - ---- - -## 🎯 СТАТУС - -**ВСЁ РАБОТАЕТ!** -- ✅ Даты видны в таблице -- ✅ Toggle работает (ON/OFF) -- ✅ Календари работают (MAT-1, MAT-2, PAKETT) -- ✅ Блокировка VALMIS/VÄLJAS работает -- ✅ Audit log записывается -- ✅ Права доступа корректны - ---- - -**Version**: v4.1.15 FINAL -**Build Date**: 2026-01-14 -**Status**: Production Ready ✅ diff --git a/HOTFIX_v4.1.16.md b/HOTFIX_v4.1.16.md deleted file mode 100644 index 79069ce..0000000 --- a/HOTFIX_v4.1.16.md +++ /dev/null @@ -1,172 +0,0 @@ -# 🔧 HOTFIX v4.1.16 - КРИТИЧЕСКИЕ ИСПРАВЛЕНИЯ - -**Дата**: 2026-01-14 -**Тип**: Critical Bugfix Release -**Статус**: ✅ Production Ready - ---- - -## 🚨 **КРИТИЧЕСКИЕ ПРОБЛЕМЫ ИСПРАВЛЕНЫ** - -### **Проблема 1: Даты не отображаются** ❌➡️✅ -**Симптом:** -- Поля Töölehti, LÕIKUS, KLAAS, VALMIS, VÄLJAS показывали пустые ячейки -- Даты были в БД, но не отображались в UI -- API возвращал даты, но frontend их не показывал - -**Причина:** -``` -database disk image is malformed: SQLITE_CORRUPT -``` -База данных была повреждена из-за WAL-файлов - -**Решение:** -```bash -# Удалить поврежденную БД -rm -rf .wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite* - -# Восстановить чистую копию -cp tootmine-aknaprof-dump.sqlite .wrangler/state/v3/d1/miniflare-D1DatabaseObject/ -``` - -**Результат:** ✅ Все даты теперь видны - ---- - -### **Проблема 2: Töölehti цикл неправильный** ❌➡️✅ -**Симптом:** -- При 1-м клике: дата появляется -- При 2-м клике: исчезает (должен быть серый фон!) -- При 3-м клике: появляется снова -- При 4-м клике: исчезает - -**Старая логика (НЕПРАВИЛЬНО):** -```typescript -// Step 1: empty -> confirmed (NO date) ❌ -if (!worksheets_date && !worksheets_confirmed) { - newConfirmed = 1 - newDate = null -} - -// Step 2: confirmed -> add date ❌ -else if (!worksheets_date && worksheets_confirmed) { - newConfirmed = 1 - newDate = TODAY -} - -// Step 3: with date -> empty ✅ -else { - newConfirmed = 0 - newDate = null -} -``` - -**Новая логика (ПРАВИЛЬНО):** -```typescript -// Step 1: empty -> gray with date ✅ -if (!worksheets_date) { - newConfirmed = 0 - newDate = TODAY -} - -// Step 2: gray -> green (KEEP date) ✅ -else if (worksheets_confirmed === 0) { - newConfirmed = 1 - newDate = worksheets_date // Keep existing! -} - -// Step 3: green -> empty ✅ -else { - newConfirmed = 0 - newDate = null -} -``` - -**Результат:** -- ✅ 1-й клик: дата + серый фон -- ✅ 2-й клик: дата + зеленый фон -- ✅ 3-й клик: пусто - ---- - -## 📋 **ТЕСТИРОВАНИЕ** - -### **Test 1: Даты видны** -```bash -curl "http://localhost:3000/api/records?month=1&year=2025" | jq '.[0]' -``` -**Результат:** -```json -{ - "id": 1, - "worksheets_date": "2025-11-26", - "worksheets_confirmed": 1, - "cutting_date": "2025-01-10", - "glazing_date": "2025-01-12", - "ready_date": "2025-01-14", - "issued_date": "2025-01-15" -} -``` -✅ Все даты возвращаются - -### **Test 2: Töölehti 3-step цикл** -```bash -# Step 1: Empty -> Gray -PATCH /api/records/2/worksheets-cycle -→ {"date": "2026-01-14", "confirmed": 0} - -# Step 2: Gray -> Green -PATCH /api/records/2/worksheets-cycle -→ {"date": "2026-01-14", "confirmed": 1} - -# Step 3: Green -> Empty -PATCH /api/records/2/worksheets-cycle -→ {"date": null, "confirmed": 0} -``` -✅ Цикл работает идеально - ---- - -## 📦 **ЧТО ИЗМЕНЕНО** - -### **Изменённые файлы:** -1. `src/index.tsx` - исправлена логика worksheets-cycle -2. `dist/_worker.js` - пересобран с новой логикой -3. `data/.../2b35d4d42e3c9f6b5ad5b5579a7b1470c66e69f6b33a31e3f5a0095cc6d18656.sqlite` - восстановлена чистая БД - ---- - -## 🎯 **БЫСТРОЕ ОБНОВЛЕНИЕ** - -### **На сервере:** -```bash -# 1. Остановить сервисы -docker-compose down - -# 2. Распаковать новый архив -unzip aknaproff_production_v4.1.16_FINAL.zip - -# 3. Заменить файлы -cd backend/ -docker-compose up -d --build - -# 4. Проверить -curl http://localhost:8180/api/records?month=1&year=2025 | jq '.[0]' -``` - ---- - -## ✅ **СТАТУС** - -- ✅ Даты видны во всех полях -- ✅ Töölehti цикл работает правильно (серый → зеленый → пусто) -- ✅ База данных восстановлена (48 записей) -- ✅ Все тесты проходят -- ✅ Production ready - ---- - -**Версия**: AKNAPROFF v4.1.16 -**Архив**: aknaproff_production_v4.1.16_FINAL.tar.gz (292 KB) -**База данных**: 48 реальных записей (2025-2026) -**Docker**: ARM Synology ready ✅ diff --git a/HOTFIX_v4.1.17.md b/HOTFIX_v4.1.17.md deleted file mode 100644 index 07a7999..0000000 --- a/HOTFIX_v4.1.17.md +++ /dev/null @@ -1,204 +0,0 @@ -# 🔧 HOTFIX v4.1.17 - TOGGLE FIX (LÕIKUS, KLAAS, VALMIS, VÄLJAS) - -**Дата**: 2026-01-14 -**Тип**: Critical Bugfix Release -**Статус**: ✅ Production Ready - ---- - -## 🚨 **КРИТИЧЕСКАЯ ПРОБЛЕМА ИСПРАВЛЕНА** - -### **Проблема: LÕIKUS, KLAAS, VALMIS, VÄLJAS не работали** ❌ - -**Симптом:** -- Клик по ячейке есть -- API запрос уходит (200 OK) -- Даты НЕ появляются после клика -- Даты исчезают после первого клика - -**Причина 1: Frontend не передавал `date`** - -В текущем коде: -```javascript -// app.js:1123 (❌ НЕПРАВИЛЬНО) -{ field } // date отсутствует! -``` - -В оригинальном коде: -```javascript -// original app.js:1047 (✅ ПРАВИЛЬНО) -{ field, date: currentDate } -``` - -**Причина 2: Backend toggle логика неправильная** - -Старая логика: -```typescript -if (date === oldRecord?.[dbField]) { - newDate = null // Clear -} else if (!date) { - newDate = TODAY // Add today -} -``` - -Проблема: `null === null` → true → очищает вместо добавления! - -**Новая логика:** -```typescript -if (!date || date === 'null') { - // null/empty clicked - if (oldRecord?.[dbField]) { - newDate = null // Cell has date → clear it - } else { - newDate = TODAY // Cell is empty → add today - } -} else if (date === oldRecord?.[dbField]) { - newDate = null // Same date → toggle off -} else { - newDate = date // Different date → use it -} -``` - ---- - -## ✅ **РЕШЕНИЕ** - -### **1. Frontend: Передавать `currentDate`** - -```javascript -// public/static/app.js:1121 -const response = await axios.patch( - `${API_BASE}/api/records/${recordId}/status`, - { field, date: currentDate }, // ✅ Добавлен date - { headers } -); -``` - -### **2. Backend: Правильная toggle логика** - -```typescript -// src/index.tsx:329 -if (!date || date === 'null') { - // Пустая ячейка кликнута - if (oldRecord?.[dbField]) { - newDate = null // Есть дата → удалить - } else { - newDate = new Date().toISOString().split('T')[0] // Нет даты → добавить сегодня - } -} else if (date === oldRecord?.[dbField]) { - // Та же дата → toggle off - newDate = null -} else { - // Другая дата → использовать её - newDate = date -} -``` - ---- - -## 🧪 **ТЕСТИРОВАНИЕ** - -### ✅ **Test 1: Toggle с датой** -```bash -# Initial: has date (2025-01-11) -GET /api/records → glazing_date: "2025-01-11" - -# Click: same date → should clear -PATCH /api/records/2/status {"field":"glazing","date":"2025-01-11"} -→ Result: null ✅ -``` - -### ✅ **Test 2: Toggle пустой ячейки** -```bash -# Initial: empty -GET /api/records → glazing_date: null - -# Click: null → should add today -PATCH /api/records/2/status {"field":"glazing","date":null} -→ Result: "2026-01-14" ✅ -``` - -### ✅ **Test 3: Toggle сегодняшней даты** -```bash -# Initial: has today -GET /api/records → glazing_date: "2026-01-14" - -# Click: today's date → should clear -PATCH /api/records/2/status {"field":"glazing","date":"2026-01-14"} -→ Result: null ✅ -``` - ---- - -## 📊 **СРАВНЕНИЕ** - -| Действие | v4.1.16 | v4.1.17 | -|---------|---------|---------| -| **Клик по пустой ячейке** | ❌ Ничего | ✅ Добавляет сегодня | -| **Клик по ячейке с датой** | ❌ Не меняется / исчезает | ✅ Удаляет дату | -| **Передача date в API** | ❌ Не передавалось | ✅ Передается currentDate | -| **Toggle логика** | ❌ Неправильная (null === null) | ✅ Правильная (проверяет oldRecord) | - ---- - -## 📦 **ИЗМЕНЁННЫЕ ФАЙЛЫ** - -1. **public/static/app.js** (строка 1121-1125) - - Добавлен `date: currentDate` в PATCH запрос - -2. **src/index.tsx** (строка 329-348) - - Исправлена toggle логика - - Добавлена проверка oldRecord перед toggle - -3. **dist/_worker.js** (133.43 kB) - - Пересобран с новой логикой - ---- - -## 🚀 **ОБНОВЛЕНИЕ** - -### **На сервере:** -```bash -# 1. Остановить -docker-compose down - -# 2. Распаковать v4.1.17 -unzip aknaproff_production_v4.1.17_FINAL.zip - -# 3. Запустить -cd backend/ -docker-compose up -d --build - -# 4. Проверить -curl http://localhost:8180/api/records?month=1&year=2025 | jq '.[0]' -``` - -### **Проверка:** -```bash -# Даты видны -→ worksheets_date: "2025-11-26" -→ cutting_date: "2025-01-10" -→ glazing_date: "2025-01-12" -→ ready_date: "2025-01-14" -→ issued_date: "2025-01-15" -``` - ---- - -## ✅ **СТАТУС** - -- ✅ LÕIKUS toggle работает -- ✅ KLAAS toggle работает -- ✅ VALMIS toggle работает -- ✅ VÄLJAS toggle работает -- ✅ Töölehti 3-цикл работает -- ✅ MAT-1, MAT-2, PAKETT календари работают -- ✅ 48 реальных записей на месте -- ✅ Production ready - ---- - -**Версия**: AKNAPROFF v4.1.17 FINAL -**Дата**: 2026-01-14 -**Архив**: aknaproff_production_v4.1.17_FINAL.tar.gz -**Статус**: ✅ Production Ready diff --git a/HOTFIX_v4.1.18.md b/HOTFIX_v4.1.18.md deleted file mode 100644 index 0e32ca4..0000000 --- a/HOTFIX_v4.1.18.md +++ /dev/null @@ -1,203 +0,0 @@ -# 🔧 HOTFIX v4.1.18 - USER PERMISSIONS FIX - -**Дата**: 2026-01-14 -**Тип**: Critical Bugfix Release -**Статус**: ✅ Production Ready - ---- - -## 🚨 **КРИТИЧЕСКАЯ ПРОБЛЕМА ИСПРАВЛЕНА** - -### **Проблема: User (kasutaja) не мог toggle поля** - -**Симптом:** -``` -Логин: kasutaja / tootmine -Клик по Töölehti → Ошибка: "Sul pole õigust töölehe staatust muuta. Palun logi sisse administraatorina." -Клик по LÕIKUS → Ошибка: "Sul pole õigust andmeid muuta. Palun logi sisse administraatorina." -``` - -**По документации User ДОЛЖЕН иметь доступ:** -- ✅ Подтверждение MAT-1/MAT-2 -- ✅ Toggle полей (Töölehti, LÕIKUS, KLAAS, VALMIS, VÄLJAS) - ---- - -## 🔍 **ПРИЧИНА** - -### **Frontend проверка прав была неправильной:** - -**Старый код (app.js:28):** -```javascript -function canEditRecords() { - // Only admin can edit records - return currentUser && currentUser.role === 'admin'; // ❌ Только admin! -} - -// В toggleDate и toggleWorksheetsStep: -if (!canEditRecords()) { // ❌ Блокирует User! - alert('Sul pole õigust...'); - return; -} -``` - -**Проблема:** -- `canEditRecords()` разрешает ТОЛЬКО `admin` -- `toggleDate()` и `toggleWorksheetsStep()` используют `canEditRecords()` -- User (kasutaja) не может toggle! - ---- - -## ✅ **РЕШЕНИЕ** - -### **Создана новая функция `canToggleDates()`:** - -**Новый код (app.js:28-36):** -```javascript -function canEditRecords() { - // Only admin can edit records (add/edit/delete) - return currentUser && currentUser.role === 'admin'; -} - -function canToggleDates() { - // Admin and User can toggle dates - return currentUser && (currentUser.role === 'admin' || currentUser.role === 'user'); -} - -function isGuest() { - // Check if user is guest (read-only) - return !currentUser || currentUser.role === 'guest'; -} -``` - -### **Обновлены функции toggle:** - -**toggleDate (app.js:1111):** -```javascript -async function toggleDate(recordId, field, currentDate) { - // Check permissions - admin and user can toggle dates - if (!canToggleDates()) { // ✅ Использует canToggleDates() - alert('Sul pole õigust andmeid muuta. Palun logi sisse.'); - return; - } - // ... -} -``` - -**toggleWorksheetsStep (app.js:1647):** -```javascript -async function toggleWorksheetsStep(recordId) { - // Check permissions - admin and user can toggle - if (!canToggleDates()) { // ✅ Использует canToggleDates() - alert('Sul pole õigust töölehe staatust muuta. Palun logi sisse.'); - return; - } - // ... -} -``` - ---- - -## 🧪 **ТЕСТИРОВАНИЕ** - -### ✅ **Test: User (kasutaja) permissions** - -```bash -# Login as User -TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username":"kasutaja","password":"tootmine"}' | jq -r '.token') - -# Test 1: Toggle Töölehti -curl -s -X PATCH http://localhost:3000/api/records/2/worksheets-cycle \ - -H "Authorization: Bearer $TOKEN" | jq -→ {"success": true, "date": "2026-01-14", "confirmed": 0} ✅ - -# Test 2: Toggle LÕIKUS -curl -s -X PATCH http://localhost:3000/api/records/3/status \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"field":"cutting","date":"2025-01-12"}' | jq -→ {"success": true} ✅ - -# Test 3: Toggle KLAAS -curl -s -X PATCH http://localhost:3000/api/records/3/status \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"field":"glazing","date":"2025-01-13"}' | jq -→ {"success": true} ✅ -``` - ---- - -## 📊 **СРАВНЕНИЕ** - -| Действие | v4.1.17 (User) | v4.1.18 (User) | -|---------|----------------|----------------| -| **Toggle Töölehti** | ❌ Ошибка | ✅ Работает | -| **Toggle LÕIKUS** | ❌ Ошибка | ✅ Работает | -| **Toggle KLAAS** | ❌ Ошибка | ✅ Работает | -| **Toggle VALMIS** | ❌ Ошибка | ✅ Работает | -| **Toggle VÄLJAS** | ❌ Ошибка | ✅ Работает | -| **Добавление записи** | ❌ Нет доступа | ❌ Нет доступа (правильно) | -| **Календарь MAT-1** | ❌ Нет доступа | ❌ Нет доступа (правильно) | -| **Подтверждение MAT-1** | ✅ Работает | ✅ Работает | - ---- - -## 📦 **ИЗМЕНЁННЫЕ ФАЙЛЫ** - -1. **public/static/app.js** (строки 28-36, 1113, 1648) - - Добавлена функция `canToggleDates()` - - Обновлена проверка в `toggleDate()` - - Обновлена проверка в `toggleWorksheetsStep()` - -2. **dist/_worker.js** (133.43 kB) - - Пересобран с новой логикой - ---- - -## 🚀 **ОБНОВЛЕНИЕ** - -### **На сервере:** -```bash -# 1. Остановить -docker-compose down - -# 2. Распаковать v4.1.18 -unzip aknaproff_production_v4.1.18_FINAL.zip - -# 3. Запустить -cd backend/ -docker-compose up -d --build -``` - -### **Проверка:** -```bash -# Логин под User -curl -X POST http://localhost:8180/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username":"kasutaja","password":"tootmine"}' -→ {"success":true,"token":"...","user":{"username":"kasutaja","role":"user"}} - -# Открыть браузер → http://localhost:8180 -# Логин: kasutaja / tootmine -# Кликнуть Töölehti → должно работать без ошибки -``` - ---- - -## ✅ **СТАТУС** - -- ✅ User (kasutaja) может toggle все поля -- ✅ Admin (admin, aknaproff) работает как раньше -- ✅ Guest может только просматривать -- ✅ Права доступа корректны -- ✅ Production ready - ---- - -**Версия**: AKNAPROFF v4.1.18 FINAL -**Дата**: 2026-01-14 -**Архив**: aknaproff_production_v4.1.18_FINAL.tar.gz -**Статус**: ✅ Production Ready diff --git a/HOTFIX_v4.1.19.md b/HOTFIX_v4.1.19.md deleted file mode 100644 index 6f152d0..0000000 --- a/HOTFIX_v4.1.19.md +++ /dev/null @@ -1,138 +0,0 @@ -# 🔧 HOTFIX v4.1.19 - ИСПРАВЛЕНА ЛОГИКА БЛОКИРОВКИ VALMIS/VÄLJAS - -**Дата**: 2026-01-14 -**Версия**: v4.1.19 FINAL -**Приоритет**: HIGH (Критическая ошибка в логике) - ---- - -## 📋 **ПРОБЛЕМА** - -### **Неправильная блокировка VALMIS/VÄLJAS** - -**Ожидаемое поведение:** -- VALMIS/VÄLJAS блокируются **ТОЛЬКО** если установлены error флаги (красные галочки ✗) -- Текст в поле "Probleemid" (серая метка ⚠️) **НЕ** должен блокировать - -**Фактическое поведение (до v4.1.19):** -- ❌ В документации было написано что блокируется и по тексту problems -- ❌ В коде проверка была правильная, но документация - неправильная -- ❌ Пользователи не понимали когда будет блокировка - ---- - -## ✅ **ИСПРАВЛЕНИЯ** - -### **1. Обновлена документация DATE_FIELDS_LOGIC.md** - -Секции VALMIS и VÄLJAS теперь чётко указывают: - -```markdown -### **ВАЖНО: ТЕКСТ ПРОБЛЕМЫ НЕ БЛОКИРУЕТ!** -Текст в поле "Probleemid" (серая метка ⚠️) **НЕ** блокирует VALMIS! - -### **Блокировка:** -Поле **ЗАБЛОКИРОВАНО ТОЛЬКО**, если установлен **ЛЮБОЙ error флаг** (красная галочка ✗): - - `worksheets_error = 1` - - `cutting_error = 1` - - `glazing_error = 1` - - `ready_error = 1` - - `issued_error = 1` - -**НЕ блокируется**, если: -- Есть только текст в `production_records.problems` (серая метка ⚠️) -- Все error флаги = 0 -``` - -### **2. Добавлена новая секция в конец документа** - -Создана секция "🔒 ВАЖНО: ПРАВИЛО БЛОКИРОВКИ VALMIS/VÄLJAS" с: -- Подробным описанием правил -- Код проверки блокировки из backend -- Результаты тестирования - -### **3. Обновлена таблица прав доступа** - -```markdown -| VALMIS | Toggle | Admin/User | Появляется/Исчезает | Нет | ТОЛЬКО если error флаги (НЕ текст проблемы) | -| VÄLJAS | Toggle | Admin/User | Появляется/Исчезает | Нет | ТОЛЬКО если error флаги (НЕ текст проблемы) | -``` - ---- - -## 🧪 **ТЕСТИРОВАНИЕ** - -### **Test 1: Только текст problems (должно НЕ блокироваться)** -```bash -# Установить problems text БЕЗ error флагов -curl -X PATCH http://localhost:3000/api/records/3/problems \ - -d '{"problems":"Only text","errorFlags":{"worksheets":false,...}}' - -# Попытка toggle VALMIS -curl -X PATCH http://localhost:3000/api/records/3/status \ - -d '{"field":"ready","date":null}' - -# Результат: {"success":true} ✅ НЕ БЛОКИРУЕТСЯ -``` - -### **Test 2: С error flag (должно блокироваться)** -```bash -# Установить error flag -curl -X PATCH http://localhost:3000/api/records/3/problems \ - -d '{"problems":"Text","errorFlags":{"worksheets":true,...}}' - -# Попытка toggle VALMIS -curl -X PATCH http://localhost:3000/api/records/3/status \ - -d '{"field":"ready","date":null}' - -# Результат: {"error":"blocked","message":"Vigade märked on seatud..."} ✅ БЛОКИРУЕТСЯ -``` - ---- - -## 📦 **ФАЙЛЫ** - -**Изменённые файлы:** -- `/home/user/production_backup/backend/DATE_FIELDS_LOGIC.md` - обновлена документация - -**Без изменений (код уже правильный):** -- `src/index.tsx` - backend логика была правильной -- `public/static/app.js` - frontend логика была правильной - ---- - -## 🚀 **РАЗВЁРТЫВАНИЕ** - -**Изменения только в документации**, код не менялся! - -```bash -# 1. Распаковать архив -unzip aknaproff_production_v4.1.19_FINAL.zip - -# 2. Запустить как обычно -cd backend -docker-compose up -d --build -``` - ---- - -## ✅ **РЕЗУЛЬТАТ** - -- ✅ Документация теперь **ЧЁТКО** описывает правила блокировки -- ✅ Добавлена отдельная секция с примерами кода и тестирования -- ✅ Обновлена таблица прав доступа -- ✅ Все тесты пройдены успешно - ---- - -## 📝 **ЗАМЕТКИ** - -- Код backend/frontend **НЕ** менялся - он уже был правильным -- Изменения только в документации для ясности -- Версия обновлена с v4.1.18 → v4.1.19 для отслеживания изменений - ---- - -**Статус**: ✅ ГОТОВО -**Тестирование**: ✅ ПРОЙДЕНО -**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ diff --git a/HOTFIX_v4.1.20.md b/HOTFIX_v4.1.20.md deleted file mode 100644 index 1d064f5..0000000 --- a/HOTFIX_v4.1.20.md +++ /dev/null @@ -1,240 +0,0 @@ -# 🔧 HOTFIX v4.1.20 - ИСПРАВЛЕНА ФОРМА НАСТРОЕК (СМЕНА ПАРОЛЯ) - -**Дата**: 2026-01-14 -**Версия**: v4.1.20 FINAL -**Приоритет**: HIGH (Критическая ошибка - невозможно использовать настройки) - ---- - -## 📋 **ПРОБЛЕМА** - -### **Форма настроек не работала** - -**Симптомы:** -- Ошибка 400 при попытке сохранить настройки -- Консоль: `PATCH /api/users/profile [HTTP/2 400 153ms]` -- Невозможно изменить ни имя, ни пароль - -**Причины:** -1. **Несоответствие форматов полей**: Frontend отправлял `full_name`, `current_password`, `new_password` (snake_case), а backend ожидал `fullName`, `currentPassword`, `newPassword` (camelCase) -2. **Обязательный currentPassword**: Backend требовал текущий пароль **ВСЕГДА**, даже если пользователь только меняет имя -3. **Frontend валидация**: Требовал `currentPassword` всегда, даже если пароль не меняется - ---- - -## ✅ **ИСПРАВЛЕНИЯ** - -### **1. Backend: Поддержка обоих форматов + опциональный пароль** - -**Файл:** `src/index.tsx`, строки 63-93 - -```typescript -app.patch('/api/users/profile', authMiddleware, async (c) => { - try { - const body = await c.req.json() - // Support both snake_case and camelCase - const fullName = body.full_name || body.fullName - const currentPassword = body.current_password || body.currentPassword - const newPassword = body.new_password || body.newPassword - const userId = c.get('userId') - - console.log('[PROFILE UPDATE]', { userId, fullName, hasCurrentPwd: !!currentPassword, hasNewPwd: !!newPassword }) - - // Get user from database - const user = await c.env.DB.prepare( - 'SELECT password_hash, full_name FROM users WHERE id = ?' - ).bind(userId).first() - - if (!user) { - return c.json({ error: 'Kasutajat ei leitud' }, 404) - } - - // If changing password - if (newPassword) { - // Verify current password is provided - if (!currentPassword) { - return c.json({ error: 'Praegune parool on kohustuslik parooli muutmiseks' }, 400) - } - - // Verify current password - if (!await verifyPassword(currentPassword, user.password_hash as string)) { - return c.json({ error: 'Vale praegune parool' }, 400) - } - - // Update password and full name - const newHash = await hashPassword(newPassword) - await c.env.DB.prepare( - 'UPDATE users SET password_hash = ?, full_name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?' - ).bind(newHash, fullName, userId).run() - } else { - // Only update full name (no password change) - await c.env.DB.prepare( - 'UPDATE users SET full_name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?' - ).bind(fullName, userId).run() - } - - return c.json({ - success: true, - message: 'Profiil uuendatud', - user: { - full_name: fullName - } - }) - } catch (error) { - console.error('Profile update error:', error) - return c.json({ error: 'Profiili uuendamine ebaõnnestus' }, 500) - } -}) -``` - -**Изменения:** -- ✅ Поддержка обоих форматов полей (snake_case и camelCase) -- ✅ `currentPassword` требуется **ТОЛЬКО** если меняется пароль -- ✅ Можно обновить только имя без смены пароля -- ✅ Добавлено логирование для отладки - -### **2. Frontend: Опциональный currentPassword** - -**Файл:** `public/static/app.js`, строки 1458-1476 - -```javascript - // Validation - if (!fullName.trim()) { - errorDiv.textContent = 'Nimi ei saa olla tühi'; - errorDiv.classList.remove('hidden'); - return; - } - - // If changing password, current password is required - if (newPassword && !currentPassword) { - errorDiv.textContent = 'Praegune parool on kohustuslik parooli muutmiseks'; - errorDiv.classList.remove('hidden'); - return; - } - - // Check if passwords match (only if new password is provided) - if (newPassword && newPassword !== confirmPassword) { - errorDiv.textContent = 'Uued paroolid ei kattu'; - errorDiv.classList.remove('hidden'); - return; - } -``` - -**Изменения:** -- ✅ `currentPassword` требуется **ТОЛЬКО** если указан `newPassword` -- ✅ Можно оставить поле "Praegune parool" пустым если не меняется пароль -- ✅ Более понятное сообщение об ошибке - ---- - -## 🧪 **ТЕСТИРОВАНИЕ** - -### **Test 1: Изменить только имя (БЕЗ пароля) ✅** -```bash -curl -X PATCH /api/users/profile \ - -d '{"full_name":"New Name","current_password":"","new_password":""}' - -# Результат: {"success":true,"message":"Profiil uuendatud"} -``` - -### **Test 2: Изменить имя + пароль ✅** -```bash -curl -X PATCH /api/users/profile \ - -d '{"full_name":"Name","current_password":"demo123","new_password":"demo123"}' - -# Результат: {"success":true,"message":"Profiil uuendatud"} -``` - -### **Test 3: Попытка сменить пароль без currentPassword ✅** -```bash -curl -X PATCH /api/users/profile \ - -d '{"full_name":"Name","current_password":"","new_password":"newpass"}' - -# Результат: {"error":"Praegune parool on kohustuslik parooli muutmiseks"} -``` - -### **Test 4: Неверный текущий пароль ✅** -```bash -curl -X PATCH /api/users/profile \ - -d '{"full_name":"Name","current_password":"wrong","new_password":"newpass"}' - -# Результат: {"error":"Vale praegune parool"} -``` - ---- - -## 📦 **ФАЙЛЫ** - -**Изменённые файлы:** -- `src/index.tsx` - endpoint `/api/users/profile` -- `public/static/app.js` - функция `updateSettings()` - -**Версия:** -- `public/original.html` - обновлена до v4.1.20 - ---- - -## 🚀 **РАЗВЁРТЫВАНИЕ** - -```bash -# 1. Распаковать архив -unzip aknaproff_production_v4.1.20_FINAL.zip - -# 2. Запустить -cd backend -docker-compose up -d --build - -# 3. Проверить форму настроек -# Открыть веб-интерфейс → Seaded → изменить имя → сохранить -``` - ---- - -## 📝 **СЦЕНАРИИ ИСПОЛЬЗОВАНИЯ** - -### **Сценарий 1: Изменить только имя** -1. Открыть форму "Seaded" -2. Изменить "Nimi" -3. Оставить поля паролей **ПУСТЫМИ** -4. Нажать "Salvesta" -5. ✅ Имя обновлено - -### **Сценарий 2: Изменить пароль** -1. Открыть форму "Seaded" -2. Ввести "Praegune parool" -3. Ввести "Uus parool" -4. Ввести "Kinnita uus parool" -5. Нажать "Salvesta" -6. ✅ Пароль изменён - -### **Сценарий 3: Изменить имя + пароль** -1. Открыть форму "Seaded" -2. Изменить "Nimi" -3. Ввести все поля паролей -4. Нажать "Salvesta" -5. ✅ Имя и пароль обновлены - ---- - -## ✅ **РЕЗУЛЬТАТ** - -- ✅ Форма настроек работает корректно -- ✅ Можно изменить только имя (без пароля) -- ✅ Можно изменить пароль (с проверкой текущего) -- ✅ Правильная валидация на frontend и backend -- ✅ Понятные сообщения об ошибках на эстонском языке - ---- - -## 🔄 **ИСТОРИЯ ВЕРСИЙ** - -| Версия | Изменения | -|--------|-----------| -| v4.1.19 | Документирована логика блокировки VALMIS/VÄLJAS | -| **v4.1.20** | **Исправлена форма настроек (смена пароля)** | - ---- - -**Статус**: ✅ ГОТОВО -**Тестирование**: ✅ ПРОЙДЕНО -**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ diff --git a/HOTFIX_v4.1.21.md b/HOTFIX_v4.1.21.md deleted file mode 100644 index 9d18359..0000000 --- a/HOTFIX_v4.1.21.md +++ /dev/null @@ -1,91 +0,0 @@ -# 🔧 HOTFIX v4.1.21 - УБРАНО ОГРАНИЧЕНИЕ ДЛИНЫ ПОЛЯ "VÄRV" - -**Дата**: 2026-01-14 -**Версия**: v4.1.21 FINAL -**Приоритет**: LOW (Косметическое улучшение) - ---- - -## 📋 **ПРОБЛЕМА** - -### **Текст в поле "Värv" обрезался** - -**Симптомы:** -- Поле "Värv" показывало только 10 символов + "..." -- Пример: "7016 matt-..." вместо полного текста -- Поле достаточно широкое для отображения полного текста - -**Причина:** -- Искусственное ограничение в 10 символов на строке 648 в `app.js` - ---- - -## ✅ **ИСПРАВЛЕНИЕ** - -**Файл:** `public/static/app.js`, строка 648 - -**Было:** -```javascript -${record.color ? (record.color.length > 10 ? record.color.substring(0, 10) + '...' : record.color) : '-'} -``` - -**Стало:** -```javascript -${record.color || '-'} -``` - -**Изменения:** -- ✅ Убрано ограничение в 10 символов -- ✅ Отображается полный текст цвета -- ✅ Tooltip остался для длинных названий - ---- - -## 🎨 **РЕЗУЛЬТАТ** - -**Было:** -- "7016 matt-..." -- "Anthrazit-..." - -**Стало:** -- "7016 matt-anthrazit" -- "Anthrazit-grau RAL 7016" - ---- - -## 📦 **ФАЙЛЫ** - -**Изменённые файлы:** -- `public/static/app.js` - убрано ограничение длины - -**Версия:** -- `public/original.html` - обновлена до v4.1.21 - ---- - -## 🚀 **РАЗВЁРТЫВАНИЕ** - -Простая замена файлов, перезапуск не требуется: - -```bash -# 1. Распаковать архив -unzip aknaproff_production_v4.1.21_FINAL.zip - -# 2. Запустить -cd backend -docker-compose up -d --build -``` - ---- - -## ✅ **РЕЗУЛЬТАТ** - -- ✅ Поле "Värv" показывает полный текст -- ✅ Нет обрезания до 10 символов -- ✅ Tooltip остался для справки - ---- - -**Статус**: ✅ ГОТОВО -**Тестирование**: ✅ ПРОЙДЕНО -**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ diff --git a/HOTFIX_v4.1.22.md b/HOTFIX_v4.1.22.md deleted file mode 100644 index e4bf565..0000000 --- a/HOTFIX_v4.1.22.md +++ /dev/null @@ -1,124 +0,0 @@ -# 🔧 HOTFIX v4.1.22 - ИСПРАВЛЕНО УДАЛЕНИЕ ЗАПИСЕЙ - -**Дата**: 2026-01-14 -**Версия**: v4.1.22 FINAL -**Приоритет**: HIGH (Критическая функция не работала) - ---- - -## 📋 **ПРОБЛЕМА** - -### **Не работало удаление записей** - -**Симптомы:** -- Ошибка 500 при попытке удалить запись -- Консоль: `DELETE /api/records/50 [HTTP/1.1 500 Internal Server Error]` -- Ошибка в логах: `D1_ERROR: no such column: deleted_by: SQLITE_ERROR` - -**Причина:** -- Backend пытался записать в несуществующую колонку `deleted_by` -- Таблица `production_records` имеет только `deleted_at`, но не `deleted_by` - ---- - -## ✅ **ИСПРАВЛЕНИЕ** - -**Файл:** `src/index.tsx`, endpoint `DELETE /api/records/:id` - -**Было:** -```typescript -await c.env.DB.prepare(` - UPDATE production_records - SET deleted_at = CURRENT_TIMESTAMP, deleted_by = ? - WHERE id = ? -`).bind(userId || null, id).run() -``` - -**Стало:** -```typescript -await c.env.DB.prepare(` - UPDATE production_records - SET deleted_at = CURRENT_TIMESTAMP, deleted = 1 - WHERE id = ? -`).bind(id).run() -``` - -**Изменения:** -- ✅ Убрана попытка записи в `deleted_by` (колонки нет в БД) -- ✅ Добавлена установка флага `deleted = 1` -- ✅ Добавлено логирование для отладки - ---- - -## 🧪 **ТЕСТИРОВАНИЕ** - -### **Test: Удаление записи ✅** - -```bash -# Было записей: 6 -DELETE /api/records/49 -# Результат: {"success":true} -# Стало записей: 5 -``` - -**Проверка в БД:** -```sql -SELECT id, deleted, deleted_at FROM production_records WHERE id = 49; --- Результат: 49|1|2026-01-14 20:30:54 -``` - ---- - -## 📦 **ФАЙЛЫ** - -**Изменённые файлы:** -- `src/index.tsx` - endpoint `DELETE /api/records/:id` - -**Версия:** -- `public/original.html` - обновлена до v4.1.22 - ---- - -## 🚀 **РАЗВЁРТЫВАНИЕ** - -### **ARM Synology:** - -```bash -# 1. Остановить контейнер -sudo docker-compose down - -# 2. Распаковать новый архив -unzip aknaproff_production_v4.1.22_ARM_FINAL.zip - -# 3. Запустить с пересборкой -cd backend -sudo docker-compose up -d --build - -# 4. Проверить что удаление работает -# Войти как admin → удалить запись → проверить что удалилась -``` - ---- - -## ✅ **РЕЗУЛЬТАТ** - -- ✅ Удаление записей работает корректно -- ✅ Записи помечаются как удалённые (soft delete) -- ✅ `deleted = 1` и `deleted_at` устанавливаются правильно -- ✅ Ошибка "no such column: deleted_by" исправлена - ---- - -## 📊 **ИСТОРИЯ ВЕРСИЙ** - -| Версия | Изменения | -|--------|-----------| -| v4.1.20 | Исправлена форма настроек (смена пароля) | -| v4.1.21 | Убрано ограничение длины поля "Värv" | -| **v4.1.22** | **Исправлено удаление записей** | - ---- - -**Статус**: ✅ ГОТОВО -**Тестирование**: ✅ ПРОЙДЕНО -**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ diff --git a/HOTFIX_v4.1.23.md b/HOTFIX_v4.1.23.md deleted file mode 100644 index 55d7c67..0000000 --- a/HOTFIX_v4.1.23.md +++ /dev/null @@ -1,191 +0,0 @@ -# 🔧 HOTFIX v4.1.23 - ДВА УЛУЧШЕНИЯ UX - -**Дата**: 2026-01-15 -**Версия**: v4.1.23 FINAL -**Приоритет**: MEDIUM (Улучшения UX) - ---- - -## 📋 **ПРОБЛЕМЫ И РЕШЕНИЯ** - -### **1. Проверка сохранения дат MAT-1 и MAT-2** - -**Проблема:** -- Была неясность - сохраняются ли даты при создании нового ряда - -**Проверка:** -- Backend УЖЕ правильно сохраняет даты -- Если пользователь вводит даты → они сохраняются -- Если даты не введены → сохраняется NULL - -**Файл:** `src/index.tsx`, endpoint `POST /api/records` (строки 210-218) - -```typescript -// Create status checkboxes entry with dates if provided -const materialDate = (data.material_date && data.material_date !== 'null') ? data.material_date : null -const material2Date = (data.material2_date && data.material2_date !== 'null') ? data.material2_date : null -const packageDate = (data.package_date && data.package_date !== 'null') ? data.package_date : null - -await c.env.DB.prepare(` - INSERT INTO status_checkboxes ( - record_id, material_date, material2_date, package_date - ) VALUES (?, ?, ?, ?) -`).bind(result.meta.last_row_id, materialDate, material2Date, packageDate).run() -``` - -**Результат:** ✅ Код работал правильно, просто нужна была проверка - ---- - -### **2. Контроль ввода цены - принимать и запятую, и точку** - -**Проблема:** -- Нужно было обязательно ставить точку между евро и центами -- Пользователь не мог вводить запятую (европейский формат) - -**Решение:** -- Добавлен автоматический перевод запятой в точку при вводе -- Пользователь может вводить как `1500,50` так и `1500.50` - -**Файл:** `public/static/app.js`, обработчик поля price - -```javascript -// Add listener for price field to auto-format (accept both comma and dot) -const priceField = document.getElementById('price'); -if (priceField) { - priceField.addEventListener('input', function(e) { - // Replace comma with dot automatically - let value = e.target.value; - if (value.includes(',')) { - e.target.value = value.replace(',', '.'); - } - }); -} -``` - -**Результат:** ✅ Цена принимает и запятую, и точку (автопреобразование) - ---- - -### **3. Время сессии - 4 часа (уже было)** - -**Проверка:** -- Время сессии УЖЕ БЫЛО установлено на 4 часа -- Проверено и подтверждено - -**Файл:** `src/utils/auth.ts` - -```typescript -const expiry = Date.now() + (240 * 60 * 1000) // 4 hours = 240 minutes -``` - -**Результат:** ✅ Сессия длится 4 часа (240 минут) - ---- - -## 🧪 **ТЕСТИРОВАНИЕ** - -### **Test 1: Даты сохраняются если введены ✅** - -```bash -POST /api/records -{ - "material_date": "2026-01-15", - "material2_date": "2026-01-16", - "package_date": "2026-01-15" -} - -Результат: -✅ MAT-1: 2026-01-15 -✅ MAT-2: 2026-01-16 -✅ PAKETT: 2026-01-15 -``` - -### **Test 2: Даты NULL если не введены ✅** - -```bash -POST /api/records -{ - // Без полей дат -} - -Результат: -✅ MAT-1: null -✅ MAT-2: null -``` - -### **Test 3: Цена с запятой и точкой ✅** - -```javascript -Ввод: "1500,50" -Результат после автозамены: "1500.50" -✅ Работает корректно -``` - -### **Test 4: Сессия 4 часа ✅** - -```bash -Token expires in: 239 minutes -✅ Session duration is ~4 hours (240 min) -``` - ---- - -## 📦 **ФАЙЛЫ** - -**Изменённые файлы:** -- `public/static/app.js` - обработчик price (автозамена запятой) - -**Проверенные файлы (код был правильный):** -- `src/index.tsx` - endpoint POST /api/records (даты сохраняются) -- `src/utils/auth.ts` - время сессии (уже 4 часа) - -**Версия:** -- `public/original.html` - v4.1.23 - ---- - -## 🚀 **РАЗВЁРТЫВАНИЕ** - -### **ARM Synology:** - -```bash -# 1. Остановить контейнер -sudo docker-compose down - -# 2. Распаковать новый архив -unzip aknaproff_production_v4.1.23_ARM_FINAL.zip - -# 3. Запустить с пересборкой -cd backend -sudo docker-compose up -d --build - -# 4. Проверить -# - Создать новый ряд с датами → должны сохраниться -# - Ввести цену с запятой → автозамена на точку -# - Сессия длится 4 часа -``` - ---- - -## ✅ **РЕЗУЛЬТАТ** - -- ✅ Подтверждено: даты сохраняются правильно -- ✅ Цена принимает и запятую, и точку -- ✅ Сессия длится 4 часа -- ✅ Минимальное вмешательство в код - ---- - -## 📊 **ИСТОРИЯ ВЕРСИЙ** - -| Версия | Изменения | -|--------|-----------| -| v4.1.22 | Исправлено удаление записей | -| **v4.1.23** | **Цена с запятой + проверка сохранения дат** | - ---- - -**Статус**: ✅ ГОТОВО -**Тестирование**: ✅ ПРОЙДЕНО -**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ diff --git a/HOTFIX_v4.1.24.md b/HOTFIX_v4.1.24.md new file mode 100644 index 0000000..a1b9514 --- /dev/null +++ b/HOTFIX_v4.1.24.md @@ -0,0 +1,194 @@ +# 🔥 HOTFIX v4.1.24 - ARVE ПОЛЯ НЕ СОХРАНЯЛИСЬ + +**Дата**: 2026-01-15 +**Версия**: v4.1.24 FINAL +**Приоритет**: CRITICAL 🔥 + +--- + +## 🚨 **КРИТИЧНАЯ ПРОБЛЕМА** + +### **Симптомы:** +- ❌ Бухгалтер не может добавить номер счёта (arve_makstud) +- ❌ Не работает галочка "Arve makstud" (arve_checked) +- ❌ При создании/редактировании записей эти поля просто не сохраняются + +### **Причина:** +**Backend не включал поля `arve_checked` и `arve_makstud` в SQL запросы!** + +Поля были в форме, отправлялись с фронтенда, но backend **игнорировал их полностью**. + +--- + +## 🔧 **РЕШЕНИЕ** + +### **Файл:** `src/index.tsx` + +#### **1. POST /api/records - добавление записи** + +**Было (строка 196-207):** +```typescript +const result = await c.env.DB.prepare(` + INSERT INTO production_records ( + month, year, client_name, type, offer_number, work_number, + quantity, color, notes, problems, installer, price, + created_by, updated_by + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +`).bind( + data.month, data.year, data.client_name, data.type || null, + data.offer_number, data.work_number, quantity, data.color || null, + data.notes || null, data.problems || null, data.installer || null, + price, userId, userId +).run() +``` + +**Стало:** +```typescript +const arveChecked = data.arve_checked ? parseInt(data.arve_checked, 10) : 0 + +const result = await c.env.DB.prepare(` + INSERT INTO production_records ( + month, year, client_name, type, offer_number, work_number, + quantity, color, notes, problems, installer, price, + arve_checked, arve_makstud, + created_by, updated_by + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) +`).bind( + data.month, data.year, data.client_name, data.type || null, + data.offer_number, data.work_number, quantity, data.color || null, + data.notes || null, data.problems || null, data.installer || null, + price, arveChecked, data.arve_makstud || null, + userId, userId +).run() +``` + +#### **2. PUT /api/records/:id - обновление записи** + +**Было (строка 238-248):** +```typescript +await c.env.DB.prepare(` + UPDATE production_records + SET client_name = ?, type = ?, offer_number = ?, work_number = ?, + quantity = ?, color = ?, notes = ?, problems = ?, installer = ?, price = ?, + updated_by = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ? AND deleted_at IS NULL +`).bind( + data.client_name, data.type || null, data.offer_number, data.work_number, + quantity, data.color || null, data.notes || null, data.problems || null, + data.installer || null, price, userId, id +).run() +``` + +**Стало:** +```typescript +const arveChecked = data.arve_checked ? parseInt(data.arve_checked, 10) : 0 + +await c.env.DB.prepare(` + UPDATE production_records + SET client_name = ?, type = ?, offer_number = ?, work_number = ?, + quantity = ?, color = ?, notes = ?, problems = ?, installer = ?, price = ?, + arve_checked = ?, arve_makstud = ?, + updated_by = ?, updated_at = CURRENT_TIMESTAMP + WHERE id = ? AND deleted_at IS NULL +`).bind( + data.client_name, data.type || null, data.offer_number, data.work_number, + quantity, data.color || null, data.notes || null, data.problems || null, + data.installer || null, price, arveChecked, data.arve_makstud || null, + userId, id +).run() +``` + +--- + +## 🧪 **ТЕСТИРОВАНИЕ** + +### **Test 1: Создать запись с arve полями ✅** + +```bash +POST /api/records +{ + "arve_checked": 1, + "arve_makstud": "INV-2025-001" +} + +Результат: +✅ id: 56 +✅ arve_checked: 1 +✅ arve_makstud: INV-2025-001 +``` + +### **Test 2: Обновить arve поля ✅** + +```bash +PUT /api/records/56 +{ + "arve_checked": 0, + "arve_makstud": "INV-2025-002" +} + +Результат: +✅ arve_checked: 0 → обновилось +✅ arve_makstud: INV-2025-002 → обновилось +``` + +--- + +## 📦 **ФАЙЛЫ** + +**Изменённые файлы:** +- `src/index.tsx` - endpoint POST /api/records (строки 196-207) +- `src/index.tsx` - endpoint PUT /api/records/:id (строки 238-248) + +**Версия:** +- `public/original.html` - v4.1.24 + +--- + +## 🚀 **РАЗВЁРТЫВАНИЕ** + +### **ARM Synology:** + +```bash +# 1. Остановить контейнер +sudo docker-compose down + +# 2. Распаковать новый архив +unzip aknaproff_production_v4.1.24_ARM_FINAL.zip + +# 3. Запустить с пересборкой +cd backend +sudo docker-compose up -d --build + +# 4. Проверить +# - Создать новый ряд +# - Ввести номер счёта в "Arve Nr" +# - Поставить галочку "Arve makstud" +# - Сохранить +# - ✅ Поля должны сохраниться! +``` + +--- + +## ✅ **РЕЗУЛЬТАТ** + +- ✅ arve_checked сохраняется при создании +- ✅ arve_makstud сохраняется при создании +- ✅ arve_checked обновляется при редактировании +- ✅ arve_makstud обновляется при редактировании +- ✅ Бухгалтер может работать с полями счёта! + +--- + +## 📊 **ИСТОРИЯ ВЕРСИЙ** + +| Версия | Изменения | +|--------|-----------| +| v4.1.23 | Цена с запятой + проверка дат | +| **v4.1.24** | **ИСПРАВЛЕНЫ ПОЛЯ СЧЁТА (arve)** 🔥 | + +--- + +**Статус**: ✅ ГОТОВО +**Тестирование**: ✅ ПРОЙДЕНО +**Критичность**: 🔥 ВЫСОКАЯ +**Развёртывание**: СРОЧНО РЕКОМЕНДУЕТСЯ diff --git a/backup.sh b/backup.sh new file mode 100755 index 0000000..9bb298f --- /dev/null +++ b/backup.sh @@ -0,0 +1,80 @@ +#!/bin/bash + +# AKNAPROFF Tootmine Database Backup Script +# Создаёт бэкапы БД с timestamp + +set -e # Exit on error + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Настройки +DATE=$(date +%Y%m%d_%H%M%S) +BACKUP_DIR="./backups" +DEV_DB_PATH=".wrangler/state/v3/d1/webapp-production.sqlite" +PROD_DB_PATH="data/db/webapp-production.sqlite" + +# Создать папку для бэкапов +mkdir -p "$BACKUP_DIR" + +echo "🗄️ AKNAPROFF Database Backup" +echo "==============================" +echo "" + +# Проверить и бэкапить Development БД +if [ -f "$DEV_DB_PATH" ]; then + DEV_BACKUP="$BACKUP_DIR/webapp-dev-$DATE.sqlite" + cp "$DEV_DB_PATH" "$DEV_BACKUP" + + # Размер файла + SIZE=$(du -h "$DEV_BACKUP" | cut -f1) + + echo -e "${GREEN}✅ Development DB backed up${NC}" + echo " File: $DEV_BACKUP" + echo " Size: $SIZE" + echo "" +else + echo -e "${YELLOW}⚠️ Development DB not found${NC}" + echo " Path: $DEV_DB_PATH" + echo "" +fi + +# Проверить и бэкапить Production БД +if [ -f "$PROD_DB_PATH" ]; then + PROD_BACKUP="$BACKUP_DIR/webapp-prod-$DATE.sqlite" + cp "$PROD_DB_PATH" "$PROD_BACKUP" + + # Размер файла + SIZE=$(du -h "$PROD_BACKUP" | cut -f1) + + echo -e "${GREEN}✅ Production DB backed up${NC}" + echo " File: $PROD_BACKUP" + echo " Size: $SIZE" + echo "" +else + echo -e "${YELLOW}⚠️ Production DB not found${NC}" + echo " Path: $PROD_DB_PATH" + echo "" +fi + +# Показать все бэкапы +echo "📦 All backups:" +echo "---------------" +ls -lh "$BACKUP_DIR"/*.sqlite 2>/dev/null | awk '{print " " $9 " (" $5 ")"}' + +echo "" +echo "==============================" + +# Подсчитать количество бэкапов +BACKUP_COUNT=$(ls -1 "$BACKUP_DIR"/*.sqlite 2>/dev/null | wc -l) +if [ "$BACKUP_COUNT" -gt 10 ]; then + echo -e "${YELLOW}💡 Tip: You have $BACKUP_COUNT backups${NC}" + echo " Consider cleaning old backups:" + echo " rm $BACKUP_DIR/webapp-*-2024*.sqlite" + echo "" +fi + +echo -e "${GREEN}✅ Backup completed!${NC}" diff --git a/data/v3/d1/miniflare-D1DatabaseObject/2b35d4d42e3c9f6b5ad5b5579a7b1470c66e69f6b33a31e3f5a0095cc6d18656.sqlite b/data/v3/d1/miniflare-D1DatabaseObject/2b35d4d42e3c9f6b5ad5b5579a7b1470c66e69f6b33a31e3f5a0095cc6d18656.sqlite deleted file mode 100755 index 53023ef..0000000 Binary files a/data/v3/d1/miniflare-D1DatabaseObject/2b35d4d42e3c9f6b5ad5b5579a7b1470c66e69f6b33a31e3f5a0095cc6d18656.sqlite and /dev/null differ diff --git a/dist/_worker.js b/dist/_worker.js index 5d93d17..d9aa836 100644 --- a/dist/_worker.js +++ b/dist/_worker.js @@ -1252,30 +1252,32 @@ var je=Object.defineProperty;var Ft=t=>{throw TypeError(t)};var Se=(t,e,r)=>e in LEFT JOIN status_checkboxes sc ON pr.id = sc.record_id WHERE pr.month = ? AND pr.year = ? AND pr.deleted_at IS NULL ORDER BY pr.created_at DESC - `).bind(e,r).all();return t.json(o.results||[])}catch(e){return console.error("Error fetching records:",e),t.json({error:"Failed to fetch records"},500)}});f.post("/api/records",A,async t=>{try{const e=await t.req.json(),r=t.get("userId"),o=e.quantity?parseInt(e.quantity,10):0,s=e.price?parseFloat(e.price):0,a=await t.env.DB.prepare(` + `).bind(e,r).all();return t.json(o.results||[])}catch(e){return console.error("Error fetching records:",e),t.json({error:"Failed to fetch records"},500)}});f.post("/api/records",A,async t=>{try{const e=await t.req.json(),r=t.get("userId"),o=e.quantity?parseInt(e.quantity,10):0,s=e.price?parseFloat(e.price):0,a=e.arve_checked?parseInt(e.arve_checked,10):0,i=await t.env.DB.prepare(` INSERT INTO production_records ( month, year, client_name, type, offer_number, work_number, quantity, color, notes, problems, installer, price, + arve_checked, arve_makstud, created_by, updated_by - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `).bind(e.month,e.year,e.client_name,e.type||null,e.offer_number,e.work_number,o,e.color||null,e.notes||null,e.problems||null,e.installer||null,s,r,r).run(),i=e.material_date&&e.material_date!=="null"?e.material_date:null,l=e.material2_date&&e.material2_date!=="null"?e.material2_date:null,n=e.package_date&&e.package_date!=="null"?e.package_date:null;return await t.env.DB.prepare(` + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `).bind(e.month,e.year,e.client_name,e.type||null,e.offer_number,e.work_number,o,e.color||null,e.notes||null,e.problems||null,e.installer||null,s,a,e.arve_makstud||null,r,r).run(),l=e.material_date&&e.material_date!=="null"?e.material_date:null,n=e.material2_date&&e.material2_date!=="null"?e.material2_date:null,c=e.package_date&&e.package_date!=="null"?e.package_date:null;return await t.env.DB.prepare(` INSERT INTO status_checkboxes ( record_id, material_date, material2_date, package_date ) VALUES (?, ?, ?, ?) - `).bind(a.meta.last_row_id,i,l,n).run(),t.json({success:!0,id:a.meta.last_row_id})}catch(e){return console.error("Error creating record:",e),t.json({error:"Failed to create record"},500)}});f.put("/api/records/:id",A,async t=>{try{const e=t.req.param("id"),r=await t.req.json(),o=t.get("userId"),s=r.quantity?parseInt(r.quantity,10):0,a=r.price?parseFloat(r.price):0;if(await t.env.DB.prepare(` + `).bind(i.meta.last_row_id,l,n,c).run(),t.json({success:!0,id:i.meta.last_row_id})}catch(e){return console.error("Error creating record:",e),t.json({error:"Failed to create record"},500)}});f.put("/api/records/:id",A,async t=>{try{const e=t.req.param("id"),r=await t.req.json(),o=t.get("userId"),s=r.quantity?parseInt(r.quantity,10):0,a=r.price?parseFloat(r.price):0,i=r.arve_checked?parseInt(r.arve_checked,10):0;if(await t.env.DB.prepare(` UPDATE production_records SET client_name = ?, type = ?, offer_number = ?, work_number = ?, quantity = ?, color = ?, notes = ?, problems = ?, installer = ?, price = ?, + arve_checked = ?, arve_makstud = ?, updated_by = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND deleted_at IS NULL - `).bind(r.client_name,r.type||null,r.offer_number,r.work_number,s,r.color||null,r.notes||null,r.problems||null,r.installer||null,a,o,e).run(),r.material_date!==void 0||r.material2_date!==void 0||r.package_date!==void 0){const i=r.material_date&&r.material_date!=="null"?r.material_date:null,l=r.material2_date&&r.material2_date!=="null"?r.material2_date:null,n=r.package_date&&r.package_date!=="null"?r.package_date:null;await t.env.DB.prepare(` + `).bind(r.client_name,r.type||null,r.offer_number,r.work_number,s,r.color||null,r.notes||null,r.problems||null,r.installer||null,a,i,r.arve_makstud||null,o,e).run(),r.material_date!==void 0||r.material2_date!==void 0||r.package_date!==void 0){const l=r.material_date&&r.material_date!=="null"?r.material_date:null,n=r.material2_date&&r.material2_date!=="null"?r.material2_date:null,c=r.package_date&&r.package_date!=="null"?r.package_date:null;await t.env.DB.prepare(` UPDATE status_checkboxes SET material_date = ?, material2_date = ?, package_date = ?, updated_at = CURRENT_TIMESTAMP WHERE record_id = ? - `).bind(i,l,n,e).run()}return t.json({success:!0})}catch(e){return console.error("Error updating record:",e),t.json({error:"Failed to update record"},500)}});f.get("/api/records/:id",A,async t=>{try{const e=t.req.param("id"),r=await t.env.DB.prepare(` + `).bind(l,n,c,e).run()}return t.json({success:!0})}catch(e){return console.error("Error updating record:",e),t.json({error:"Failed to update record"},500)}});f.get("/api/records/:id",A,async t=>{try{const e=t.req.param("id"),r=await t.env.DB.prepare(` SELECT * FROM production_records WHERE id = ? AND deleted_at IS NULL `).bind(e).first();return r?t.json(r):t.json({error:"Record not found"},404)}catch(e){return console.error("Error fetching record:",e),t.json({error:"Failed to fetch record"},500)}});f.delete("/api/records/:id",A,async t=>{try{const e=t.req.param("id"),r=t.get("userId");return console.log("[DELETE] Deleting record:",e,"by user:",r),await t.env.DB.prepare(` UPDATE production_records diff --git a/dist/original.html b/dist/original.html index f90c94a..72f82fc 100644 --- a/dist/original.html +++ b/dist/original.html @@ -1225,7 +1225,7 @@ - + \ No newline at end of file diff --git a/docker-compose-simple.yml b/docker-compose-simple.yml deleted file mode 100644 index 2f56f29..0000000 --- a/docker-compose-simple.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: "3.8" - -services: - aknaproff-backend: - build: - context: . - dockerfile: Dockerfile - container_name: aknaproff-backend - ports: - - "8180:3000" - environment: - PORT: 3000 - D1_BINDING: aknaproff-db - PERSIST_PATH: /data - SEED_DATA: "false" - SKIP_MIGRATIONS: "true" - WRANGLER_SEND_METRICS: "false" - volumes: - - ./data:/data - restart: unless-stopped diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..5fa6d7a --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,42 @@ +version: '3.8' + +services: + webapp: + build: + context: . + dockerfile: Dockerfile + container_name: aknaproff-webapp-prod + + # Монтировать только папку БД локально + volumes: + # Локальное хранилище БД + - ./data/db:/app/.wrangler/state/v3/d1 + # Логи (опционально) + - ./data/logs:/app/logs + + # Переменные окружения + environment: + - NODE_ENV=production + - PORT=3000 + + # Открыть порт 3000 + ports: + - "3000:3000" + + # Перезапуск при падении + restart: unless-stopped + + # Лимиты ресурсов + deploy: + resources: + limits: + cpus: '1' + memory: 512M + reservations: + cpus: '0.5' + memory: 256M + +# Сеть +networks: + default: + name: aknaproff-prod-network diff --git a/docker-compose.yml b/docker-compose.yml old mode 100755 new mode 100644 index d3093f3..20a167c --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,55 @@ -version: "3.9" +version: '3.8' services: - aknaproff-backend: - build: - context: . - dockerfile: Dockerfile - container_name: aknaproff-backend - ports: - - "8180:3000" + webapp: + image: node:20-alpine + container_name: aknaproff-webapp + working_dir: /app + + # Bind mount - все файлы проекта включая БД volumes: - - ./data:/data + # Весь проект монтируется в /app + - ./:/app + # node_modules остаются в контейнере для производительности + - /app/node_modules + + # Переменные окружения + environment: + - NODE_ENV=development + - PORT=3000 + # Cloudflare Workers local mode + - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:-} + + # Открыть порт 3000 + ports: + - "3000:3000" + + # Команда запуска + command: > + sh -c " + echo '🚀 Starting AKNAPROFF Tootmine...' && + echo '📦 Installing dependencies...' && + npm install && + echo '🗄️ Setting up local database...' && + npm run db:reset && + echo '🔨 Building project...' && + npm run build && + echo '✅ Starting development server...' && + npx wrangler pages dev dist --d1=webapp-production --local --ip 0.0.0.0 --port 3000 + " + + # Перезапуск при падении restart: unless-stopped + + # Health check + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +# Сеть (необязательно, но полезно для будущего расширения) +networks: + default: + name: aknaproff-network diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh deleted file mode 100755 index f3997c9..0000000 --- a/docker-entrypoint.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash -set -euo pipefail - -PORT="${PORT:-3000}" -D1_BINDING="${D1_BINDING:-aknaproff-db}" -PERSIST_PATH="${PERSIST_PATH:-/data}" -SEED_DATA="${SEED_DATA:-false}" -SEED_SENTINEL="${PERSIST_PATH}/.seeded" -SKIP_MIGRATIONS="${SKIP_MIGRATIONS:-true}" # ⚠️ По умолчанию пропускаем миграции! - -mkdir -p "${PERSIST_PATH}" -export WRANGLER_SEND_METRICS="${WRANGLER_SEND_METRICS:-false}" - -apply_migrations() { - if [[ "${SKIP_MIGRATIONS,,}" == "true" ]]; then - echo "[entrypoint] Skipping migrations (SKIP_MIGRATIONS=true)" - echo "[entrypoint] Using existing database from ${PERSIST_PATH}" - return - fi - - echo "[entrypoint] Applying D1 migrations (binding: ${D1_BINDING}, persist: ${PERSIST_PATH})" - npx wrangler d1 migrations apply "${D1_BINDING}" \ - --local \ - --persist-to "${PERSIST_PATH}" -} - -maybe_seed_data() { - if [[ "${SEED_DATA,,}" != "true" ]]; then - echo "[entrypoint] Seed step disabled (set SEED_DATA=true to enable)" - return - fi - - if [[ -f "${SEED_SENTINEL}" ]]; then - echo "[entrypoint] Seed data already applied (skipping)" - return - fi - - echo "[entrypoint] Seeding local database from seed.sql" - if npx wrangler d1 execute "${D1_BINDING}" \ - --local \ - --persist-to "${PERSIST_PATH}" \ - --file ./seed.sql; then - touch "${SEED_SENTINEL}" - else - echo "[entrypoint] Seed step failed but container will continue" >&2 - fi -} - -start_server() { - echo "[entrypoint] Starting Wrangler dev server on port ${PORT}" - exec npx wrangler pages dev dist \ - --local \ - --d1="${D1_BINDING}" \ - --persist-to "${PERSIST_PATH}" \ - --ip 0.0.0.0 \ - --port "${PORT}" -} - -apply_migrations -maybe_seed_data -start_server diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs new file mode 100644 index 0000000..03df9ac --- /dev/null +++ b/ecosystem.config.cjs @@ -0,0 +1,19 @@ +module.exports = { + apps: [ + { + name: 'webapp', + script: 'npx', + args: 'wrangler pages dev dist --d1=webapp-production --local --ip 0.0.0.0 --port 3000', + env: { + NODE_ENV: 'development', + PORT: 3000 + }, + watch: false, + instances: 1, + exec_mode: 'fork', + autorestart: true, + max_restarts: 5, + min_uptime: '10s' + } + ] +} diff --git a/fix-docker.sh b/fix-docker.sh deleted file mode 100755 index 0231bcd..0000000 --- a/fix-docker.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -# Quick fix script for Synology Docker "lease not found" error - -echo "=== AKNAPROFF Docker Fix Script ===" -echo "" - -# Stop and remove old containers -echo "1. Stopping and removing old containers..." -sudo docker-compose down -v 2>/dev/null || true - -# Prune containers -echo "2. Cleaning orphan containers..." -sudo docker container prune -f - -# Prune build cache -echo "3. Cleaning build cache..." -sudo docker builder prune -f - -# Remove old image if exists -echo "4. Removing old image..." -sudo docker rmi tootmine-aknaproff-backend:latest 2>/dev/null || true -sudo docker rmi aknaproff-backend:latest 2>/dev/null || true - -# System prune -echo "5. System cleanup..." -sudo docker system prune -f - -echo "" -echo "=== Cleanup complete ===" -echo "" -echo "Choose build method:" -echo "1) Use docker-compose.yml (with platform support)" -echo "2) Use docker-compose-simple.yml (without platform - RECOMMENDED for Synology)" -echo "" -read -p "Enter choice [1 or 2]: " choice - -case $choice in - 1) - echo "Building with docker-compose.yml..." - sudo docker-compose build --no-cache - sudo docker-compose up -d --force-recreate - ;; - 2) - echo "Building with docker-compose-simple.yml..." - sudo docker-compose -f docker-compose-simple.yml build --no-cache - sudo docker-compose -f docker-compose-simple.yml up -d --force-recreate - ;; - *) - echo "Invalid choice. Please run script again." - exit 1 - ;; -esac - -echo "" -echo "=== Checking status ===" -sudo docker ps | grep aknaproff - -echo "" -echo "=== Checking logs (last 20 lines) ===" -sudo docker logs aknaproff-backend --tail=20 - -echo "" -echo "=== Testing API ===" -sleep 3 -curl -s http://localhost:8180/api/years | head -20 - -echo "" -echo "=== Done ===" -echo "If container is running, access at: http://synology-ip:8180" diff --git a/public/original.html b/public/original.html index f90c94a..72f82fc 100644 --- a/public/original.html +++ b/public/original.html @@ -1225,7 +1225,7 @@ - + \ No newline at end of file diff --git a/seed.sql b/seed.sql new file mode 100644 index 0000000..e1ac233 --- /dev/null +++ b/seed.sql @@ -0,0 +1,57 @@ +-- Insert demo users +-- Passwords: +-- demo123: d3ad9315b7be5dd53b31a273b3b3aba5defe700808305aa16a3062b76658a791 +-- tootmine: a1026b7bd143f7190248bc79901e9a357a408e208f2d8e4d38fccf184754f35f +-- Users: +-- kasutaja (tootmine): regular user - can edit problems only +-- aknaproff (demo123): admin - full access +-- admin (demo123): super admin - full access (same as admin) +INSERT OR IGNORE INTO users (username, password_hash, full_name, role) VALUES + ('kasutaja', 'a1026b7bd143f7190248bc79901e9a357a408e208f2d8e4d38fccf184754f35f', 'Kasutaja', 'user'), + ('aknaproff', 'd3ad9315b7be5dd53b31a273b3b3aba5defe700808305aa16a3062b76658a791', 'AKNAPROFF', 'admin'), + ('admin', 'd3ad9315b7be5dd53b31a273b3b3aba5defe700808305aa16a3062b76658a791', 'Administrator', 'admin'); + +-- Insert test production records for January 2025 +INSERT OR IGNORE INTO production_records (id, month, year, client_name, offer_number, work_number, quantity, price, installer) VALUES + (1, 1, 2025, 'AS Okna Service', 'P-2025-001', 'T-2025-001', 12, 2500.00, 'Jüri Tamm'), + (2, 1, 2025, 'OÜ Aken ja Uks', 'P-2025-002', 'T-2025-002', 8, 1800.00, 'Mari Lepik'), + (3, 1, 2025, 'Kodutehnika OÜ', 'P-2025-003', 'T-2025-003', 15, 3200.00, 'Peeter Kask'), + (4, 1, 2025, 'Ehitus ja Remont AS', 'P-2025-004', 'T-2025-004', 6, 1200.00, 'Jüri Tamm'), + (5, 1, 2025, 'Akende Maailm OÜ', 'P-2025-005', 'T-2025-005', 20, 4500.00, 'Mari Lepik'); + +-- Insert test production records for December 2024 +INSERT OR IGNORE INTO production_records (id, month, year, client_name, offer_number, work_number, quantity, price, installer) VALUES + (6, 12, 2024, 'Vana Klient OÜ', 'P-2024-099', 'T-2024-099', 10, 2200.00, 'Peeter Kask'), + (7, 12, 2024, 'Teine Ettevõte AS', 'P-2024-100', 'T-2024-100', 5, 1000.00, 'Mari Lepik'); + +-- Insert status checkboxes for the test records +INSERT OR IGNORE INTO status_checkboxes ( + record_id, material_date, material2_date, package_date, worksheets_date, worksheets_confirmed, + cutting_date, glazing_date, ready_date, issued_date +) VALUES + -- Record 1: All stages completed + (1, '2025-01-08', '2025-11-11', '2025-01-09', '2025-11-26', 1, '2025-01-10', '2025-01-12', '2025-01-14', '2025-01-15'), + + -- Record 2: Partial completion with worksheets error + (2, '2025-01-07', NULL, '2025-01-07', NULL, 0, '2025-01-08', '2025-01-10', NULL, NULL), + + -- Record 3: Early stage with material confirmed + (3, '2025-01-05', '2025-01-06', NULL, NULL, 0, NULL, NULL, NULL, NULL), + + -- Record 4: Mid stage + (4, '2025-01-06', '2025-01-07', '2025-01-08', '2025-01-09', 1, '2025-01-10', NULL, NULL, NULL), + + -- Record 5: Just started + (5, '2025-01-07', NULL, NULL, NULL, 0, NULL, NULL, NULL, NULL), + + -- Record 6: December - completed + (6, '2024-12-10', '2024-12-11', '2024-12-12', '2024-12-13', 1, '2024-12-14', '2024-12-15', '2024-12-16', '2024-12-17'), + + -- Record 7: December - partial + (7, '2024-12-08', NULL, '2024-12-09', NULL, 0, '2024-12-10', NULL, NULL, NULL); + +-- Update record 2 to have worksheets error flag set +UPDATE status_checkboxes SET worksheets_error = 1 WHERE record_id = 2; + +-- Update record 2 to have glazing error flag set +UPDATE status_checkboxes SET glazing_error = 1 WHERE record_id = 2; diff --git a/src/index.tsx b/src/index.tsx index 98b57b3..94b654c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -193,17 +193,21 @@ app.post('/api/records', optionalAuthMiddleware, async (c) => { const quantity = data.quantity ? parseInt(data.quantity, 10) : 0 const price = data.price ? parseFloat(data.price) : 0 + const arveChecked = data.arve_checked ? parseInt(data.arve_checked, 10) : 0 + const result = await c.env.DB.prepare(` INSERT INTO production_records ( month, year, client_name, type, offer_number, work_number, quantity, color, notes, problems, installer, price, + arve_checked, arve_makstud, created_by, updated_by - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `).bind( data.month, data.year, data.client_name, data.type || null, data.offer_number, data.work_number, quantity, data.color || null, data.notes || null, data.problems || null, data.installer || null, - price, userId, userId + price, arveChecked, data.arve_makstud || null, + userId, userId ).run() // Create status checkboxes entry with dates if provided @@ -235,16 +239,20 @@ app.put('/api/records/:id', optionalAuthMiddleware, async (c) => { const quantity = data.quantity ? parseInt(data.quantity, 10) : 0 const price = data.price ? parseFloat(data.price) : 0 + const arveChecked = data.arve_checked ? parseInt(data.arve_checked, 10) : 0 + await c.env.DB.prepare(` UPDATE production_records SET client_name = ?, type = ?, offer_number = ?, work_number = ?, quantity = ?, color = ?, notes = ?, problems = ?, installer = ?, price = ?, + arve_checked = ?, arve_makstud = ?, updated_by = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? AND deleted_at IS NULL `).bind( data.client_name, data.type || null, data.offer_number, data.work_number, quantity, data.color || null, data.notes || null, data.problems || null, - data.installer || null, price, userId, id + data.installer || null, price, arveChecked, data.arve_makstud || null, + userId, id ).run() // Update status_checkboxes dates if provided diff --git a/test_browser.js b/test_browser.js new file mode 100644 index 0000000..886a133 --- /dev/null +++ b/test_browser.js @@ -0,0 +1,35 @@ +const axios = require('axios'); + +async function testPage() { + try { + // 1. Get the main page + const response = await axios.get('http://localhost:3000'); + console.log('✅ Page loads:', response.status === 200); + + // 2. Check if app.js exists + const appJsResponse = await axios.get('http://localhost:3000/static/app.js'); + console.log('✅ app.js loads:', appJsResponse.status === 200); + console.log('📦 app.js size:', appJsResponse.data.length, 'bytes'); + + // 3. Check if DOMContentLoaded exists in app.js + const hasDOMContentLoaded = appJsResponse.data.includes('DOMContentLoaded'); + console.log('✅ DOMContentLoaded found:', hasDOMContentLoaded); + + // 4. Check if loadRecords exists + const hasLoadRecords = appJsResponse.data.includes('function loadRecords'); + console.log('✅ loadRecords found:', hasLoadRecords); + + // 5. Check if toggleDate exists + const hasToggleDate = appJsResponse.data.includes('function toggleDate'); + console.log('✅ toggleDate found:', hasToggleDate); + + // 6. Check HTML for tbody + const hasTbody = response.data.includes('