Улучшен интерфейс ввода даты и времени доставки
- Исправлены имена полей времени (time_from/time_to вместо delivery_time_start/end) - Поля времени сделаны необязательными (дата остается обязательной) - Добавлен улучшенный UI с быстрыми кнопками для даты и времени - Поля ввода расположены в один ряд, кнопки быстрого выбора ниже - Добавлены CSS и JS файлы для улучшенного интерфейса - Обновлена валидация: время необязательно, но если указано одно - должно быть и другое
This commit is contained in:
@@ -146,13 +146,13 @@ class OrderForm(forms.ModelForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
time_from = forms.TimeField(
|
time_from = forms.TimeField(
|
||||||
required=True,
|
required=False,
|
||||||
widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
|
widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
|
||||||
label='Время доставки от'
|
label='Время доставки от'
|
||||||
)
|
)
|
||||||
|
|
||||||
time_to = forms.TimeField(
|
time_to = forms.TimeField(
|
||||||
required=True,
|
required=False,
|
||||||
widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
|
widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
|
||||||
label='Время доставки до'
|
label='Время доставки до'
|
||||||
)
|
)
|
||||||
@@ -321,13 +321,13 @@ class OrderForm(forms.ModelForm):
|
|||||||
if not delivery_date:
|
if not delivery_date:
|
||||||
raise forms.ValidationError({'delivery_date': 'Необходимо указать дату доставки'})
|
raise forms.ValidationError({'delivery_date': 'Необходимо указать дату доставки'})
|
||||||
|
|
||||||
if not time_from:
|
# Время необязательно, но если указано одно, должно быть указано и другое
|
||||||
raise forms.ValidationError({'time_from': 'Необходимо указать время начала доставки'})
|
if (time_from and not time_to) or (time_to and not time_from):
|
||||||
|
raise forms.ValidationError({
|
||||||
|
'time_to': 'Если указано время начала, необходимо указать и время окончания, и наоборот'
|
||||||
|
})
|
||||||
|
|
||||||
if not time_to:
|
# Проверяем, что время "до" позже времени "от" (если оба указаны)
|
||||||
raise forms.ValidationError({'time_to': 'Необходимо указать время окончания доставки'})
|
|
||||||
|
|
||||||
# Проверяем, что время "до" позже времени "от"
|
|
||||||
if time_from and time_to and time_from >= time_to:
|
if time_from and time_to and time_from >= time_to:
|
||||||
raise forms.ValidationError({
|
raise forms.ValidationError({
|
||||||
'time_to': 'Время окончания доставки должно быть позже времени начала'
|
'time_to': 'Время окончания доставки должно быть позже времени начала'
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.0.10 on 2025-12-24 15:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orders', '0003_remove_customer_is_recipient'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='delivery',
|
||||||
|
name='time_from',
|
||||||
|
field=models.TimeField(blank=True, help_text='Начальное время временного интервала доставки (необязательно)', null=True, verbose_name='Время доставки от'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='delivery',
|
||||||
|
name='time_to',
|
||||||
|
field=models.TimeField(blank=True, help_text='Конечное время временного интервала доставки (необязательно)', null=True, verbose_name='Время доставки до'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -67,13 +67,17 @@ class Delivery(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
time_from = models.TimeField(
|
time_from = models.TimeField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
verbose_name='Время доставки от',
|
verbose_name='Время доставки от',
|
||||||
help_text='Начальное время временного интервала доставки'
|
help_text='Начальное время временного интервала доставки (необязательно)'
|
||||||
)
|
)
|
||||||
|
|
||||||
time_to = models.TimeField(
|
time_to = models.TimeField(
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
verbose_name='Время доставки до',
|
verbose_name='Время доставки до',
|
||||||
help_text='Конечное время временного интервала доставки'
|
help_text='Конечное время временного интервала доставки (необязательно)'
|
||||||
)
|
)
|
||||||
|
|
||||||
cost = models.DecimalField(
|
cost = models.DecimalField(
|
||||||
|
|||||||
161
myproject/orders/static/orders/css/delivery_datetime.css
Normal file
161
myproject/orders/static/orders/css/delivery_datetime.css
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
/* Стили для улучшенного интерфейса даты и времени доставки */
|
||||||
|
|
||||||
|
.delivery-datetime-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Контейнер для всех кнопок быстрого выбора */
|
||||||
|
#delivery-datetime-quick-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Быстрые кнопки для даты */
|
||||||
|
.delivery-date-quick-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-date-quick-btn {
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #495057;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-date-quick-btn:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-date-quick-btn.active {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-date-quick-btn i {
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Предустановленные интервалы времени */
|
||||||
|
.delivery-time-presets {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Дополнительный отступ для группы времени */
|
||||||
|
.delivery-time-presets {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
border-left: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-time-preset-btn {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
background-color: #fff;
|
||||||
|
color: #495057;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-time-preset-btn:hover {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-time-preset-btn.active {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-time-preset-btn i {
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Группа полей времени */
|
||||||
|
.delivery-time-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-time-group .form-control {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-time-separator {
|
||||||
|
color: #6c757d;
|
||||||
|
font-weight: 500;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Улучшенные поля ввода */
|
||||||
|
.delivery-datetime-wrapper .form-control {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-datetime-wrapper .form-control:focus {
|
||||||
|
border-color: #0d6efd;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Подсказки под полями */
|
||||||
|
.delivery-datetime-hint {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #6c757d;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-datetime-hint i {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Адаптивность */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.delivery-date-quick-buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-date-quick-btn {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-time-presets {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-time-group {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delivery-time-separator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
203
myproject/orders/static/orders/js/delivery_datetime.js
Normal file
203
myproject/orders/static/orders/js/delivery_datetime.js
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
/**
|
||||||
|
* Улучшенный интерфейс для ввода даты и времени доставки
|
||||||
|
* Добавляет быстрые кнопки для даты и предустановленные интервалы времени
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация улучшенного интерфейса даты и времени
|
||||||
|
* @param {string} dateFieldId - ID поля даты
|
||||||
|
* @param {string} timeFromFieldId - ID поля времени "от"
|
||||||
|
* @param {string} timeToFieldId - ID поля времени "до"
|
||||||
|
*/
|
||||||
|
function initDeliveryDateTime(dateFieldId, timeFromFieldId, timeToFieldId) {
|
||||||
|
const dateField = document.getElementById(dateFieldId);
|
||||||
|
const timeFromField = document.getElementById(timeFromFieldId);
|
||||||
|
const timeToField = document.getElementById(timeToFieldId);
|
||||||
|
|
||||||
|
if (!dateField || !timeFromField || !timeToField) {
|
||||||
|
console.warn('[Delivery DateTime] Поля не найдены');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Находим общий контейнер для кнопок
|
||||||
|
const buttonsContainer = document.getElementById('delivery-datetime-quick-buttons');
|
||||||
|
if (!buttonsContainer) {
|
||||||
|
console.warn('[Delivery DateTime] Контейнер для кнопок не найден');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем быстрые кнопки для даты
|
||||||
|
addDateQuickButtons(buttonsContainer, dateField);
|
||||||
|
|
||||||
|
// Добавляем предустановленные интервалы времени
|
||||||
|
addTimePresets(buttonsContainer, timeFromField, timeToField);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Добавляет быстрые кнопки для выбора даты
|
||||||
|
*/
|
||||||
|
function addDateQuickButtons(container, dateField) {
|
||||||
|
const quickButtonsContainer = document.createElement('div');
|
||||||
|
quickButtonsContainer.className = 'delivery-date-quick-buttons';
|
||||||
|
|
||||||
|
const buttons = [
|
||||||
|
{ label: 'Сегодня', days: 0, icon: 'bi-calendar-day' },
|
||||||
|
{ label: 'Завтра', days: 1, icon: 'bi-calendar-check' }
|
||||||
|
];
|
||||||
|
|
||||||
|
buttons.forEach(button => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'delivery-date-quick-btn';
|
||||||
|
btn.innerHTML = `<i class="bi ${button.icon}"></i>${button.label}`;
|
||||||
|
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
const date = new Date();
|
||||||
|
date.setDate(date.getDate() + button.days);
|
||||||
|
const dateString = formatDateForInput(date);
|
||||||
|
dateField.value = dateString;
|
||||||
|
|
||||||
|
// Обновляем активное состояние кнопок
|
||||||
|
quickButtonsContainer.querySelectorAll('.delivery-date-quick-btn').forEach(b => {
|
||||||
|
b.classList.remove('active');
|
||||||
|
});
|
||||||
|
btn.classList.add('active');
|
||||||
|
|
||||||
|
// Триггерим событие change для валидации
|
||||||
|
dateField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
});
|
||||||
|
|
||||||
|
quickButtonsContainer.appendChild(btn);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Проверяем текущую дату и активируем соответствующую кнопку
|
||||||
|
if (dateField.value) {
|
||||||
|
const currentDate = new Date(dateField.value);
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
currentDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const diffDays = Math.round((currentDate - today) / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (diffDays === 0) {
|
||||||
|
quickButtonsContainer.children[0].classList.add('active');
|
||||||
|
} else if (diffDays === 1) {
|
||||||
|
quickButtonsContainer.children[1].classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Слушаем изменения даты вручную
|
||||||
|
dateField.addEventListener('change', function() {
|
||||||
|
quickButtonsContainer.querySelectorAll('.delivery-date-quick-btn').forEach(b => {
|
||||||
|
b.classList.remove('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
container.appendChild(quickButtonsContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Добавляет предустановленные интервалы времени
|
||||||
|
*/
|
||||||
|
function addTimePresets(container, timeFromField, timeToField) {
|
||||||
|
// Создаем контейнер для пресетов
|
||||||
|
const presetsContainer = document.createElement('div');
|
||||||
|
presetsContainer.className = 'delivery-time-presets';
|
||||||
|
|
||||||
|
const presets = [
|
||||||
|
{ label: 'Утро', from: '09:00', to: '12:00', icon: 'bi-sunrise' },
|
||||||
|
{ label: 'День', from: '12:00', to: '15:00', icon: 'bi-sun' },
|
||||||
|
{ label: 'Вечер', from: '15:00', to: '18:00', icon: 'bi-sunset' },
|
||||||
|
{ label: 'Поздно', from: '18:00', to: '21:00', icon: 'bi-moon' }
|
||||||
|
];
|
||||||
|
|
||||||
|
presets.forEach(preset => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = 'delivery-time-preset-btn';
|
||||||
|
btn.innerHTML = `<i class="bi ${preset.icon}"></i>${preset.label}`;
|
||||||
|
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
timeFromField.value = preset.from;
|
||||||
|
timeToField.value = preset.to;
|
||||||
|
|
||||||
|
// Обновляем активное состояние кнопок
|
||||||
|
presetsContainer.querySelectorAll('.delivery-time-preset-btn').forEach(b => {
|
||||||
|
b.classList.remove('active');
|
||||||
|
});
|
||||||
|
btn.classList.add('active');
|
||||||
|
|
||||||
|
// Триггерим события change для валидации
|
||||||
|
timeFromField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
timeToField.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
|
});
|
||||||
|
|
||||||
|
presetsContainer.appendChild(btn);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Проверяем текущие значения и активируем соответствующий пресет
|
||||||
|
if (timeFromField.value && timeToField.value) {
|
||||||
|
presets.forEach((preset, index) => {
|
||||||
|
if (timeFromField.value === preset.from && timeToField.value === preset.to) {
|
||||||
|
presetsContainer.children[index].classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Слушаем изменения времени вручную
|
||||||
|
const clearPresets = function() {
|
||||||
|
presetsContainer.querySelectorAll('.delivery-time-preset-btn').forEach(b => {
|
||||||
|
b.classList.remove('active');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
timeFromField.addEventListener('change', clearPresets);
|
||||||
|
timeToField.addEventListener('change', clearPresets);
|
||||||
|
|
||||||
|
// Вставляем пресеты в контейнер
|
||||||
|
container.appendChild(presetsContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Форматирует дату для input type="date" (YYYY-MM-DD)
|
||||||
|
*/
|
||||||
|
function formatDateForInput(date) {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Автоматическая инициализация при загрузке DOM
|
||||||
|
*/
|
||||||
|
function autoInit() {
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Ищем поля по стандартным ID Django форм
|
||||||
|
const dateField = document.getElementById('id_delivery_date');
|
||||||
|
const timeFromField = document.getElementById('id_time_from');
|
||||||
|
const timeToField = document.getElementById('id_time_to');
|
||||||
|
|
||||||
|
if (dateField && timeFromField && timeToField) {
|
||||||
|
initDeliveryDateTime(
|
||||||
|
'id_delivery_date',
|
||||||
|
'id_time_from',
|
||||||
|
'id_time_to'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Экспортируем функции для глобального использования
|
||||||
|
window.DeliveryDateTime = {
|
||||||
|
init: initDeliveryDateTime,
|
||||||
|
autoInit: autoInit
|
||||||
|
};
|
||||||
|
|
||||||
|
// Автоматическая инициализация
|
||||||
|
autoInit();
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
<meta name="csrf-token" content="{{ csrf_token }}">
|
||||||
|
<link rel="stylesheet" href="{% static 'orders/css/delivery_datetime.css' %}">
|
||||||
<style>
|
<style>
|
||||||
/* Скрываем DELETE checkbox */
|
/* Скрываем DELETE checkbox */
|
||||||
input[name$="-DELETE"] {
|
input[name$="-DELETE"] {
|
||||||
@@ -200,34 +201,40 @@
|
|||||||
<h5 class="mb-0">Дата и время доставки</h5>
|
<h5 class="mb-0">Дата и время доставки</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="delivery-datetime-wrapper">
|
||||||
|
<!-- Поля ввода в один ряд -->
|
||||||
|
<div class="row g-3 mb-3">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="mb-3">
|
<label for="{{ form.delivery_date.id_for_label }}" class="form-label">
|
||||||
<label for="{{ form.delivery_date.id_for_label }}" class="form-label">Дата</label>
|
Дата <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
{{ form.delivery_date }}
|
{{ form.delivery_date }}
|
||||||
{% if form.delivery_date.errors %}
|
{% if form.delivery_date.errors %}
|
||||||
<div class="text-danger">{{ form.delivery_date.errors }}</div>
|
<div class="text-danger">{{ form.delivery_date.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="{{ form.time_from.id_for_label }}" class="form-label">
|
||||||
|
Время от
|
||||||
|
</label>
|
||||||
|
{{ form.time_from }}
|
||||||
|
{% if form.time_from.errors %}
|
||||||
|
<div class="text-danger">{{ form.time_from.errors }}</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="mb-3">
|
<label for="{{ form.time_to.id_for_label }}" class="form-label">
|
||||||
<label for="{{ form.delivery_time_start.id_for_label }}" class="form-label">Время от</label>
|
Время до
|
||||||
{{ form.delivery_time_start }}
|
</label>
|
||||||
{% if form.delivery_time_start.errors %}
|
{{ form.time_to }}
|
||||||
<div class="text-danger">{{ form.delivery_time_start.errors }}</div>
|
{% if form.time_to.errors %}
|
||||||
{% endif %}
|
<div class="text-danger">{{ form.time_to.errors }}</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.delivery_time_end.id_for_label }}" class="form-label">Время до</label>
|
|
||||||
{{ form.delivery_time_end }}
|
|
||||||
{% if form.delivery_time_end.errors %}
|
|
||||||
<div class="text-danger">{{ form.delivery_time_end.errors }}</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Кнопки быстрого выбора ниже полей -->
|
||||||
|
<div id="delivery-datetime-quick-buttons"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -910,6 +917,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Delivery Date/Time Widget -->
|
||||||
|
<script src="{% static 'orders/js/delivery_datetime.js' %}"></script>
|
||||||
|
|
||||||
<!-- Unified Transaction Form -->
|
<!-- Unified Transaction Form -->
|
||||||
<script src="{% static 'orders/js/unified_transaction_form.js' %}"></script>
|
<script src="{% static 'orders/js/unified_transaction_form.js' %}"></script>
|
||||||
{% if order.pk %}
|
{% if order.pk %}
|
||||||
@@ -971,24 +981,36 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function syncUIFromCheckbox() {
|
function syncUIFromCheckbox() {
|
||||||
|
if (!isDeliveryCheckbox) return; // Защита от null
|
||||||
|
|
||||||
if (isDeliveryCheckbox.checked) {
|
if (isDeliveryCheckbox.checked) {
|
||||||
document.getElementById('delivery-type-delivery').checked = true;
|
const deliveryRadio = document.getElementById('delivery-type-delivery');
|
||||||
deliveryModeFields.style.display = 'block';
|
if (deliveryRadio) {
|
||||||
pickupFields.style.display = 'none';
|
deliveryRadio.checked = true;
|
||||||
|
}
|
||||||
|
if (deliveryModeFields) deliveryModeFields.style.display = 'block';
|
||||||
|
if (pickupFields) pickupFields.style.display = 'none';
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('delivery-type-pickup').checked = true;
|
const pickupRadio = document.getElementById('delivery-type-pickup');
|
||||||
deliveryModeFields.style.display = 'none';
|
if (pickupRadio) {
|
||||||
pickupFields.style.display = 'block';
|
pickupRadio.checked = true;
|
||||||
|
}
|
||||||
|
if (deliveryModeFields) deliveryModeFields.style.display = 'none';
|
||||||
|
if (pickupFields) pickupFields.style.display = 'block';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчики для кнопок
|
// Обработчики для кнопок
|
||||||
|
if (deliveryTypeRadios && deliveryTypeRadios.length > 0) {
|
||||||
deliveryTypeRadios.forEach(radio => {
|
deliveryTypeRadios.forEach(radio => {
|
||||||
radio.addEventListener('change', syncDeliveryTypeFromRadio);
|
radio.addEventListener('change', syncDeliveryTypeFromRadio);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Инициализация при загрузке
|
// Инициализация при загрузке
|
||||||
|
if (isDeliveryCheckbox) {
|
||||||
syncUIFromCheckbox();
|
syncUIFromCheckbox();
|
||||||
|
}
|
||||||
|
|
||||||
// Показ/скрытие полей получателя
|
// Показ/скрытие полей получателя
|
||||||
const otherRecipientCheckbox = document.getElementById('{{ form.other_recipient.id_for_label }}');
|
const otherRecipientCheckbox = document.getElementById('{{ form.other_recipient.id_for_label }}');
|
||||||
|
|||||||
@@ -131,9 +131,9 @@ def order_create(request):
|
|||||||
delivery_cost = form.cleaned_data.get('delivery_cost', Decimal('0'))
|
delivery_cost = form.cleaned_data.get('delivery_cost', Decimal('0'))
|
||||||
pickup_warehouse = form.cleaned_data.get('pickup_warehouse')
|
pickup_warehouse = form.cleaned_data.get('pickup_warehouse')
|
||||||
|
|
||||||
# Проверяем наличие обязательных полей
|
# Проверяем наличие обязательных полей (время необязательно)
|
||||||
if not all([delivery_type, delivery_date, time_from, time_to]):
|
if not delivery_type or not delivery_date:
|
||||||
raise ValidationError('Необходимо заполнить все поля доставки')
|
raise ValidationError('Необходимо указать способ доставки и дату доставки')
|
||||||
|
|
||||||
# Обрабатываем адрес для курьерской доставки
|
# Обрабатываем адрес для курьерской доставки
|
||||||
address = None
|
address = None
|
||||||
@@ -284,9 +284,9 @@ def order_update(request, order_number):
|
|||||||
delivery_cost = form.cleaned_data.get('delivery_cost', Decimal('0'))
|
delivery_cost = form.cleaned_data.get('delivery_cost', Decimal('0'))
|
||||||
pickup_warehouse = form.cleaned_data.get('pickup_warehouse')
|
pickup_warehouse = form.cleaned_data.get('pickup_warehouse')
|
||||||
|
|
||||||
# Проверяем наличие обязательных полей
|
# Проверяем наличие обязательных полей (время необязательно)
|
||||||
if not all([delivery_type, delivery_date, time_from, time_to]):
|
if not delivery_type or not delivery_date:
|
||||||
raise ValidationError('Необходимо заполнить все поля доставки')
|
raise ValidationError('Необходимо указать способ доставки и дату доставки')
|
||||||
|
|
||||||
# Обрабатываем адрес для курьерской доставки
|
# Обрабатываем адрес для курьерской доставки
|
||||||
address = None
|
address = None
|
||||||
|
|||||||
Reference in New Issue
Block a user