v4.1.24: Добавлены поля arve_checked и arve_makstud
- FEAT: Добавлены поля arve_checked (int) и arve_makstud (string) в production_records
- POST /api/records: INSERT с arve_checked, arve_makstud
- PUT /api/records/🆔 UPDATE с arve_checked, arve_makstud
- Docker: docker-compose.prod.yml, ecosystem.config.cjs (PM2)
- wrangler.toml → wrangler.jsonc
- seed.sql: полные тестовые данные
- test_browser.js: E2E тесты
- Удалены старые HOTFIX-файлы (v4.1.11-v4.1.23)
- Удалены data/*.sqlite из репозитория
This commit is contained in:
217
ACCESS_RIGHTS.md
217
ACCESS_RIGHTS.md
@@ -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
|
|
||||||
232
CHANGELOG.md
232
CHANGELOG.md
@@ -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)
|
|
||||||
@@ -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":"..."}`
|
|
||||||
|
|
||||||
---
|
|
||||||
68
Dockerfile
Executable file → Normal file
68
Dockerfile
Executable file → Normal file
@@ -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
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
# Копировать package files
|
||||||
RUN npm install
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Установить зависимости
|
||||||
|
RUN npm ci --only=production
|
||||||
|
|
||||||
|
# Копировать исходники
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Собрать проект
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# ---------- Runtime stage ----------
|
# Stage 2: Runtime
|
||||||
FROM node:20-bookworm-slim
|
FROM node:20-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
# Установить dumb-init для правильной обработки сигналов
|
||||||
WRANGLER_SEND_METRICS=false \
|
RUN apk add --no-cache dumb-init
|
||||||
PORT=3000 \
|
|
||||||
D1_BINDING=aknaproff-db \
|
|
||||||
PERSIST_PATH=/data
|
|
||||||
|
|
||||||
# Copy everything from builder (includes node_modules, dist, migrations, etc.)
|
# Создать пользователя без root
|
||||||
COPY --from=builder /app /app
|
RUN addgroup -g 1001 -S nodejs && \
|
||||||
|
adduser -S nodejs -u 1001
|
||||||
|
|
||||||
# Create data directory
|
# Копировать зависимости из builder
|
||||||
RUN mkdir -p /data
|
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
|
EXPOSE 3000
|
||||||
|
|
||||||
# Persist D1 SQLite data between restarts
|
# Health check
|
||||||
VOLUME ["/data"]
|
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)
|
# Запуск с dumb-init
|
||||||
CMD ["npx", "wrangler", "pages", "dev", "dist", \
|
ENTRYPOINT ["dumb-init", "--"]
|
||||||
"--local", \
|
|
||||||
"--d1=aknaproff-db", \
|
# Команда запуска
|
||||||
"--persist-to=/data", \
|
CMD ["sh", "-c", "npm run db:reset && npx wrangler pages dev dist --d1=webapp-production --local --ip 0.0.0.0 --port 3000"]
|
||||||
"--ip=0.0.0.0", \
|
|
||||||
"--port=3000"]
|
|
||||||
|
|||||||
147
FILES_TO_COPY.txt
Normal file
147
FILES_TO_COPY.txt
Normal file
@@ -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 (желтый с ℹ️). БД не затронута.
|
||||||
@@ -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
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**ПРОБЛЕМА ИСПРАВЛЕНА! Добавление записей теперь работает корректно.** ✅
|
|
||||||
@@ -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
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**ПРОБЛЕМА ИСПРАВЛЕНА! Редактирование записей и дат теперь работает корректно.** ✅
|
|
||||||
@@ -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 ✅
|
|
||||||
@@ -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 ✅
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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 для отслеживания изменений
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Статус**: ✅ ГОТОВО
|
|
||||||
**Тестирование**: ✅ ПРОЙДЕНО
|
|
||||||
**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ
|
|
||||||
@@ -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** | **Исправлена форма настроек (смена пароля)** |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Статус**: ✅ ГОТОВО
|
|
||||||
**Тестирование**: ✅ ПРОЙДЕНО
|
|
||||||
**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ
|
|
||||||
@@ -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 остался для справки
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Статус**: ✅ ГОТОВО
|
|
||||||
**Тестирование**: ✅ ПРОЙДЕНО
|
|
||||||
**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ
|
|
||||||
@@ -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** | **Исправлено удаление записей** |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Статус**: ✅ ГОТОВО
|
|
||||||
**Тестирование**: ✅ ПРОЙДЕНО
|
|
||||||
**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ
|
|
||||||
@@ -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** | **Цена с запятой + проверка сохранения дат** |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Статус**: ✅ ГОТОВО
|
|
||||||
**Тестирование**: ✅ ПРОЙДЕНО
|
|
||||||
**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ
|
|
||||||
194
HOTFIX_v4.1.24.md
Normal file
194
HOTFIX_v4.1.24.md
Normal file
@@ -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)** 🔥 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Статус**: ✅ ГОТОВО
|
||||||
|
**Тестирование**: ✅ ПРОЙДЕНО
|
||||||
|
**Критичность**: 🔥 ВЫСОКАЯ
|
||||||
|
**Развёртывание**: СРОЧНО РЕКОМЕНДУЕТСЯ
|
||||||
80
backup.sh
Executable file
80
backup.sh
Executable file
@@ -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}"
|
||||||
Binary file not shown.
14
dist/_worker.js
vendored
14
dist/_worker.js
vendored
@@ -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
|
LEFT JOIN status_checkboxes sc ON pr.id = sc.record_id
|
||||||
WHERE pr.month = ? AND pr.year = ? AND pr.deleted_at IS NULL
|
WHERE pr.month = ? AND pr.year = ? AND pr.deleted_at IS NULL
|
||||||
ORDER BY pr.created_at DESC
|
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 (
|
INSERT INTO production_records (
|
||||||
month, year, client_name, type, offer_number, work_number,
|
month, year, client_name, type, offer_number, work_number,
|
||||||
quantity, color, notes, problems, installer, price,
|
quantity, color, notes, problems, installer, price,
|
||||||
|
arve_checked, arve_makstud,
|
||||||
created_by, updated_by
|
created_by, updated_by
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) 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(`
|
`).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 (
|
INSERT INTO status_checkboxes (
|
||||||
record_id, material_date, material2_date, package_date
|
record_id, material_date, material2_date, package_date
|
||||||
) VALUES (?, ?, ?, ?)
|
) 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
|
UPDATE production_records
|
||||||
SET client_name = ?, type = ?, offer_number = ?, work_number = ?,
|
SET client_name = ?, type = ?, offer_number = ?, work_number = ?,
|
||||||
quantity = ?, color = ?, notes = ?, problems = ?, installer = ?, price = ?,
|
quantity = ?, color = ?, notes = ?, problems = ?, installer = ?, price = ?,
|
||||||
|
arve_checked = ?, arve_makstud = ?,
|
||||||
updated_by = ?, updated_at = CURRENT_TIMESTAMP
|
updated_by = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ? AND deleted_at IS NULL
|
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
|
UPDATE status_checkboxes
|
||||||
SET material_date = ?,
|
SET material_date = ?,
|
||||||
material2_date = ?,
|
material2_date = ?,
|
||||||
package_date = ?,
|
package_date = ?,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE record_id = ?
|
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
|
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(`
|
`).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
|
UPDATE production_records
|
||||||
|
|||||||
2
dist/original.html
vendored
2
dist/original.html
vendored
@@ -1225,7 +1225,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js"></script>
|
||||||
<script src="/static/app.js?v=4.1.23"></script>
|
<script src="/static/app.js?v=4.1.24"></script>
|
||||||
|
|
||||||
|
|
||||||
</body></html>
|
</body></html>
|
||||||
@@ -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
|
|
||||||
42
docker-compose.prod.yml
Normal file
42
docker-compose.prod.yml
Normal file
@@ -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
|
||||||
60
docker-compose.yml
Executable file → Normal file
60
docker-compose.yml
Executable file → Normal file
@@ -1,13 +1,55 @@
|
|||||||
version: "3.9"
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
aknaproff-backend:
|
webapp:
|
||||||
build:
|
image: node:20-alpine
|
||||||
context: .
|
container_name: aknaproff-webapp
|
||||||
dockerfile: Dockerfile
|
working_dir: /app
|
||||||
container_name: aknaproff-backend
|
|
||||||
ports:
|
# Bind mount - все файлы проекта включая БД
|
||||||
- "8180:3000"
|
|
||||||
volumes:
|
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
|
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
|
||||||
|
|||||||
@@ -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
|
|
||||||
19
ecosystem.config.cjs
Normal file
19
ecosystem.config.cjs
Normal file
@@ -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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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"
|
|
||||||
@@ -1225,7 +1225,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js"></script>
|
||||||
<script src="/static/app.js?v=4.1.23"></script>
|
<script src="/static/app.js?v=4.1.24"></script>
|
||||||
|
|
||||||
|
|
||||||
</body></html>
|
</body></html>
|
||||||
57
seed.sql
Normal file
57
seed.sql
Normal file
@@ -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;
|
||||||
@@ -193,17 +193,21 @@ app.post('/api/records', optionalAuthMiddleware, async (c) => {
|
|||||||
const quantity = data.quantity ? parseInt(data.quantity, 10) : 0
|
const quantity = data.quantity ? parseInt(data.quantity, 10) : 0
|
||||||
const price = data.price ? parseFloat(data.price) : 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(`
|
const result = await c.env.DB.prepare(`
|
||||||
INSERT INTO production_records (
|
INSERT INTO production_records (
|
||||||
month, year, client_name, type, offer_number, work_number,
|
month, year, client_name, type, offer_number, work_number,
|
||||||
quantity, color, notes, problems, installer, price,
|
quantity, color, notes, problems, installer, price,
|
||||||
|
arve_checked, arve_makstud,
|
||||||
created_by, updated_by
|
created_by, updated_by
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
`).bind(
|
`).bind(
|
||||||
data.month, data.year, data.client_name, data.type || null,
|
data.month, data.year, data.client_name, data.type || null,
|
||||||
data.offer_number, data.work_number, quantity, data.color || null,
|
data.offer_number, data.work_number, quantity, data.color || null,
|
||||||
data.notes || null, data.problems || null, data.installer || null,
|
data.notes || null, data.problems || null, data.installer || null,
|
||||||
price, userId, userId
|
price, arveChecked, data.arve_makstud || null,
|
||||||
|
userId, userId
|
||||||
).run()
|
).run()
|
||||||
|
|
||||||
// Create status checkboxes entry with dates if provided
|
// 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 quantity = data.quantity ? parseInt(data.quantity, 10) : 0
|
||||||
const price = data.price ? parseFloat(data.price) : 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(`
|
await c.env.DB.prepare(`
|
||||||
UPDATE production_records
|
UPDATE production_records
|
||||||
SET client_name = ?, type = ?, offer_number = ?, work_number = ?,
|
SET client_name = ?, type = ?, offer_number = ?, work_number = ?,
|
||||||
quantity = ?, color = ?, notes = ?, problems = ?, installer = ?, price = ?,
|
quantity = ?, color = ?, notes = ?, problems = ?, installer = ?, price = ?,
|
||||||
|
arve_checked = ?, arve_makstud = ?,
|
||||||
updated_by = ?, updated_at = CURRENT_TIMESTAMP
|
updated_by = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ? AND deleted_at IS NULL
|
WHERE id = ? AND deleted_at IS NULL
|
||||||
`).bind(
|
`).bind(
|
||||||
data.client_name, data.type || null, data.offer_number, data.work_number,
|
data.client_name, data.type || null, data.offer_number, data.work_number,
|
||||||
quantity, data.color || null, data.notes || null, data.problems || null,
|
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()
|
).run()
|
||||||
|
|
||||||
// Update status_checkboxes dates if provided
|
// Update status_checkboxes dates if provided
|
||||||
|
|||||||
35
test_browser.js
Normal file
35
test_browser.js
Normal file
@@ -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('<tbody id="recordsTable"');
|
||||||
|
console.log('✅ tbody#recordsTable in HTML:', hasTbody);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error:', error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testPage();
|
||||||
16
wrangler.jsonc
Normal file
16
wrangler.jsonc
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"$schema": "node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "webapp",
|
||||||
|
"compatibility_date": "2025-11-28",
|
||||||
|
"compatibility_flags": ["nodejs_compat"],
|
||||||
|
"pages_build_output_dir": "./dist",
|
||||||
|
|
||||||
|
// D1 Database configuration
|
||||||
|
"d1_databases": [
|
||||||
|
{
|
||||||
|
"binding": "DB",
|
||||||
|
"database_name": "webapp-production",
|
||||||
|
"database_id": "local"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
name = "aknaproff-tootmine"
|
|
||||||
compatibility_date = "2025-11-29"
|
|
||||||
pages_build_output_dir = "./dist"
|
|
||||||
compatibility_flags = ["nodejs_compat"]
|
|
||||||
|
|
||||||
[[d1_databases]]
|
|
||||||
binding = "DB"
|
|
||||||
database_name = "aknaproff-db"
|
|
||||||
database_id = "local-aknaproff-db"
|
|
||||||
Reference in New Issue
Block a user