Files
octopus/myproject/orders/templates/orders/status_form.html
Andrey Smakotin dcfb76121d SECURITY: Защита критичных полей системных статусов от редактирования
Заблокировал изменение полей is_positive_end и is_negative_end для
системных статусов заказов, так как эти флаги используются в сигналах
inventory для управления резервированием и списанием товаров со склада.

Что изменено:
- OrderStatusForm: Добавлена блокировка (disabled=True) для полей
  is_positive_end и is_negative_end при редактировании системных статусов
- status_form.html: Заменено информационное предупреждение на красное
  с детальным описанием заблокированных полей и их влияния на систему

Почему это критично:
Эти флаги проверяются в 3 сигналах inventory/signals.py:
1. rollback_sale_on_status_change - откатывает продажи при уходе от 'completed'
2. release_reservations_on_cancellation - освобождает резервы при отмене
3. reserve_stock_on_uncancellation - резервирует при восстановлении заказа

Случайное изменение флагов может привести к:
- Неправильному освобождению резервов товара
- Двойному резервированию
- Блокировке товара навсегда
- Списанию товара для отмененных заказов

Разрешено редактировать для системных статусов: name, label, color, description

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 01:07:15 +03:00

302 lines
15 KiB
HTML
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.
{% extends "base.html" %}
{% load static %}
{% block title %}{% if form.instance.pk %}Редактировать{% else %}Создать{% endif %} статус{% endblock %}
{% block content %}
<div class="container-fluid mt-4">
<div class="row mb-4">
<div class="col-md-8">
<h1>
{% if form.instance.pk %}
Редактировать статус: {{ form.instance.name }}
{% else %}
Создать новый статус
{% endif %}
</h1>
{% if is_system %}
<div class="alert alert-danger mt-2">
<i class="fas fa-exclamation-triangle"></i>
<strong>ВНИМАНИЕ!</strong> Это системный статус, используемый в бизнес-логике системы.
<ul class="mb-0 mt-2">
<li><strong>Код статуса</strong> — заблокирован (используется в коде для проверок)</li>
<li><strong>Положительный/Отрицательный исход</strong> — заблокированы (управляют резервированием и списанием товаров со склада)</li>
<li>Разрешено изменять: название, метку, цвет и описание</li>
</ul>
</div>
{% endif %}
</div>
<div class="col-md-4 text-end">
<a href="{% url 'orders:status_list' %}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Вернуться к статусам
</a>
</div>
</div>
{% if form.non_field_errors %}
<div class="alert alert-danger" role="alert">
<h4 class="alert-heading">Ошибка!</h4>
{% for error in form.non_field_errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
{% endif %}
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-body">
<form method="post" novalidate>
{% csrf_token %}
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.name.id_for_label }}" class="form-label">
{{ form.name.label }}
<span class="text-danger">*</span>
</label>
{{ form.name }}
{% if form.name.errors %}
<div class="invalid-feedback d-block">
{% for error in form.name.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<small class="form-text text-muted">
Например: Выполнен, В процессе, Возврат
</small>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.code.id_for_label }}" class="form-label">
{{ form.code.label }}
<span class="text-danger">*</span>
</label>
{{ form.code }}
{% if form.code.errors %}
<div class="invalid-feedback d-block">
{% for error in form.code.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<small class="form-text text-muted">
{% if form.code.field.help_text %}
{{ form.code.field.help_text|safe }}
{% else %}
Латинские буквы, цифры и подчеркивания
{% endif %}
</small>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.label.id_for_label }}" class="form-label">
{{ form.label.label }}
</label>
{{ form.label }}
{% if form.label.errors %}
<div class="invalid-feedback d-block">
{% for error in form.label.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<small class="form-text text-muted">
Для отображения в интерфейсе (опционально)
</small>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.color.id_for_label }}" class="form-label">
{{ form.color.label }}
</label>
<div class="input-group">
{{ form.color }}
<span class="input-group-text" id="color-preview" style="width: 60px; background-color: #808080;"></span>
</div>
{% if form.color.errors %}
<div class="invalid-feedback d-block">
{% for error in form.color.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
<div class="mb-3">
<label for="{{ form.description.id_for_label }}" class="form-label">
{{ form.description.label }}
</label>
{{ form.description }}
{% if form.description.errors %}
<div class="invalid-feedback d-block">
{% for error in form.description.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<small class="form-text text-muted">
Описание для пользователей (опционально)
</small>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-check mb-3">
{{ form.is_positive_end }}
<label class="form-check-label" for="{{ form.is_positive_end.id_for_label }}">
{{ form.is_positive_end.label }}
</label>
<small class="d-block text-muted mt-1">
Отметьте, если это успешный финальный статус (Выполнен)
</small>
</div>
</div>
<div class="col-md-6">
<div class="form-check mb-3">
{{ form.is_negative_end }}
<label class="form-check-label" for="{{ form.is_negative_end.id_for_label }}">
{{ form.is_negative_end.label }}
</label>
<small class="d-block text-muted mt-1">
Отметьте, если это отрицательный финальный статус (Отменен)
</small>
</div>
</div>
</div>
<div class="d-flex gap-2 mt-4">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i>
{% if form.instance.pk %}
Сохранить изменения
{% else %}
Создать статус
{% endif %}
</button>
<a href="{% url 'orders:status_list' %}" class="btn btn-secondary">
<i class="fas fa-times"></i> Отменить
</a>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header bg-light">
<h6 class="mb-0">Предпросмотр</h6>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Название статуса</label>
<p class="lead">
<span id="preview-name">{{ form.instance.name|default:"Название" }}</span>
</p>
</div>
<div class="mb-3">
<label class="form-label">Код статуса</label>
<code id="preview-code">{{ form.instance.code|default:"код" }}</code>
</div>
<div class="mb-3">
<label class="form-label">Внешний вид</label>
<div style="padding: 10px; border-radius: 4px; background-color: {{ form.instance.color|default:'#808080' }}; color: white; text-align: center;" id="preview-color">
<strong id="preview-color-text">{{ form.instance.name|default:"Статус" }}</strong>
</div>
</div>
<div class="mb-3">
<label class="form-label">Тип статуса</label>
<p id="preview-type">
{% if form.instance.is_system %}
<span class="badge bg-info">Системный</span>
{% else %}
<span class="badge bg-success">Пользовательский</span>
{% endif %}
</p>
</div>
<div class="mb-3">
<label class="form-label">Финальный статус</label>
<p id="preview-end">
{% if form.instance.is_positive_end %}
<span class="badge bg-success">✓ Успешный исход сделки</span>
{% elif form.instance.is_negative_end %}
<span class="badge bg-danger">✗ Отрицательный исход сделки</span>
{% else %}
<span class="badge bg-secondary">Промежуточный</span>
{% endif %}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const nameInput = document.getElementById('{{ form.name.id_for_label }}');
const codeInput = document.getElementById('{{ form.code.id_for_label }}');
const colorInput = document.getElementById('{{ form.color.id_for_label }}');
const positiveEndCheckbox = document.getElementById('{{ form.is_positive_end.id_for_label }}');
const negativeEndCheckbox = document.getElementById('{{ form.is_negative_end.id_for_label }}');
const previewName = document.getElementById('preview-name');
const previewCode = document.getElementById('preview-code');
const previewColor = document.getElementById('preview-color');
const previewColorText = document.getElementById('preview-color-text');
const colorPreview = document.getElementById('color-preview');
const previewEnd = document.getElementById('preview-end');
function updatePreview() {
// Обновляем название
previewName.textContent = nameInput.value || 'Название';
previewColorText.textContent = nameInput.value || 'Статус';
// Обновляем код
previewCode.textContent = codeInput.value || 'код';
// Обновляем цвет
const color = colorInput.value || '#808080';
previewColor.style.backgroundColor = color;
colorPreview.style.backgroundColor = color;
// Обновляем тип конца
if (positiveEndCheckbox.checked) {
previewEnd.innerHTML = '<span class="badge bg-success">✓ Успешный исход сделки</span>';
} else if (negativeEndCheckbox.checked) {
previewEnd.innerHTML = '<span class="badge bg-danger">✗ Отрицательный исход сделки</span>';
} else {
previewEnd.innerHTML = '<span class="badge bg-secondary">Промежуточный</span>';
}
}
nameInput.addEventListener('input', updatePreview);
codeInput.addEventListener('input', updatePreview);
colorInput.addEventListener('change', updatePreview);
positiveEndCheckbox.addEventListener('change', updatePreview);
negativeEndCheckbox.addEventListener('change', updatePreview);
// Инициальное обновление
updatePreview();
});
</script>
{% endblock %}