feat(requests): добавлена клиентская валидация формы создания заявления

- RecipientPicker.validate() проверяет наличие получателей/отделов
- RecipientPicker.setError()/clearError() для визуальной индикации ошибок
- Валидация в requestCrudHandler для create и edit
- Проверка: автор не может быть получателем
- Автоматическая очистка ошибки при изменении получателей
- Удалено временное логирование из сериализатора
This commit is contained in:
admin i 2025-12-03 07:30:39 +03:00
parent 7d15ad1a4f
commit 20bd4daade
3 changed files with 86 additions and 3 deletions

View File

@ -245,8 +245,9 @@ class RequestWriteSerializer(serializers.ModelSerializer):
def to_internal_value(self, data: Mapping[str, Any]) -> dict[str, Any]:
"""Предвалидационная очистка входа.
Для обычного пользователя удаляет `employee`, `status`, `approver` до полевой валидации,
чтобы не получить 400 из-за, например, несуществующего PK в `employee`.
Для обычного пользователя удаляет `employee`, `status`, `approver`
до полевой валидации, чтобы не получить 400 из-за, например,
несуществующего PK в `employee`.
Args:
data (Mapping[str, Any]): Входные данные запроса.
@ -260,6 +261,7 @@ class RequestWriteSerializer(serializers.ModelSerializer):
data.pop("employee", None)
data.pop("status", None)
data.pop("approver", None)
return super().to_internal_value(data)
def validate(self, attrs):
@ -316,7 +318,8 @@ class RequestWriteSerializer(serializers.ModelSerializer):
def create(self, validated_data: dict[str, Any]) -> Request:
"""Создание заявки.
Обычным пользователям проставляет текущего пользователя; статус берётся из default модели.
Обычным пользователям проставляет текущего пользователя;
статус берётся из default модели.
Args:
validated_data (dict[str, Any]): Провалидированные данные.

View File

@ -491,6 +491,9 @@ export class RecipientPicker {
}
triggerChange() {
// Очищаем ошибку при любом изменении
this.clearError();
if (this.options.onChange) {
this.options.onChange(this.getValues());
}
@ -505,6 +508,49 @@ export class RecipientPicker {
};
}
/**
* Проверяет, заполнены ли получатели
* @returns {boolean} true если есть получатели или отделы
*/
validate() {
const hasDepartments = this.state.departments.length > 0;
const hasRecipients = this.state.recipients.length > 0;
const isValid = hasDepartments || hasRecipients;
if (!isValid) {
this.setError('Укажите хотя бы одного получателя или отдел');
} else {
this.clearError();
}
return isValid;
}
/**
* Устанавливает сообщение об ошибке
* @param {string} message - Текст ошибки
*/
setError(message) {
this.clearError();
const errorDiv = document.createElement('div');
errorDiv.className = 'invalid-feedback d-block';
errorDiv.textContent = message;
errorDiv.id = 'recipientPickerError';
this.container.appendChild(errorDiv);
this.container.classList.add('is-invalid');
}
/**
* Убирает сообщение об ошибке
*/
clearError() {
const errorDiv = this.container.querySelector('#recipientPickerError');
if (errorDiv) {
errorDiv.remove();
}
this.container.classList.remove('is-invalid');
}
setValues(data) {
if (data.department_ids) {
this.state.departments = Array.isArray(data.department_ids)

View File

@ -65,6 +65,23 @@ export function initRequestCrudHandler(options) {
// Добавляем данные получателей из RecipientPicker
if (createRecipientPicker) {
// Валидация: для обычной отправки должны быть получатели или отделы
if (saveAs === 'submit') {
if (!createRecipientPicker.validate()) {
return;
}
// Проверка: автор не может быть получателем
const recipients = createRecipientPicker.getValues();
if (recipients.recipient_ids && window.currentUserId) {
if (recipients.recipient_ids.includes(window.currentUserId)) {
alert('Вы не можете отправить заявление самому себе');
createRecipientPicker.setError('Вы не можете быть получателем');
return;
}
}
}
const recipients = createRecipientPicker.getValues();
// Добавляем department_ids
@ -146,6 +163,23 @@ export function initRequestCrudHandler(options) {
// Добавляем данные получателей из RecipientPicker
if (editRecipientPicker) {
// Валидация: для обычной отправки должны быть получатели или отделы
if (saveAs === 'submit') {
if (!editRecipientPicker.validate()) {
return;
}
// Проверка: автор не может быть получателем
const recipients = editRecipientPicker.getValues();
if (recipients.recipient_ids && window.currentUserId) {
if (recipients.recipient_ids.includes(window.currentUserId)) {
alert('Вы не можете отправить заявление самому себе');
editRecipientPicker.setError('Вы не можете быть получателем');
return;
}
}
}
const recipients = editRecipientPicker.getValues();
// Добавляем department_ids