Corp/backend/templates/procurement/request_form.html
Игорь Надеин 2e92c4d7a0 fix: исправлена загрузка отделов и обработка пагинированного ответа
- API departments возвращает объект с пагинацией {results: [...]}, а не массив
- Добавлена универсальная обработка как массива, так и объекта с results
- Добавлены проверки существования DOM элементов перед обращением
- Исправлена ошибка при редактировании заявки
2025-12-10 16:53:09 +03:00

314 lines
12 KiB
HTML
Raw Permalink 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 %}Новая заявка{% endblock %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/components/page-header.css' %}">
<link rel="stylesheet" href="{% static 'css/components/procurement.css' %}">
{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h4 class="mb-0">
<i class="bi-cart-plus me-2"></i>Новая заявка на закупку
</h4>
</div>
<form method="POST" id="requestForm">
{% csrf_token %}
<div class="card-body">
<div class="row g-3">
<div class="col-12">
<label class="form-label">Название заявки <span class="text-danger">*</span></label>
<input type="text" name="title" class="form-control"
placeholder="Например: Закупка ноутбуков для нового сотрудника"
required maxlength="255">
<div class="form-text">Кратко опишите суть заявки</div>
</div>
<div class="col-12">
<label class="form-label">Обоснование / описание</label>
<textarea name="description" class="form-control" rows="4"
placeholder="Опишите необходимость закупки, для каких целей..."
maxlength="2000"></textarea>
</div>
<div class="col-md-6">
<label class="form-label">Отдел <span class="text-danger">*</span></label>
<select name="department" class="form-select" required id="departmentSelect">
<option value="">Загрузка...</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Срочность <span class="text-danger">*</span></label>
<select name="urgency" class="form-select" required>
<option value="low">🟢 Низкая — до 30 дней</option>
<option value="medium" selected>🟡 Средняя — до 14 дней</option>
<option value="high">🟠 Высокая — до 7 дней</option>
<option value="critical">🔴 Критическая — до 3 дней</option>
</select>
</div>
</div>
</div>
<div class="card-footer d-flex gap-2 justify-content-between">
<a href="{% url 'procurement_frontend:request_list' %}" class="btn btn-outline-secondary">
<i class="bi-arrow-left me-1"></i>Отмена
</a>
<div class="d-flex gap-2">
<button type="submit" name="action" value="draft" class="btn btn-outline-primary">
<i class="bi-save me-1"></i>Сохранить черновик
</button>
<button type="submit" name="action" value="submit" class="btn btn-primary">
<i class="bi-send me-1"></i>Создать и отправить
</button>
</div>
</div>
</form>
</div>
<div class="alert alert-info mt-3">
<i class="bi-lightbulb me-2"></i>
<strong>Подсказка:</strong> После создания заявки вы сможете добавить детальные позиции
(товары с количеством и ценами) на странице заявки.
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
const requestId = {{ request_id|default:'null' }};
let isEdit = requestId !== null;
// Загрузка данных при старте
document.addEventListener('DOMContentLoaded', async () => {
console.log('[Init] Page loaded, requestId:', requestId);
// Проверяем наличие элемента select
const select = document.getElementById('departmentSelect');
if (!select) {
console.error('[Init] Element #departmentSelect not found!');
return;
}
await loadDepartments();
if (isEdit) {
await loadRequestData();
}
});
async function loadDepartments() {
const select = document.getElementById('departmentSelect');
try {
console.log('[Departments] Loading from /api/v1/departments/');
const resp = await fetch('/api/v1/departments/');
console.log('[Departments] Response status:', resp.status);
if (!resp.ok) {
const errorText = await resp.text();
console.error('[Departments] Error response:', errorText);
select.innerHTML = '<option value="">Ошибка загрузки отделов</option>';
alert('Не удалось загрузить список отделов. Проверьте консоль.');
return;
}
const departments = await resp.json();
console.log('[Departments] Raw response:', departments);
// API может вернуть массив или объект с пагинацией {results: [...]}
const departmentsList = Array.isArray(departments) ? departments : (departments.results || []);
console.log('[Departments] Loaded:', departmentsList.length, 'departments');
if (!departmentsList || departmentsList.length === 0) {
select.innerHTML = '<option value="">Нет доступных отделов</option>';
return;
}
select.innerHTML = '<option value="">Выберите отдел...</option>';
departmentsList.forEach(dept => {
const option = document.createElement('option');
option.value = dept.id;
option.textContent = dept.name;
// Автовыбор отдела текущего пользователя, если он один
if (!isEdit && departmentsList.length === 1) {
option.selected = true;
}
select.appendChild(option);
});
console.log('[Departments] Select populated successfully');
} catch (e) {
console.error('[Departments] Error loading departments:', e);
select.innerHTML = '<option value="">Ошибка сети</option>';
alert('Ошибка загрузки отделов: ' + e.message);
}
}
async function loadRequestData() {
try {
const resp = await fetch(`/api/v1/procurement/requests/${requestId}/`);
if (!resp.ok) {
throw new Error('Не удалось загрузить заявку');
}
const request = await resp.json();
// Заполняем форму
document.querySelector('[name="title"]').value = request.title || '';
document.querySelector('[name="description"]').value = request.description || '';
document.querySelector('[name="urgency"]').value = request.urgency || 'medium';
// Отдел выбираем после загрузки списка
const deptSelect = document.getElementById('departmentSelect');
if (request.department) {
const deptId = typeof request.department === 'object' ? request.department.id : request.department;
deptSelect.value = deptId;
}
// Обновляем заголовок
const cardHeader = document.querySelector('.card-header h4');
if (cardHeader) {
cardHeader.innerHTML = '<i class="bi-pencil me-2"></i>Редактирование заявки #' + requestId;
}
const breadcrumbItem = document.querySelector('.breadcrumb-item.active');
if (breadcrumbItem) {
breadcrumbItem.textContent = 'Редактирование заявки';
}
// Скрываем кнопку "Создать и отправить" для редактирования
const submitBtn = document.querySelector('[name="action"][value="submit"]');
if (submitBtn) {
submitBtn.remove();
}
// Меняем текст кнопки сохранения
const draftBtn = document.querySelector('[name="action"][value="draft"]');
if (draftBtn) {
draftBtn.innerHTML = '<i class="bi-save me-1"></i>Сохранить изменения';
}
} catch (e) {
console.error('Error loading request:', e);
alert('Ошибка загрузки заявки: ' + e.message);
}
}
// Обработка отправки формы
document.getElementById('requestForm').addEventListener('submit', async (e) => {
e.preventDefault();
const form = e.target;
const submitBtn = e.submitter;
const action = submitBtn?.value || 'draft';
if (!form.checkValidity()) {
form.reportValidity();
return;
}
// Блокируем кнопки
const buttons = form.querySelectorAll('button[type="submit"]');
buttons.forEach(btn => btn.disabled = true);
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Сохранение...';
try {
const formData = new FormData(form);
const data = {
title: formData.get('title'),
description: formData.get('description'),
department: parseInt(formData.get('department')),
urgency: formData.get('urgency')
};
let request;
if (isEdit) {
// Обновляем существующую заявку
const resp = await fetch(`/api/v1/procurement/requests/${requestId}/`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': formData.get('csrfmiddlewaretoken')
},
body: JSON.stringify(data)
});
if (!resp.ok) {
const err = await resp.json();
throw new Error(err.error || JSON.stringify(err));
}
request = await resp.json();
} else {
// Создаём новую заявку
const resp = await fetch('/api/v1/procurement/requests/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': formData.get('csrfmiddlewaretoken')
},
body: JSON.stringify(data)
});
if (!resp.ok) {
const err = await resp.json();
throw new Error(err.error || JSON.stringify(err));
}
request = await resp.json();
// Если нужно сразу отправить на согласование
if (action === 'submit') {
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Отправка...';
const submitResp = await fetch(`/api/v1/procurement/requests/${request.id}/submit/`, {
method: 'POST',
headers: {
'X-CSRFToken': formData.get('csrfmiddlewaretoken')
}
});
if (!submitResp.ok) {
const err = await submitResp.json();
// Заявка создана, но не отправлена - перенаправляем на детали
alert('Заявка создана, но не удалось отправить на согласование: ' + (err.error || 'Неизвестная ошибка'));
}
}
}
// Перенаправляем на страницу заявки
window.location.href = `/procurement/orders/${request.id}/`;
} catch (error) {
console.error('Error:', error);
alert('Ошибка: ' + error.message);
// Разблокируем кнопки
buttons.forEach(btn => btn.disabled = false);
submitBtn.innerHTML = originalText;
}
});
</script>
{% endblock %}