Files
watersurf-erp/HISTORY.md

287 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# История изменений ERP WaterSurf
## 2025-02-26 00:15 UTC Экономика заказа клиента: связи и отчёт
**Задача**: Цепочка «Заказ клиента → Поступления денежных средств (несколько) → Заказ поставщику по заказу → Расход на оплату поставщику; плюс прочие расходы по заказу (логистика)». По заказу клиента видеть: сколько поступило, сколько потрачено, маржа, маржинальность, рентабельность.
**Решение**:
- В модель **SupplierOrder** добавлено поле **customer_order** (FK на CustomerOrder, null/blank) — заказ поставщику оформляется по заказу клиента.
- В модель **CashExpense** добавлено поле **customer_order** (FK на CustomerOrder, null/blank) — расходы по заказу клиента без привязки к заказу поставщику (логистика и т.п.).
- В формах заказа поставщику и расхода денежных средств добавлены поля выбора заказа клиента; в шаблоне заказа поставщику выведено поле «Заказ клиента».
- В модель **CustomerOrder** добавлен метод **get_economics()**: сумма поступлений (cash_inflows), расходы через заказы поставщику (CashExpense по supplier_order.customer_order), прямые расходы по заказу (cash_expenses), итого расходов, маржа, маржинальность % (маржа/поступления), рентабельность % (маржа/расходы).
- Добавлена страница **Просмотр заказа клиента** (CustomerOrderDetail): данные заказа, блок «Экономика заказа» (таблица показателей), списки поступлений, заказов поставщику и прочих расходов по заказу с ссылками на создание/редактирование. В списке заказов клиента номер ведёт на эту страницу.
**Изменения**: documents/models.py (customer_order в SupplierOrder и CashExpense, get_economics), миграция 0002_add_customer_order_links, documents/forms.py (поля customer_order), supplier_order_form.html (поле заказ клиента), documents/views.py (CustomerOrderDetail), documents/urls.py, шаблон customer_order_detail.html, customer_order_list.html (ссылка с номера на просмотр).
---
## 2025-02-25 23:55 UTC Дата при открытии формы, без лишней пустой строки при редактировании
**Проблема**: При открытии формы редактирования заказа поле «Дата» не подставлялось из БД; в табличной части вместе с существующими строками отображалась лишняя пустая строка.
**Решение**: Для виджета даты (DateInput с type="date") задан явный формат `format="%Y-%m-%d"`, чтобы значение из БД выводилось в виде, ожидаемом HTML5 (YYYY-MM-DD). Для редактирования заказов используются отдельные formset-классы с `extra=0`: при открытии формы показываются только сохранённые строки; новая строка добавляется по кнопке «+ Добавить строку».
**Изменения**: documents/forms.py (format для всех DateInput; CustomerOrderItemFormSetUpdate и SupplierOrderItemFormSetUpdate с extra=0), documents/views.py (импорт и использование *FormSetUpdate в Update-представлениях заказов).
---
## 2025-02-25 23:45 UTC Сохранение заказа: дата и табличная часть
**Проблема**: При сохранении заказа клиента не сохранялись поле «Дата» и табличная часть (товары).
**Причина**: В JavaScript переиндексация строк и клонирование использовали неверный формат имён полей formset: префикс в Django — `items` (related_name), имена имеют вид `items-0-product`, `items-1-price`. В коде искали `items0`, `items1` (без дефиса), из‑за чего переиндексация не срабатывала, клоны получали те же имена `items-0-*`, при отправке дубликаты перезаписывали друг друга и formset обрабатывал только одну строку. При ошибке валидации formset в контекст не передавался обновлённый formset с ошибками.
**Решение**: В reindexRows и при клонировании строк используется корректный формат: `prefix + '-\\d+'` для индекса (например `items-0`), замена на `prefix + '-' + i`; при клонировании — `prefix + '-\\d+-'` для совпадения с суффиксом поля. При невалидном formset в контекст передаётся `formset=formset`, чтобы на форме отображались ошибки.
**Изменения**: order_form.html, supplier_order_form.html (регулярные выражения в reindexRows и в обработчике «Добавить строку»), documents/views.py (передача formset в get_context_data при ошибке валидации для заказов клиента и поставщику).
---
## 2025-02-25 23:30 UTC Поле Цена: разделители числа, без стрелок в Цена и Количество
**Проблема**: В поле «Цена» не было разделителей разрядов; стрелки вверх/вниз в полях «Цена» и «Количество» мешали.
**Решение**: Поле «Цена» выводится как текст (TextInput с inputmode="decimal"): при потере фокуса значение форматируется с неразрывными пробелами и запятой как десятичный разделитель; при фокусе показывается «сырое» число для редактирования; при загрузке страницы существующие цены форматируются. Перед отправкой формы пробелы и запятая по-прежнему снимаются. У полей «Количество» (type="number») скрыты спиннеры через CSS (-moz-appearance: textfield и ::-webkit-inner-spin-button).
**Изменения**: documents/forms.py (виджет price — TextInput), order_form.html и supplier_order_form.html (focusin/focusout для форматирования цены, начальное форматирование), theme-compact.css (скрытие спиннеров у .ws-col-qty input[type="number"]).
---
## 2025-02-25 23:15 UTC Удаление строки табличной части по кнопке-крестику
**Проблема**: Удаление строки происходило при сохранении документа (чекбокс «Удалить»), что было неочевидно и неудобно.
**Решение**: В формах заказа клиента и заказа поставщику чекбокс и подпись «Удалить» заменены на кнопку в виде красного крестика (×). По нажатию строка сразу удаляется из DOM, индексы полей formset пересчитываются (reindexRows), обновляется TOTAL_FORMS и пересчёт сумм по строкам. При добавлении новой строки после вставки клона также вызывается reindexRows().
**Изменения**: order_form.html (кнопка .ws-btn-remove-row, reindexRows, делегированный click на tbody), supplier_order_form.html (аналогично), theme-compact.css (стили .ws-btn-remove-row — красный крестик, hover; удалены стили .ws-delete-row-label).
---
## 2025-02-25 22:50 UTC Добавление строки: одна строка за клик, понятное удаление
**Проблема**: По кнопке «Добавить строку» добавлялось сразу две строки; было неочевидно, как удалить лишнюю.
**Причина**: В order_form.html блок `{% block extra_js %}` был вложен в `{% block content %}`, из‑за чего скрипт попадал на страницу дважды и на кнопку вешались два обработчика.
**Решение**: Блок контента формы закрыт до скрипта: сначала `{% endblock %}`, затем `{% block extra_js %}` со скриптом — скрипт подключается один раз, добавляется одна строка за клик. Для удаления строки: у чекбокса в колонке «Удалить» добавлена подпись «Удалить» (label), при клонировании строки у новой подписи обновляется атрибут `for` под новый id чекбокса. Стили для подписи: cursor pointer, мелкий текст.
**Изменения**: order_form.html (структура блоков, label «Удалить», обновление for у клона), supplier_order_form.html (label «Удалить», обновление for у клона), theme-compact.css (стили .ws-delete-row-label).
---
## 2025-02-25 22:45 UTC Поле «Цена»: ввод с клавиатуры сохраняется
**Проблема**: В табличной части заказа в поле «Цена» сохранялось только изменение стрелками вверх/вниз, введённое с клавиатуры значение не сохранялось.
**Причина**: При blur в поле подставлялось отформатированное значение с пробелами («1 851 635.00»). Для `input type="number"` такие значения недопустимы, браузер сбрасывал поле.
**Решение**: Форматирование с пробелами убрано из самого поля «Цена». Разделитель разрядов остаётся только в ячейке «Стоимость» (обновляется по input). Обработчики blur/focus для поля цены и начальное форматирование при загрузке удалены в order_form.html и supplier_order_form.html. Перед отправкой формы по-прежнему выполняется удаление пробелов из значений цены.
**Изменения**: documents/order_form.html, documents/supplier_order_form.html.
---
## 2025-02-25 22:40 UTC Убрана горизонтальная полоса прокрутки в таблице товаров
**Проблема**: В табличной части заказа по-прежнему отображалась горизонтальная полоса прокрутки.
**Решение**: Для обёртки таблицы задано `overflow-x: hidden`, у колонок убраны `min-width`, для ячеек включены `overflow: hidden` и `box-sizing: border-box`. Ширины колонок заданы в сумме 100% (45+14+12+10+14+5), чтобы таблица не выходила за контейнер.
**Изменения**: theme-compact.css.
---
## 2025-02-25 22:35 UTC Табличная часть заказа: без скролла, цена/стоимость с разделителем
**Проблема**: В таблице товаров появлялась горизонтальная полоса прокрутки; поле «Товар» слишком широкое; в полях «Цена» и «Стоимость» не было разделителя разрядов.
**Решение**: Включён table-layout: fixed и заданы доли колонок в % (Товар 28%, Цена 14%, Валюта 12%, Количество 10%, Стоимость 14%, Удалить 5%), чтобы таблица помещалась без скролла. Поле «Товар» ограничено по ширине, select с overflow: hidden. Поля «Цена» и «Стоимость»: выравнивание по правому краю, узкое поле под 8-значную сумму. В JS добавлены formatNum/parseNum: разделитель тысяч (неразрывный пробел) и запятая как десятичный разделитель; при blur цена форматируется, при focus и при submit — пробелы снимаются перед отправкой. Стоимость в ячейке выводится с разделителем. Аналогичные правки в форме заказа поставщику.
**Изменения**: order_form.html, supplier_order_form.html (colgroup, классы ячеек, JS форматирование), theme-compact.css (ширины колонок).
---
## 2025-02-25 22:25 UTC Временное отключение авторизации для отладки
**Проблема**: Для быстрой разработки и отладки нужно работать без входа.
**Решение**: Добавлена настройка `REQUIRE_LOGIN` (по умолчанию `false`). При `REQUIRE_LOGIN=false` представления не требуют входа; мидлварь `OptionalAuthMiddleware` подставляет первого активного суперпользователя для анонимных запросов, чтобы меню и поле «Автор» работали. Миксин `config.mixins.LoginRequiredMixin` проверяет `REQUIRE_LOGIN` и при `false` не перенаправляет на логин. Чтобы вернуть авторизацию: в `.env` задать `REQUIRE_LOGIN=true` и перезапустить приложение.
**Изменения**: config/settings.py (REQUIRE_LOGIN, OptionalAuthMiddleware), config/mixins.py, config/middleware.py, documents/views.py, references/views.py, users/views.py (импорт LoginRequiredMixin из config.mixins), .env.example (комментарий про REQUIRE_LOGIN).
---
## 2025-02-25 22:15 UTC Поле «Дата»: размер и кнопка календаря
**Проблема**: Дата не помещалась в поле (обрезка по ширине), кнопка выбора календаря сливалась с тёмным фоном.
**Решение**: Ширина поля даты увеличена до 11.5rem. Для кнопки календаря (::-webkit-calendar-picker-indicator) задан акцентный фон (--ws-accent), белая иконка календаря через SVG data URI, при наведении — --ws-accent-hover.
**Изменения**: theme-compact.css.
---
## 2025-02-25 22:10 UTC Поле «Дата» как ввод даты (type=date)
**Проблема**: Поле даты в документах было текстовым (size=10).
**Решение**: Во всех формах документов (заказ клиента, заказ поставщику, поступление, перемещение, расход) виджет даты заменён на `DateInput(attrs={"type": "date"})` — отображается нативный выбор даты в браузере.
**Изменения**: documents/forms.py (виджеты date во всех пяти формах).
---
## 2025-02-25 22:00 UTC Компактные формы денежных документов (поступление, перемещение, расход)
**Проблема**: Формы поступления, перемещения и расхода денежных средств выводили все поля списком с подписями сверху.
**Решение**: Общий шаблон cash_doc_form.html переведён на компактную раскладку по образцу заказов: первая строка — Дата и Номер (подпись слева от поля, 10 и 15 символов), остальные поля — парами в одну строку в две колонки (подпись слева). Добавлены виджеты даты/номера в CashInflowForm, CashTransferForm, CashExpenseForm.
**Изменения**: documents/forms.py (виджеты date/number в трёх формах), documents/cash_doc_form.html (компактная разметка с ws-form-compact).
---
## 2025-02-25 21:45 UTC Подписи полей слева от полей в одну строку
**Проблема**: Подписи полей в формах заказов были расположены над полями ввода.
**Решение**: В компактных формах подписи выровнены слева от полей в одну строку: группа поля — flex-строка (label + поле), минимальная ширина подписи 6.5rem, сообщения об ошибках переносятся на следующую строку с отступом.
**Изменения**: theme-compact.css (стили .ws-form-group как flex row, выравнивание подписей).
---
## 2025-02-25 21:30 UTC Компактные формы документов (заказы клиента и поставщику)
**Проблема**: Формы создания/редактирования заказов занимали много места: каждое поле с новой строки на всю ширину.
**Решение**:
- Дата и Номер в одной строке: поле «Дата» — 10 символов (size=10), «Номер» — 15 символов (size=15, maxlength=15).
- Вид заказа и Организация в одной строке; Клиент и Автор в одной строке (заказ клиента). Аналогично заказ поставщику: Организация и Поставщик, Валюта и Курс, затем Автор.
- Таблица товаров: колонка «Товар» шире (35%, min 14rem), колонка «Количество» уже (поле ввода 4ch, до двухзначного числа).
- Добавлены виджеты для даты/номера в формах и для количества в formset (size=3, width: 4ch). Новый файл `static/css/theme-compact.css` с раскладкой строк формы (grid) и ширинами колонок таблицы.
**Изменения**: documents/forms.py (CustomerOrderForm, SupplierOrderForm, CustomerOrderItemForm, SupplierOrderItemForm, виджеты), documents/order_form.html и supplier_order_form.html (компактная разметка по полям), base.html (подключение theme-compact.css), новый theme-compact.css.
---
## 2025-02-25 21:00 UTC Принудительное применение темы (кэш, шрифт, кнопки)
**Проблема**: На скриншоте стили не применялись: кнопка бирюзовая вместо синей (#34AFE3), шрифт Geologica не отображался.
**Решение**:
- К ссылке theme.css добавлен параметр ?v=4 для сброса кэша браузера и прокси.
- Для кнопки .btn-ws-primary заданы явные цвета #34AFE3 и #00868F и усилена специфичность селекторов (body .btn.btn-ws-primary).
- На html и body задан font-family Geologica; для потомков — font-family: inherit, чтобы шрифт применялся ко всей странице.
**Изменения**: base.html (theme.css?v=4), theme.css (коммент v4, кнопки по HEX, наследование шрифта).
---
## 2025-02-25 20:45 UTC Логотип убран; дизайн строго по брендбуку
**Проблема**: Логотип в шапке был вставлен некорректно; визуально тема не отличалась от дефолтной — цвета и шрифты не применялись.
**Решение**:
- Логотип из шапки убран, в навбаре снова текст «WaterSurf ERP».
- Файл логотипа удалён из `static/images/`.
- Фон страницы и поверхностей задан только основными цветами бренда: Black #0A121D; границы — Gray 04 #617E92; текст — Gray 01 #F5F9FD, Gray 03 #B1C7D7, Gray 04 #617E92.
- Ключевой акцент взят только из дополнительных цветов: Blue (02) #34AFE3, при наведении Blue (03) #00868F.
- Добавлены переопределения Bootstrap с !important для body, main, кнопок, полей ввода, таблиц и типографики, чтобы гарантированно применялись Geologica и палитра брендбука.
**Изменения**: base.html (удалён img логотипа), theme.css (палитра только из брендбука, акцент из дополнительных, переопределения Bootstrap), удалён app/static/images/watersurf-logo.png.
---
## 2025-02-25 20:15 UTC Стили по корпоративному брендбуку и логотип
**Проблема**: Нужно привести интерфейс в соответствие с корпоративным брендбуком WaterSurf и использовать логотип компании.
**Решение**:
- В `static/css/theme.css` применены цвета брендбука: базовый фон #0A121D, ключевой акцент #0DADBB, градации серого (Gray 0104), дополнительные красный/зелёный для состояний.
- Подключён шрифт Geologica (Google Fonts): Light для основного текста, Medium для подзаголовков, Bold для заголовков; учтён letter-spacing по брендбуку.
- В шапке вместо текстовой надписи выводится логотип WaterSurf из `static/images/watersurf-logo.png`, рядом — подпись «ERP».
- Сохранены тёмная тема, минималистичные карточки, таблицы и формы в едином стиле.
**Изменения**: base.html (подключение Geologica, логотип в навбаре), theme.css (палитра и типографика брендбука), добавлен каталог static/images и файл watersurf-logo.png.
---
## 2025-02-25 19:00 UTC Кнопка «Добавить строку» в табличной части заказов
**Проблема**: В форме заказа клиента (и заказа поставщику) нельзя было добавить более одной строки товаров — formset показывал только одну пустую строку (extra=1).
**Решение**: Под таблицей товаров добавлена кнопка «+ Добавить строку». По нажатию скрипт клонирует последнюю строку, подменяет в ней индексы полей (items-N-…) и значение TOTAL_FORMS в management form, очищает значения и добавляет строку в таблицу. Аналогично реализовано для заказа поставщику.
---
## 2025-02-25 18:45 UTC Тёмная тема и обновлённый UI/UX
**Проблема**: Требовался современный тёмный интерфейс, минималистичный и удобный.
**Решение**:
- Добавлена кастомная тема в `static/css/theme.css`: тёмный фон (оттенки #0d1117#21262d), акцент бирюзовый (#2dd4bf), типографика Inter, переменные для цветов и отступов.
- Обновлён базовый шаблон: навбар с выпадающими меню в стиле темы, контейнер контента с ограничением ширины, сообщения (alerts) в стиле темы.
- Все страницы переведены на карточки (ws-card), таблицы (ws-table) с чередованием и hover, кнопки (btn-ws-primary, btn-ws-secondary, btn-ws-danger), формы с группами полей и явными метками.
- Учтены UX: читаемый контраст, фокус на полях ввода, разделители секций в формах, выравнивание чисел в таблицах, пустые состояния.
**Изменения**: base.html, registration/login.html, home.html, шаблоны references/* и documents/*, новый static/css/theme.css. В Dockerfile добавлен collectstatic при сборке образа.
---
## 2025-02-25 18:15 UTC Ошибка 500 при создании заказа клиента
**Проблема**: При открытии формы создания заказа клиента возникала ошибка 500 (AttributeError: 'NoneType' object has no attribute 'pk').
**Решение**: В `CustomerOrderCreate.get_context_data` при создании нового заказа `self.object` ещё None; обращение к `self.object.pk` вызывало исключение. Условие заменено на `if self.object and self.object.pk`, как в форме заказа поставщику.
---
## 2025-02-25 15:25 UTC Исправление 403 CSRF при входе
**Проблема**: При первой авторизации под администратором по https://erp.gen7x.ru возникала ошибка 403 (ошибка проверки CSRF, запрос отклонён).
**Решение**: Добавлена настройка `CSRF_TRUSTED_ORIGINS` в `config/settings.py`: указаны `https://erp.gen7x.ru`, а также localhost/127.0.0.1 для локальной отладки. При работе за Nginx Django проверяет заголовок Origin/Referer; без доверенного источника запросы с формы логина отклонялись.
**Изменения**: `app/config/settings.py` — список CSRF_TRUSTED_ORIGINS; опциональная переменная `CSRF_TRUSTED_ORIGINS` в `.env.example`.
---
## 2025-02-25 15:10 UTC Администратор и SSH для Git
**Проблема**: Требовалось создать первого администратора системы и настроить SSH для самостоятельного push в Gitea.
**Решение**:
- Создан Django superuser (логин `admin`, пароль и email записаны в `/root/docs/secrets/mysecrets.md`).
- Сгенерирован SSH-ключ для Gitea (`/root/.ssh/gitea_cursor_agent_ed25519`), публичный ключ добавлен в учётную запись cursor-agent через API.
- В `~/.ssh/config` добавлен хост `git.gen7x.ru` (HostName 127.0.0.1, Port 2222, User git, указан ключ).
- Remote репозитория переведён на SSH: `git@git.gen7x.ru:cursor-agent/watersurf-erp.git`, выполнен успешный push ветки main.
**Изменения**:
- README.md: секция «Git и пуш» обновлена с указанием SSH и отсутствия необходимости ввода пароля с сервера.
---
## 2025-02-25 15:00 UTC Начальная структура проекта (MVP)
**Проблема**: необходимо развернуть систему класса ERP для WaterSurf с веб-доступом, авторизацией и хранением данных в БД.
**Решение**:
- Создан проект в `/opt/watersurf-erp`: Django 5, PostgreSQL 16, Docker Compose.
- Реализованы справочники: Валюты, Виды заказов, Клиенты, Организации, Поставщики, Сотрудники, Счета денежных средств, Товары (CRUD через веб и админку).
- Реализованы документы: Заказ клиента, Заказ поставщику (с табличными частями товаров), Поступление/Перемещение/Расход денежных средств (автогенерация номера).
- Связь пользователь → сотрудник (профиль) для автоматической подстановки автора в документах.
- Настроены Nginx (erp.gen7x.ru), добавлен volume в список бэкапов платформы.
**Изменения**:
- Структура приложения: config, references, documents, users; шаблоны, формы, представления; миграции.
- Файлы: README.md, HISTORY.md, .env.example, docker-compose.yml, Dockerfile, manage.sh, requirements.txt.
**Проверка**:
```bash
cd /opt/watersurf-erp
docker compose up -d
docker compose logs -f app
# Открыть https://erp.gen7x.ru/ (после перезагрузки Nginx)
```