diff --git a/myproject/inventory/forms.py b/myproject/inventory/forms.py
index 19fe023..7d2af7e 100644
--- a/myproject/inventory/forms.py
+++ b/myproject/inventory/forms.py
@@ -356,11 +356,6 @@ class IncomingDocumentForm(forms.ModelForm):
super().__init__(*args, **kwargs)
self.fields['warehouse'].queryset = Warehouse.objects.filter(is_active=True)
- # Устанавливаем дату по умолчанию - сегодня
- if not self.initial.get('date'):
- from django.utils import timezone
- self.initial['date'] = timezone.now().date()
-
# Если есть склад по умолчанию - предвыбираем его
if not self.initial.get('warehouse'):
default_warehouse = Warehouse.objects.filter(
diff --git a/myproject/inventory/views/incoming_document.py b/myproject/inventory/views/incoming_document.py
index 1895dcd..1012b12 100644
--- a/myproject/inventory/views/incoming_document.py
+++ b/myproject/inventory/views/incoming_document.py
@@ -35,6 +35,13 @@ class IncomingDocumentCreateView(LoginRequiredMixin, CreateView):
form_class = IncomingDocumentForm
template_name = 'inventory/incoming_document/incoming_document_form.html'
+ def get_initial(self):
+ """Устанавливаем начальные значения для формы"""
+ initial = super().get_initial()
+ from django.utils import timezone
+ initial['date'] = timezone.now().date()
+ return initial
+
def form_valid(self, form):
document = IncomingDocumentService.create_document(
warehouse=form.cleaned_data['warehouse'],
diff --git a/myproject/products/static/products/js/bulk-category-modal.js b/myproject/products/static/products/js/bulk-category-modal.js
index d410644..bb2e7e3 100644
--- a/myproject/products/static/products/js/bulk-category-modal.js
+++ b/myproject/products/static/products/js/bulk-category-modal.js
@@ -37,27 +37,35 @@
// Event listeners
bulkCategoryAction.addEventListener('click', handleOpenModal);
- applyBtn.addEventListener('click', handleApply);
+
+ if (applyBtn) {
+ applyBtn.addEventListener('click', handleApply);
+ }
+
if (categorySearchInput) {
categorySearchInput.addEventListener('input', handleCategorySearch);
}
// Listen for mode changes
const modeRadios = document.querySelectorAll('input[name="bulkCategoryMode"]');
- modeRadios.forEach(radio => {
- radio.addEventListener('change', () => {
- hideError();
- updateModeUI();
- updateApplyButtonState();
+ if (modeRadios.length > 0) {
+ modeRadios.forEach(radio => {
+ radio.addEventListener('change', () => {
+ hideError();
+ updateModeUI();
+ updateApplyButtonState();
+ });
});
- });
+ }
// Listen for modal close to reset state
modal.addEventListener('hidden.bs.modal', resetModalState);
- // Initial UI state
- updateModeUI();
- updateApplyButtonState();
+ // Initial UI state (only if elements exist)
+ if (modeRadios.length > 0) {
+ updateModeUI();
+ updateApplyButtonState();
+ }
console.log('Bulk category modal initialized');
}
@@ -317,6 +325,10 @@
* Update apply button state
*/
function updateApplyButtonState() {
+ if (!applyBtn) {
+ return;
+ }
+
const mode = getCurrentMode();
if (mode === 'clear') {
@@ -332,28 +344,111 @@
* Handle apply button click
*/
async function handleApply() {
- if (selectedCategoryIds.size === 0) {
- showError('Выберите хотя бы одну категорию');
- return;
- }
-
+ const mode = getCurrentMode();
const selectedItems = getSelectedItems();
+
if (selectedItems.length === 0) {
showError('Нет выбранных товаров');
return;
}
- // Определяем режим: если чекбокс включен - replace, иначе - add
- const actionMode = clearExistingToggle.checked ? 'replace' : 'add';
+ // Режим очистки категорий
+ if (mode === 'clear') {
+ const confirmed = confirm(
+ `Вы уверены, что хотите удалить ВСЕ категории у ${selectedItems.length} выбранных товаров?\n\nЭто действие нельзя отменить!`
+ );
+ if (!confirmed) {
+ return;
+ }
+
+ const csrfToken = getCsrfToken();
+ if (!csrfToken) {
+ showError('CSRF токен не найден. Обновите страницу и попробуйте снова.');
+ console.error('CSRF token not found');
+ return;
+ }
+
+ const requestData = {
+ items: selectedItems,
+ category_ids: [],
+ action_mode: 'clear'
+ };
+
+ applyBtn.disabled = true;
+ applyBtn.innerHTML = 'Применение...';
+
+ try {
+ const response = await fetch('/products/api/bulk-update-categories/', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRFToken': csrfToken
+ },
+ body: JSON.stringify(requestData)
+ });
+
+ if (!response.ok) {
+ let errorMessage = 'Ошибка сервера';
+ try {
+ const responseText = await response.text();
+ try {
+ const errorData = JSON.parse(responseText);
+ errorMessage = errorData.message || errorData.error || errorMessage;
+ } catch (jsonError) {
+ console.error('Server error response:', responseText);
+ errorMessage = `Ошибка ${response.status}: ${response.statusText}`;
+ if (response.status === 403) {
+ errorMessage = 'Доступ запрещён. Возможно, проблема с CSRF токеном. Обновите страницу и попробуйте снова.';
+ }
+ }
+ } catch (readError) {
+ console.error('Error reading response:', readError);
+ errorMessage = `Ошибка ${response.status}: ${response.statusText}`;
+ }
+ showError(errorMessage);
+ return;
+ }
+
+ const result = await response.json();
+
+ if (result.success) {
+ showSuccessToast(result.message || `Категории удалены у ${result.updated_count} товаров`);
+ modalInstance.hide();
+
+ setTimeout(() => {
+ if (confirm('Категории очищены. Обновить страницу?')) {
+ location.reload();
+ }
+ }, 500);
+ } else {
+ showError(result.message || 'Не удалось очистить категории');
+ }
+ } catch (error) {
+ console.error('Error applying clear categories:', error);
+ showError('Произошла ошибка. Попробуйте снова. Ошибка: ' + error.message);
+ } finally {
+ applyBtn.innerHTML = ' Применить';
+ applyBtn.disabled = false;
+ updateApplyButtonState();
+ }
+
+ return;
+ }
+
+ // Режим add/replace
+ if (selectedCategoryIds.size === 0) {
+ showError('Выберите хотя бы одну категорию');
+ return;
+ }
+
+ const actionMode = mode === 'replace' ? 'replace' : 'add';
- // Prepare request data
const requestData = {
items: selectedItems,
category_ids: Array.from(selectedCategoryIds),
action_mode: actionMode
};
- // Проверка CSRF токена
const csrfToken = getCsrfToken();
if (!csrfToken) {
showError('CSRF токен не найден. Обновите страницу и попробуйте снова.');
@@ -372,7 +467,7 @@
method: 'POST',
headers: {
'Content-Type': 'application/json',
- 'X-CSRFToken': getCsrfToken()
+ 'X-CSRFToken': csrfToken
},
body: JSON.stringify(requestData)
});
@@ -440,101 +535,6 @@
}
}
- /**
- * Handle clear all categories button click
- */
- async function handleClearAll() {
- const selectedItems = getSelectedItems();
- if (selectedItems.length === 0) {
- showError('Нет выбранных товаров');
- return;
- }
-
- // Подтверждение опасной операции
- if (!confirm(`Вы уверены, что хотите удалить ВСЕ категории у ${selectedItems.length} выбранных товаров?\n\nЭто действие нельзя отменить!`)) {
- return;
- }
-
- // Проверка CSRF токена
- const csrfToken = getCsrfToken();
- if (!csrfToken) {
- showError('CSRF токен не найден. Обновите страницу и попробуйте снова.');
- console.error('CSRF token not found');
- return;
- }
-
- // Отправляем запрос с пустым списком категорий и режимом replace
- const requestData = {
- items: selectedItems,
- category_ids: [], // Пустой список = очистить все
- action_mode: 'clear' // Специальный режим
- };
-
- // Disable button and show loading
- clearAllBtn.disabled = true;
- clearAllBtn.innerHTML = 'Очистка...';
-
- console.log('Sending clear categories request:', requestData);
-
- try {
- const response = await fetch('/products/api/bulk-update-categories/', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-CSRFToken': getCsrfToken()
- },
- body: JSON.stringify(requestData)
- });
-
- // Проверяем статус ответа
- if (!response.ok) {
- let errorMessage = 'Ошибка сервера';
- try {
- // Читаем тело один раз как текст
- const responseText = await response.text();
- // Пытаемся распарсить как JSON
- try {
- const errorData = JSON.parse(responseText);
- errorMessage = errorData.message || errorData.error || errorMessage;
- } catch (jsonError) {
- // Если не JSON, используем текст как есть или формируем сообщение
- console.error('Server error response:', responseText);
- errorMessage = `Ошибка ${response.status}: ${response.statusText}`;
- if (response.status === 403) {
- errorMessage = 'Доступ запрещён. Возможно, проблема с CSRF токеном. Обновите страницу и попробуйте снова.';
- }
- }
- } catch (readError) {
- console.error('Error reading response:', readError);
- errorMessage = `Ошибка ${response.status}: ${response.statusText}`;
- }
- showError(errorMessage);
- return;
- }
-
- const result = await response.json();
-
- if (result.success) {
- showSuccessToast(result.message || `Категории удалены у ${result.updated_count} товаров`);
- modalInstance.hide();
-
- setTimeout(() => {
- if (confirm('Категории очищены. Обновить страницу?')) {
- location.reload();
- }
- }, 500);
- } else {
- showError(result.message || 'Не удалось очистить категории');
- }
- } catch (error) {
- console.error('Error clearing categories:', error);
- showError('Произошла ошибка. Попробуйте снова. Ошибка: ' + error.message);
- } finally {
- clearAllBtn.innerHTML = ' Очистить все категории';
- clearAllBtn.disabled = false;
- }
- }
-
/**
* Show error message in modal
*/
@@ -597,12 +597,20 @@
*/
function resetModalState() {
selectedCategoryIds.clear();
- categorySearchInput.value = '';
+
+ if (categorySearchInput) {
+ categorySearchInput.value = '';
+ }
+
hideError();
-
- // Сбрасываем чекбокс очистки
- clearExistingToggle.checked = false;
-
+
+ // Сбрасываем режим на "добавить"
+ const addModeRadio = document.getElementById('bulkCategoryModeAdd');
+ if (addModeRadio) {
+ addModeRadio.checked = true;
+ }
+
+ updateModeUI();
updateApplyButtonState();
}
diff --git a/myproject/products/templates/products/products_list.html b/myproject/products/templates/products/products_list.html
index 3d88b79..8c93f41 100644
--- a/myproject/products/templates/products/products_list.html
+++ b/myproject/products/templates/products/products_list.html
@@ -486,5 +486,5 @@
{% block extra_js %}
{% load static %}
-
+
{% endblock %}