Добавлен универсальный поиск клиента и быстрое создание нового клиента
Реализованы следующие функции: - AJAX API endpoint для поиска клиента по имени, телефону или email одновременно - AJAX API endpoint для создания нового клиента прямо при создании заказа - Интерактивная форма поиска в поле "Клиент" с использованием Select2 - При отсутствии результатов поиска предлагается создать нового клиента с автоматическим заполнением формы введенными данными - Модальное окно для создания клиента во всплывающем окне (не на отдельной странице) - Автоматический выбор созданного клиента после сохранения Изменения: 1. customers/views.py - добавлены endpoints api_search_customers и api_create_customer 2. customers/urls.py - добавлены URL маршруты для новых endpoints 3. orders/templates/orders/order_form.html - обновлена инициализация Select2 для поиска, добавлено модальное окно и стили 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,31 @@
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Стили для поиска клиента */
|
||||
.customer-option {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.customer-create-option {
|
||||
color: #28a745;
|
||||
font-weight: 500;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.customer-create-option i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
/* Select2 dropdown styling */
|
||||
.select2-results__option.customer-option-item {
|
||||
padding: 8px 8px;
|
||||
border-bottom: 1px solid #f1f1f1;
|
||||
}
|
||||
|
||||
.select2-results__option.customer-option-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -374,7 +399,84 @@
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Инициализация Select2 для обычных полей (не товары)
|
||||
// Инициализация Select2 для поля customer с поиском
|
||||
$('#{{ form.customer.id_for_label }}').select2({
|
||||
theme: 'bootstrap-5',
|
||||
width: '100%',
|
||||
language: 'ru',
|
||||
placeholder: 'Начните вводить имя, телефон или email',
|
||||
minimumInputLength: 1,
|
||||
ajax: {
|
||||
url: '{% url "customers:api-search-customers" %}',
|
||||
dataType: 'json',
|
||||
delay: 250,
|
||||
data: function(params) {
|
||||
return {
|
||||
q: params.term,
|
||||
page: params.page || 1
|
||||
};
|
||||
},
|
||||
processResults: function(data) {
|
||||
return {
|
||||
results: data.results,
|
||||
pagination: {
|
||||
more: data.pagination.more
|
||||
}
|
||||
};
|
||||
},
|
||||
cache: true
|
||||
},
|
||||
templateResult: formatCustomerOption,
|
||||
templateSelection: formatCustomerSelection,
|
||||
escapeMarkup: function(markup) { return markup; }
|
||||
});
|
||||
|
||||
// Форматирование опции в списке
|
||||
function formatCustomerOption(option) {
|
||||
if (!option.id) {
|
||||
return option.text;
|
||||
}
|
||||
|
||||
if (option.is_create_option) {
|
||||
return '<div class="customer-create-option"><i class="bi bi-plus-circle"></i> ' + option.text + '</div>';
|
||||
}
|
||||
|
||||
let html = '<div class="customer-option">';
|
||||
html += '<strong>' + option.name + '</strong>';
|
||||
if (option.phone) {
|
||||
html += '<br><small class="text-muted">Телефон: ' + option.phone + '</small>';
|
||||
}
|
||||
if (option.email) {
|
||||
html += '<br><small class="text-muted">Email: ' + option.email + '</small>';
|
||||
}
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// Форматирование выбранного значения
|
||||
function formatCustomerSelection(option) {
|
||||
if (!option.id) {
|
||||
return option.text;
|
||||
}
|
||||
if (option.is_create_option) {
|
||||
return option.text;
|
||||
}
|
||||
return option.name;
|
||||
}
|
||||
|
||||
// Обработка выбора в Select2
|
||||
$('#{{ form.customer.id_for_label }}').on('select2:select', function(e) {
|
||||
const data = e.params.data;
|
||||
|
||||
if (data.is_create_option) {
|
||||
// Очищаем select2
|
||||
$(this).val(null).trigger('change');
|
||||
// Открываем модальное окно для создания клиента
|
||||
openCreateCustomerModal(data.search_text);
|
||||
}
|
||||
});
|
||||
|
||||
// Инициализация Select2 для остальных полей
|
||||
$('.select2:not(.select2-order-item)').select2({
|
||||
theme: 'bootstrap-5',
|
||||
width: '100%',
|
||||
@@ -633,6 +735,114 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
// === СОЗДАНИЕ НОВОГО КЛИЕНТА ===
|
||||
|
||||
// Функция открытия модального окна создания клиента
|
||||
window.openCreateCustomerModal = function(searchText = '') {
|
||||
// Заполняем форму предложенными данными
|
||||
if (searchText) {
|
||||
// Пытаемся распознать введенные данные
|
||||
const phoneRegex = /[\d\s\-\+\(\)]+/;
|
||||
const emailRegex = /[^\s@]+@[^\s@]+\.[^\s@]+/;
|
||||
|
||||
// Ищем email и телефон в строке поиска
|
||||
const emailMatch = searchText.match(emailRegex);
|
||||
const phoneMatch = searchText.match(phoneRegex);
|
||||
|
||||
// Если это похоже на email, заполняем email
|
||||
if (emailMatch) {
|
||||
document.getElementById('customer-email').value = emailMatch[0];
|
||||
}
|
||||
// Если это похоже на телефон (много цифр), заполняем телефон
|
||||
else if (phoneMatch && phoneMatch[0].replace(/\D/g, '').length >= 9) {
|
||||
document.getElementById('customer-phone').value = phoneMatch[0];
|
||||
}
|
||||
// Иначе считаем это имя
|
||||
else {
|
||||
document.getElementById('customer-name').value = searchText;
|
||||
}
|
||||
}
|
||||
|
||||
// Очищаем поля которые не заполнены
|
||||
const customerNameInput = document.getElementById('customer-name');
|
||||
const customerPhoneInput = document.getElementById('customer-phone');
|
||||
const customerEmailInput = document.getElementById('customer-email');
|
||||
const createCustomerModal = new bootstrap.Modal(document.getElementById('createCustomerModal'));
|
||||
|
||||
// Очищаем сообщения об ошибках
|
||||
document.getElementById('customer-form-errors').innerHTML = '';
|
||||
document.getElementById('customer-form-errors').style.display = 'none';
|
||||
|
||||
createCustomerModal.show();
|
||||
};
|
||||
|
||||
// Обработчик сохранения нового клиента
|
||||
document.getElementById('save-customer-btn').addEventListener('click', function() {
|
||||
const name = document.getElementById('customer-name').value.trim();
|
||||
const phone = document.getElementById('customer-phone').value.trim();
|
||||
const email = document.getElementById('customer-email').value.trim();
|
||||
|
||||
// Базовая валидация
|
||||
const errors = [];
|
||||
if (!name) {
|
||||
errors.push('Имя клиента обязательно');
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
const errorDiv = document.getElementById('customer-form-errors');
|
||||
errorDiv.innerHTML = '<ul class="mb-0">' + errors.map(e => '<li>' + e + '</li>').join('') + '</ul>';
|
||||
errorDiv.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
// Отправляем AJAX запрос
|
||||
fetch('{% url "customers:api-create-customer" %}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token }}'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
phone: phone || null,
|
||||
email: email || null
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Закрываем модальное окно
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('createCustomerModal'));
|
||||
modal.hide();
|
||||
|
||||
// Выбираем созданного клиента в Select2
|
||||
const $customerSelect = $('#{{ form.customer.id_for_label }}');
|
||||
const newOption = new Option(data.name, data.id, true, true);
|
||||
$customerSelect.append(newOption).trigger('change');
|
||||
|
||||
// Очищаем форму
|
||||
document.getElementById('customer-name').value = '';
|
||||
document.getElementById('customer-phone').value = '';
|
||||
document.getElementById('customer-email').value = '';
|
||||
document.getElementById('customer-form-errors').innerHTML = '';
|
||||
document.getElementById('customer-form-errors').style.display = 'none';
|
||||
|
||||
// Показываем успешное сообщение
|
||||
alert(`Клиент "${data.name}" успешно создан!`);
|
||||
} else {
|
||||
const errorDiv = document.getElementById('customer-form-errors');
|
||||
errorDiv.innerHTML = '<div class="alert alert-danger mb-0">' + data.error + '</div>';
|
||||
errorDiv.style.display = 'block';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
const errorDiv = document.getElementById('customer-form-errors');
|
||||
errorDiv.innerHTML = '<div class="alert alert-danger mb-0">Ошибка при создании клиента: ' + error.message + '</div>';
|
||||
errorDiv.style.display = 'block';
|
||||
});
|
||||
});
|
||||
|
||||
// === ВРЕМЕННЫЕ КОМПЛЕКТЫ ===
|
||||
|
||||
// Модальное окно для создания временного комплекта
|
||||
@@ -757,7 +967,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
|
||||
// Отправляем AJAX запрос
|
||||
fetch('{% url "orders:temporary-kit-create" %}', {
|
||||
fetch('{% url "products:api-temporary-kit-create" %}', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -874,6 +1084,54 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Модальное окно для создания нового клиента -->
|
||||
<div class="modal fade" id="createCustomerModal" tabindex="-1" aria-labelledby="createCustomerModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="createCustomerModalLabel">
|
||||
<i class="bi bi-person-plus"></i> Создать нового клиента
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="customer-form-errors" style="display: none;" class="alert alert-danger"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="customer-name" class="form-label">
|
||||
Имя клиента <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input type="text" class="form-control" id="customer-name"
|
||||
placeholder="Например: Иван Петров">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="customer-phone" class="form-label">Телефон</label>
|
||||
<input type="tel" class="form-control" id="customer-phone"
|
||||
placeholder="+375291234567 или 8(029)1234567">
|
||||
<small class="form-text text-muted">
|
||||
Введите в любом формате, будет автоматически преобразован
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="customer-email" class="form-label">Email</label>
|
||||
<input type="email" class="form-control" id="customer-email"
|
||||
placeholder="ivan@example.com">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<i class="bi bi-x-circle"></i> Отмена
|
||||
</button>
|
||||
<button type="button" class="btn btn-success" id="save-customer-btn">
|
||||
<i class="bi bi-check-circle"></i> Создать клиента
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Модальное окно для создания временного комплекта -->
|
||||
<div class="modal fade" id="tempKitModal" tabindex="-1" aria-labelledby="tempKitModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
|
||||
Reference in New Issue
Block a user