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:
Deploy Bot
2026-01-16 11:36:00 +02:00
parent 4fe9b0fdc9
commit 8b36ea16ef
33 changed files with 705 additions and 2985 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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
View 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
View 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 (желтый с ). БД не затронута.

View File

@@ -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
```
---
**ПРОБЛЕМА ИСПРАВЛЕНА! Добавление записей теперь работает корректно.**

View File

@@ -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
```
---
**ПРОБЛЕМА ИСПРАВЛЕНА! Редактирование записей и дат теперь работает корректно.**

View File

@@ -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 ✅

View File

@@ -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 ✅

View File

@@ -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

View File

@@ -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

View File

@@ -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 для отслеживания изменений
---
**Статус**: ✅ ГОТОВО
**Тестирование**: ✅ ПРОЙДЕНО
**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ

View File

@@ -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** | **Исправлена форма настроек (смена пароля)** |
---
**Статус**: ✅ ГОТОВО
**Тестирование**: ✅ ПРОЙДЕНО
**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ

View File

@@ -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 остался для справки
---
**Статус**: ✅ ГОТОВО
**Тестирование**: ✅ ПРОЙДЕНО
**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ

View File

@@ -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** | **Исправлено удаление записей** |
---
**Статус**: ✅ ГОТОВО
**Тестирование**: ✅ ПРОЙДЕНО
**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ

View File

@@ -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
View 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
View 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}"

14
dist/_worker.js vendored
View File

@@ -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
View File

@@ -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>

View File

@@ -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
View 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
View 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

View File

@@ -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
View 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'
}
]
}

View File

@@ -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"

View File

@@ -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
View 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;

View File

@@ -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
View 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
View 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"
}
]
}

View File

@@ -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"