- Добавлены модальные окна для всех действий на странице деталей * Добавление позиции (#addItemModal) * Одобрение заявки (#approveModal) * Отклонение заявки (#rejectModal) - Реализованы JavaScript обработчики для всех кнопок * deleteItem() - удаление позиции * saveItemBtn - сохранение новой позиции * confirmApproveBtn - одобрение с комментарием * confirmRejectBtn - отклонение с обязательным комментарием - Исправлены API endpoints (items вместо request-items) - Доработана форма создания/редактирования заявки * Удалены несуществующие поля (desired_date, estimated_cost) * Добавлен выбор отдела из API * Реализована AJAX отправка формы * Поддержка создания черновика и отправки на согласование * Поддержка редактирования существующих заявок (PATCH) - Добавлен URL и view для редактирования заявок - Кнопка "Редактировать" теперь ведёт на страницу редактирования
574 lines
23 KiB
HTML
574 lines
23 KiB
HTML
{% 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="card mb-4">
|
||
<div class="card-body">
|
||
<div class="d-flex align-items-start gap-3">
|
||
<div class="proc-urgency-icon {{ request_obj.urgency }}" style="width: 48px; height: 48px; font-size: 1.25rem;">
|
||
{% if request_obj.urgency == 'critical' %}
|
||
<i class="bi-exclamation-triangle-fill"></i>
|
||
{% elif request_obj.urgency == 'high' %}
|
||
<i class="bi-arrow-up-circle"></i>
|
||
{% elif request_obj.urgency == 'medium' %}
|
||
<i class="bi-dash-circle"></i>
|
||
{% else %}
|
||
<i class="bi-arrow-down-circle"></i>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="flex-grow-1">
|
||
<h3 class="mb-1">{{ request_obj.title }}</h3>
|
||
<div class="d-flex flex-wrap gap-3 text-muted small">
|
||
<span><i class="bi-building me-1"></i>{{ request_obj.department.name }}</span>
|
||
<span><i class="bi-person me-1"></i>{{ request_obj.requestor.get_full_name }}</span>
|
||
<span><i class="bi-calendar me-1"></i>{{ request_obj.created_at|date:"d.m.Y H:i" }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="text-end">
|
||
{% include "procurement/_status_badge.html" with status=request_obj.status %}
|
||
<div class="mt-2 h4 mb-0">{{ request_obj.total_cost|floatformat:0 }} ₽</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row g-4">
|
||
{# ===== Основная информация ===== #}
|
||
<div class="col-lg-8">
|
||
{# Описание #}
|
||
<div class="card mb-4">
|
||
<div class="card-header">
|
||
<h5 class="mb-0"><i class="bi-text-paragraph me-2"></i>Описание</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
{% if request_obj.description %}
|
||
<p class="mb-0" style="white-space: pre-wrap;">{{ request_obj.description }}</p>
|
||
{% else %}
|
||
<p class="text-muted mb-0">Описание не указано</p>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
{# Позиции заявки #}
|
||
<div class="card mb-4">
|
||
<div class="card-header d-flex align-items-center justify-content-between">
|
||
<h5 class="mb-0"><i class="bi-list-check me-2"></i>Позиции ({{ request_obj.items.count }})</h5>
|
||
{% if can_edit %}
|
||
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#addItemModal">
|
||
<i class="bi-plus-lg"></i> Добавить
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% if request_obj.items.exists %}
|
||
<div class="proc-table-header d-none d-lg-block">
|
||
<div class="proc-table-row">
|
||
<div class="proc-cell" style="flex: 3;"><h6 class="mb-0 text-secondary">Наименование</h6></div>
|
||
<div class="proc-cell" style="flex: 1;"><h6 class="mb-0 text-secondary text-center">Кол-во</h6></div>
|
||
<div class="proc-cell" style="flex: 1;"><h6 class="mb-0 text-secondary text-end">Цена</h6></div>
|
||
<div class="proc-cell" style="flex: 1;"><h6 class="mb-0 text-secondary text-end">Сумма</h6></div>
|
||
{% if can_edit %}
|
||
<div class="proc-cell" style="width: 50px;"></div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-body p-0">
|
||
{% for item in request_obj.items.all %}
|
||
<div class="proc-request-item">
|
||
<div class="proc-cell" style="flex: 3;">
|
||
<div class="fw-medium">{{ item.name }}</div>
|
||
{% if item.description %}
|
||
<small class="text-muted d-block mt-1">{{ item.description|truncatechars:80 }}</small>
|
||
{% endif %}
|
||
</div>
|
||
<div class="proc-cell text-center" style="flex: 1;">
|
||
<span class="d-lg-none text-muted small">Количество: </span>
|
||
{{ item.quantity }} {{ item.unit }}
|
||
</div>
|
||
<div class="proc-cell text-end" style="flex: 1;">
|
||
<span class="d-lg-none text-muted small">Цена: </span>
|
||
{{ item.estimated_unit_price|floatformat:0 }} ₽
|
||
</div>
|
||
<div class="proc-cell text-end" style="flex: 1;">
|
||
<span class="d-lg-none text-muted small">Сумма: </span>
|
||
<strong>{{ item.total_price|floatformat:0 }} ₽</strong>
|
||
</div>
|
||
{% if can_edit %}
|
||
<div class="proc-cell text-center" style="width: 50px;">
|
||
<button class="btn btn-sm btn-link text-danger p-0" title="Удалить" onclick="deleteItem({{ item.id }})">
|
||
<i class="bi-trash"></i>
|
||
</button>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
|
||
{# Итого #}
|
||
<div class="proc-table-header border-top">
|
||
<div class="proc-table-row">
|
||
<div class="proc-cell" style="flex: 3;"></div>
|
||
<div class="proc-cell" style="flex: 1;"></div>
|
||
<div class="proc-cell text-end" style="flex: 1;"><h6 class="mb-0 text-secondary">Итого:</h6></div>
|
||
<div class="proc-cell text-end" style="flex: 1;"><h6 class="mb-0 fw-bold">{{ request_obj.total_cost|floatformat:0 }} ₽</h6></div>
|
||
{% if can_edit %}
|
||
<div class="proc-cell" style="width: 50px;"></div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% else %}
|
||
<div class="card-body">
|
||
<div class="text-center py-4 text-muted">
|
||
<i class="bi-inbox fs-3 d-block mb-2"></i>
|
||
<p class="mb-0">Позиции не добавлены</p>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
{# История согласования #}
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5 class="mb-0"><i class="bi-clock-history me-2"></i>История согласования</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
{% if request_obj.approvals.exists %}
|
||
<div class="timeline">
|
||
{% for approval in request_obj.approvals.all %}
|
||
<div class="d-flex gap-3 mb-3">
|
||
<div class="flex-shrink-0">
|
||
{% if approval.status == 'approved' %}
|
||
<span class="badge bg-success rounded-circle p-2"><i class="bi-check"></i></span>
|
||
{% elif approval.status == 'rejected' %}
|
||
<span class="badge bg-danger rounded-circle p-2"><i class="bi-x"></i></span>
|
||
{% else %}
|
||
<span class="badge bg-secondary rounded-circle p-2"><i class="bi-hourglass"></i></span>
|
||
{% endif %}
|
||
</div>
|
||
<div>
|
||
<div class="fw-medium">{{ approval.approver.get_full_name }}</div>
|
||
<div class="text-muted small">
|
||
{% if approval.status == 'approved' %}
|
||
Одобрено
|
||
{% if approval.decided_at %} — {{ approval.decided_at|date:"d.m.Y H:i" }}{% endif %}
|
||
{% elif approval.status == 'rejected' %}
|
||
Отклонено
|
||
{% if approval.decided_at %} — {{ approval.decided_at|date:"d.m.Y H:i" }}{% endif %}
|
||
{% else %}
|
||
Ожидает решения
|
||
{% endif %}
|
||
</div>
|
||
{% if approval.comment %}
|
||
<div class="mt-1 small">{{ approval.comment }}</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<p class="text-muted mb-0">Согласования ещё не начаты</p>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# ===== Боковая панель ===== #}
|
||
<div class="col-lg-4">
|
||
{# Действия #}
|
||
<div class="card mb-4">
|
||
<div class="card-header">
|
||
<h5 class="mb-0"><i class="bi-gear me-2"></i>Действия</h5>
|
||
</div>
|
||
<div class="card-body d-grid gap-2">
|
||
{% if can_edit and request_obj.status == 'draft' %}
|
||
<button class="btn btn-primary" id="submitRequest">
|
||
<i class="bi-send me-2"></i>Отправить на согласование
|
||
</button>
|
||
<a href="{% url 'procurement_frontend:request_edit' request_obj.id %}" class="btn btn-outline-secondary">
|
||
<i class="bi-pencil me-2"></i>Редактировать
|
||
</a>
|
||
{% endif %}
|
||
|
||
{% if can_approve and request_obj.status == 'pending' %}
|
||
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#approveModal">
|
||
<i class="bi-check-lg me-2"></i>Одобрить
|
||
</button>
|
||
<button class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#rejectModal">
|
||
<i class="bi-x-lg me-2"></i>Отклонить
|
||
</button>
|
||
{% endif %}
|
||
|
||
<a href="{% url 'procurement_frontend:request_list' %}" class="btn btn-outline-secondary">
|
||
<i class="bi-arrow-left me-2"></i>К списку
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
{# Информация #}
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h5 class="mb-0"><i class="bi-info-circle me-2"></i>Информация</h5>
|
||
</div>
|
||
<ul class="list-group list-group-flush">
|
||
<li class="list-group-item d-flex justify-content-between">
|
||
<span class="text-muted">Срочность</span>
|
||
<span>
|
||
{% if request_obj.urgency == 'critical' %}🔴 Критическая
|
||
{% elif request_obj.urgency == 'high' %}🟠 Высокая
|
||
{% elif request_obj.urgency == 'medium' %}🟡 Средняя
|
||
{% else %}🟢 Низкая{% endif %}
|
||
</span>
|
||
</li>
|
||
<li class="list-group-item d-flex justify-content-between">
|
||
<span class="text-muted">Статус</span>
|
||
<span>{% include "procurement/_status_badge.html" with status=request_obj.status %}</span>
|
||
</li>
|
||
{% if request_obj.executor %}
|
||
<li class="list-group-item d-flex justify-content-between">
|
||
<span class="text-muted">Исполнитель</span>
|
||
<span>{{ request_obj.executor.get_full_name }}</span>
|
||
</li>
|
||
{% endif %}
|
||
<li class="list-group-item d-flex justify-content-between">
|
||
<span class="text-muted">Создано</span>
|
||
<span>{{ request_obj.created_at|date:"d.m.Y H:i" }}</span>
|
||
</li>
|
||
<li class="list-group-item d-flex justify-content-between">
|
||
<span class="text-muted">Обновлено</span>
|
||
<span>{{ request_obj.updated_at|date:"d.m.Y H:i" }}</span>
|
||
</li>
|
||
{% if request_obj.submitted_at %}
|
||
<li class="list-group-item d-flex justify-content-between">
|
||
<span class="text-muted">Отправлено на согласование</span>
|
||
<span>{{ request_obj.submitted_at|date:"d.m.Y H:i" }}</span>
|
||
</li>
|
||
{% endif %}
|
||
{% if request_obj.started_at %}
|
||
<li class="list-group-item d-flex justify-content-between">
|
||
<span class="text-muted">Взято в работу</span>
|
||
<span>{{ request_obj.started_at|date:"d.m.Y H:i" }}</span>
|
||
</li>
|
||
{% endif %}
|
||
{% if request_obj.completed_at %}
|
||
<li class="list-group-item d-flex justify-content-between">
|
||
<span class="text-muted">Завершено</span>
|
||
<span>{{ request_obj.completed_at|date:"d.m.Y H:i" }}</span>
|
||
</li>
|
||
{% endif %}
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# ===== МОДАЛЬНЫЕ ОКНА ===== #}
|
||
|
||
{# Модальное окно добавления позиции #}
|
||
<div class="modal fade" id="addItemModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title"><i class="bi-plus-lg me-2"></i>Добавить позицию</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="addItemForm">
|
||
<div class="mb-3">
|
||
<label class="form-label">Наименование <span class="text-danger">*</span></label>
|
||
<input type="text" class="form-control" name="name" required>
|
||
</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Описание</label>
|
||
<textarea class="form-control" name="description" rows="2"></textarea>
|
||
</div>
|
||
<div class="row">
|
||
<div class="col-md-4 mb-3">
|
||
<label class="form-label">Количество <span class="text-danger">*</span></label>
|
||
<input type="number" class="form-control" name="quantity" step="1" min="1" value="1" required>
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label class="form-label">Ед. изм. <span class="text-danger">*</span></label>
|
||
<input type="text" class="form-control" name="unit" value="шт" required>
|
||
</div>
|
||
<div class="col-md-4 mb-3">
|
||
<label class="form-label">Цена <span class="text-danger">*</span></label>
|
||
<input type="number" class="form-control" name="estimated_unit_price" step="0.01" min="0" required>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button type="button" class="btn btn-primary" id="saveItemBtn">Добавить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# Модальное окно одобрения #}
|
||
<div class="modal fade" id="approveModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title"><i class="bi-check-lg me-2"></i>Одобрить заявку</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p>Вы уверены, что хотите одобрить эту заявку?</p>
|
||
<div class="mb-3">
|
||
<label class="form-label">Комментарий (необязательно)</label>
|
||
<textarea class="form-control" id="approveComment" rows="3" placeholder="Добавьте комментарий..."></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button type="button" class="btn btn-success" id="confirmApproveBtn">Одобрить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{# Модальное окно отклонения #}
|
||
<div class="modal fade" id="rejectModal" tabindex="-1">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title"><i class="bi-x-lg me-2"></i>Отклонить заявку</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p>Вы уверены, что хотите отклонить эту заявку?</p>
|
||
<div class="mb-3">
|
||
<label class="form-label">Причина отклонения <span class="text-danger">*</span></label>
|
||
<textarea class="form-control" id="rejectComment" rows="3" placeholder="Укажите причину отклонения..." required></textarea>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button type="button" class="btn btn-danger" id="confirmRejectBtn">Отклонить</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
// Получаем ID заявки из URL
|
||
const requestId = window.location.pathname.split('/').filter(x => x && !isNaN(x))[0];
|
||
let requestData = null;
|
||
|
||
// Загрузка данных при старте
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
if (requestId) {
|
||
loadRequestDetail();
|
||
}
|
||
});
|
||
|
||
async function loadRequestDetail() {
|
||
try {
|
||
const resp = await fetch(`/api/v1/procurement/requests/${requestId}/`);
|
||
if (!resp.ok) {
|
||
throw new Error('Request not found');
|
||
}
|
||
|
||
requestData = await resp.json();
|
||
renderRequestDetail(requestData);
|
||
|
||
} catch (e) {
|
||
console.error('Error loading request:', e);
|
||
document.querySelector('.container').innerHTML = `
|
||
<div class="alert alert-danger">
|
||
<i class="bi-exclamation-triangle me-2"></i>
|
||
Ошибка загрузки заявки
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
function renderRequestDetail(req) {
|
||
// Обновляем title страницы
|
||
document.title = `Заявка #${req.id}`;
|
||
|
||
// Рендерим контент (упрощённая версия - можно расширить)
|
||
// Основные элементы уже есть в шаблоне, просто обновляем их содержимое
|
||
|
||
// Обновляем иконку срочности
|
||
const urgencyIcon = document.querySelector('.proc-urgency-icon');
|
||
if (urgencyIcon) {
|
||
urgencyIcon.className = `proc-urgency-icon ${req.urgency}`;
|
||
const icons = {
|
||
critical: 'bi-exclamation-triangle-fill',
|
||
high: 'bi-arrow-up-circle',
|
||
medium: 'bi-dash-circle',
|
||
low: 'bi-arrow-down-circle'
|
||
};
|
||
urgencyIcon.innerHTML = `<i class="${icons[req.urgency] || 'bi-dash-circle'}"></i>`;
|
||
}
|
||
}
|
||
|
||
// Отправка на согласование
|
||
document.getElementById('submitRequest')?.addEventListener('click', async () => {
|
||
if (!confirm('Отправить заявку на согласование?')) return;
|
||
|
||
try {
|
||
const resp = await fetch(`/api/v1/procurement/requests/${requestId}/submit/`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || '{{ csrf_token }}'
|
||
}
|
||
});
|
||
|
||
if (resp.ok) {
|
||
window.location.reload();
|
||
} else {
|
||
const err = await resp.json();
|
||
alert(err.error || 'Ошибка отправки');
|
||
}
|
||
} catch (e) {
|
||
console.error(e);
|
||
alert('Ошибка сети');
|
||
}
|
||
});
|
||
|
||
// Добавление позиции
|
||
document.getElementById('saveItemBtn')?.addEventListener('click', async () => {
|
||
const form = document.getElementById('addItemForm');
|
||
if (!form.checkValidity()) {
|
||
form.reportValidity();
|
||
return;
|
||
}
|
||
|
||
const formData = new FormData(form);
|
||
const data = {
|
||
name: formData.get('name'),
|
||
description: formData.get('description'),
|
||
quantity: parseFloat(formData.get('quantity')),
|
||
unit: formData.get('unit'),
|
||
estimated_unit_price: parseFloat(formData.get('estimated_unit_price')),
|
||
request: requestId
|
||
};
|
||
|
||
try {
|
||
const resp = await fetch('/api/v1/procurement/items/', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || '{{ csrf_token }}'
|
||
},
|
||
body: JSON.stringify(data)
|
||
});
|
||
|
||
if (resp.ok) {
|
||
bootstrap.Modal.getInstance(document.getElementById('addItemModal')).hide();
|
||
form.reset();
|
||
window.location.reload();
|
||
} else {
|
||
const err = await resp.json();
|
||
alert(err.error || 'Ошибка добавления позиции');
|
||
}
|
||
} catch (e) {
|
||
console.error(e);
|
||
alert('Ошибка сети');
|
||
}
|
||
});
|
||
|
||
// Удаление позиции
|
||
async function deleteItem(itemId) {
|
||
if (!confirm('Удалить эту позицию?')) return;
|
||
|
||
try {
|
||
const resp = await fetch(`/api/v1/procurement/items/${itemId}/`, {
|
||
method: 'DELETE',
|
||
headers: {
|
||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || '{{ csrf_token }}'
|
||
}
|
||
});
|
||
|
||
if (resp.ok) {
|
||
window.location.reload();
|
||
} else {
|
||
const err = await resp.json();
|
||
alert(err.error || 'Ошибка удаления');
|
||
}
|
||
} catch (e) {
|
||
console.error(e);
|
||
alert('Ошибка сети');
|
||
}
|
||
}
|
||
|
||
// Одобрение заявки
|
||
document.getElementById('confirmApproveBtn')?.addEventListener('click', async () => {
|
||
const comment = document.getElementById('approveComment').value;
|
||
|
||
try {
|
||
const resp = await fetch(`/api/v1/procurement/requests/${requestId}/approve/`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || '{{ csrf_token }}'
|
||
},
|
||
body: JSON.stringify({ comment })
|
||
});
|
||
|
||
if (resp.ok) {
|
||
bootstrap.Modal.getInstance(document.getElementById('approveModal')).hide();
|
||
window.location.reload();
|
||
} else {
|
||
const err = await resp.json();
|
||
alert(err.error || 'Ошибка одобрения');
|
||
}
|
||
} catch (e) {
|
||
console.error(e);
|
||
alert('Ошибка сети');
|
||
}
|
||
});
|
||
|
||
// Отклонение заявки
|
||
document.getElementById('confirmRejectBtn')?.addEventListener('click', async () => {
|
||
const comment = document.getElementById('rejectComment').value;
|
||
|
||
if (!comment.trim()) {
|
||
alert('Укажите причину отклонения');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const resp = await fetch(`/api/v1/procurement/requests/${requestId}/reject/`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]')?.value || '{{ csrf_token }}'
|
||
},
|
||
body: JSON.stringify({ comment })
|
||
});
|
||
|
||
if (resp.ok) {
|
||
bootstrap.Modal.getInstance(document.getElementById('rejectModal')).hide();
|
||
window.location.reload();
|
||
} else {
|
||
const err = await resp.json();
|
||
alert(err.error || 'Ошибка отклонения');
|
||
}
|
||
} catch (e) {
|
||
console.error(e);
|
||
alert('Ошибка сети');
|
||
}
|
||
});
|
||
</script>
|
||
{% endblock %}
|