feat: Add Product Kit creation and editing functionality with new views and templates.
This commit is contained in:
@@ -76,7 +76,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Загрузка с устройства -->
|
<!-- Загрузка с устройства -->
|
||||||
<input type="file" name="photos" accept="image/*" multiple class="form-control form-control-sm" id="id_photos">
|
<input type="file" name="photos" accept="image/*" multiple class="form-control form-control-sm"
|
||||||
|
id="id_photos">
|
||||||
<div id="photoPreviewContainer" class="mt-2" style="display: none;">
|
<div id="photoPreviewContainer" class="mt-2" style="display: none;">
|
||||||
<div id="photoPreview" class="row g-1"></div>
|
<div id="photoPreview" class="row g-1"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -97,7 +98,8 @@
|
|||||||
<div class="card-body p-3">
|
<div class="card-body p-3">
|
||||||
<p class="small text-muted mb-3">
|
<p class="small text-muted mb-3">
|
||||||
Сгенерируйте привлекательное название для вашего букета автоматически
|
Сгенерируйте привлекательное название для вашего букета автоматически
|
||||||
<br><span class="badge bg-secondary mt-1">В базе: <span id="bouquetNamesCount">{{ bouquet_names_count }}</span> названий</span>
|
<br><span class="badge bg-secondary mt-1">В базе: <span id="bouquetNamesCount">{{
|
||||||
|
bouquet_names_count }}</span> названий</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="d-flex gap-2 mb-4">
|
<div class="d-flex gap-2 mb-4">
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm" id="populateNamesBtn">
|
<button type="button" class="btn btn-outline-primary btn-sm" id="populateNamesBtn">
|
||||||
@@ -111,27 +113,36 @@
|
|||||||
<!-- Предложения названий -->
|
<!-- Предложения названий -->
|
||||||
<div class="name-suggestions">
|
<div class="name-suggestions">
|
||||||
<!-- Строка 1 -->
|
<!-- Строка 1 -->
|
||||||
<div class="d-flex justify-content-between align-items-center py-2 border-bottom name-row" data-name-id="">
|
<div class="d-flex justify-content-between align-items-center py-2 border-bottom name-row"
|
||||||
|
data-name-id="">
|
||||||
<span class="text-muted small name-text">-</span>
|
<span class="text-muted small name-text">-</span>
|
||||||
<div class="d-flex gap-1 name-buttons" style="display: none;">
|
<div class="d-flex gap-1 name-buttons" style="display: none;">
|
||||||
<button type="button" class="btn btn-success btn-xs btn-take-name">Взять</button>
|
<button type="button"
|
||||||
<button type="button" class="btn btn-outline-danger btn-xs btn-delete-name">Удалить</button>
|
class="btn btn-success btn-xs btn-take-name">Взять</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-danger btn-xs btn-delete-name">Удалить</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Строка 2 -->
|
<!-- Строка 2 -->
|
||||||
<div class="d-flex justify-content-between align-items-center py-2 border-bottom name-row" data-name-id="">
|
<div class="d-flex justify-content-between align-items-center py-2 border-bottom name-row"
|
||||||
|
data-name-id="">
|
||||||
<span class="text-muted small name-text">-</span>
|
<span class="text-muted small name-text">-</span>
|
||||||
<div class="d-flex gap-1 name-buttons" style="display: none;">
|
<div class="d-flex gap-1 name-buttons" style="display: none;">
|
||||||
<button type="button" class="btn btn-success btn-xs btn-take-name">Взять</button>
|
<button type="button"
|
||||||
<button type="button" class="btn btn-outline-danger btn-xs btn-delete-name">Удалить</button>
|
class="btn btn-success btn-xs btn-take-name">Взять</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-danger btn-xs btn-delete-name">Удалить</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Строка 3 -->
|
<!-- Строка 3 -->
|
||||||
<div class="d-flex justify-content-between align-items-center py-2 name-row" data-name-id="">
|
<div class="d-flex justify-content-between align-items-center py-2 name-row"
|
||||||
|
data-name-id="">
|
||||||
<span class="text-muted small name-text">-</span>
|
<span class="text-muted small name-text">-</span>
|
||||||
<div class="d-flex gap-1 name-buttons" style="display: none;">
|
<div class="d-flex gap-1 name-buttons" style="display: none;">
|
||||||
<button type="button" class="btn btn-success btn-xs btn-take-name">Взять</button>
|
<button type="button"
|
||||||
<button type="button" class="btn btn-outline-danger btn-xs btn-delete-name">Удалить</button>
|
class="btn btn-success btn-xs btn-take-name">Взять</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-outline-danger btn-xs btn-delete-name">Удалить</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,8 +165,10 @@
|
|||||||
<!-- Базовая цена (отображение) -->
|
<!-- Базовая цена (отображение) -->
|
||||||
<div class="mb-3 p-2 rounded" style="background: #f8f9fa; border-left: 3px solid #6c757d;">
|
<div class="mb-3 p-2 rounded" style="background: #f8f9fa; border-left: 3px solid #6c757d;">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<span class="text-muted small"><i class="bi bi-calculator"></i> Сумма цен компонентов:</span>
|
<span class="text-muted small"><i class="bi bi-calculator"></i> Сумма цен
|
||||||
<span id="basePriceDisplay" class="fw-semibold" style="font-size: 1.1rem;">0.00 руб.</span>
|
компонентов:</span>
|
||||||
|
<span id="basePriceDisplay" class="fw-semibold" style="font-size: 1.1rem;">0.00
|
||||||
|
руб.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -169,13 +182,15 @@
|
|||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<input type="number" id="id_increase_percent" class="form-control" placeholder="%" step="0.01" min="0">
|
<input type="number" id="id_increase_percent" class="form-control"
|
||||||
|
placeholder="%" step="0.01" min="0">
|
||||||
<span class="input-group-text">%</span>
|
<span class="input-group-text">%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<input type="number" id="id_increase_amount" class="form-control" placeholder="руб" step="0.01" min="0">
|
<input type="number" id="id_increase_amount" class="form-control"
|
||||||
|
placeholder="руб" step="0.01" min="0">
|
||||||
<span class="input-group-text">руб</span>
|
<span class="input-group-text">руб</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -191,13 +206,15 @@
|
|||||||
<div class="row g-2">
|
<div class="row g-2">
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<input type="number" id="id_decrease_percent" class="form-control" placeholder="%" step="0.01" min="0">
|
<input type="number" id="id_decrease_percent" class="form-control"
|
||||||
|
placeholder="%" step="0.01" min="0">
|
||||||
<span class="input-group-text">%</span>
|
<span class="input-group-text">%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
<input type="number" id="id_decrease_amount" class="form-control" placeholder="руб" step="0.01" min="0">
|
<input type="number" id="id_decrease_amount" class="form-control"
|
||||||
|
placeholder="руб" step="0.01" min="0">
|
||||||
<span class="input-group-text">руб</span>
|
<span class="input-group-text">руб</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -210,8 +227,10 @@
|
|||||||
<!-- Итоговая цена -->
|
<!-- Итоговая цена -->
|
||||||
<div class="p-2 rounded" style="background: #e7f5e7; border-left: 3px solid #198754;">
|
<div class="p-2 rounded" style="background: #e7f5e7; border-left: 3px solid #198754;">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
<span class="text-dark small"><i class="bi bi-check-circle me-1"></i><strong>Итоговая цена:</strong></span>
|
<span class="text-dark small"><i class="bi bi-check-circle me-1"></i><strong>Итоговая
|
||||||
<span id="finalPriceDisplay" class="fw-bold" style="font-size: 1.3rem; color: #198754;">0.00 руб.</span>
|
цена:</strong></span>
|
||||||
|
<span id="finalPriceDisplay" class="fw-bold"
|
||||||
|
style="font-size: 1.3rem; color: #198754;">0.00 руб.</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -227,7 +246,8 @@
|
|||||||
<h6 class="mb-2 text-muted"><i class="bi bi-tag-discount"></i> Цена со скидкой</h6>
|
<h6 class="mb-2 text-muted"><i class="bi bi-tag-discount"></i> Цена со скидкой</h6>
|
||||||
<label class="form-label small mb-1">{{ form.sale_price.label }}</label>
|
<label class="form-label small mb-1">{{ form.sale_price.label }}</label>
|
||||||
{{ form.sale_price }}
|
{{ form.sale_price }}
|
||||||
<small class="form-text text-muted">Если указана, будет использоваться вместо расчетной цены</small>
|
<small class="form-text text-muted">Если указана, будет использоваться вместо расчетной
|
||||||
|
цены</small>
|
||||||
{% if form.sale_price.errors %}
|
{% if form.sale_price.errors %}
|
||||||
<div class="text-danger small mt-1">{{ form.sale_price.errors }}</div>
|
<div class="text-danger small mt-1">{{ form.sale_price.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -296,7 +316,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sticky Footer -->
|
<!-- Sticky Footer -->
|
||||||
<div class="sticky-bottom bg-white border-top mt-4 p-3 d-flex justify-content-between align-items-center shadow-sm">
|
<div
|
||||||
|
class="sticky-bottom bg-white border-top mt-4 p-3 d-flex justify-content-between align-items-center shadow-sm">
|
||||||
<a href="{% url 'products:products-list' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'products:products-list' %}" class="btn btn-outline-secondary">
|
||||||
Отмена
|
Отмена
|
||||||
</a>
|
</a>
|
||||||
@@ -425,6 +446,7 @@
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(-10px);
|
transform: translateY(-10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
@@ -528,7 +550,9 @@
|
|||||||
|
|
||||||
/* Адаптивность */
|
/* Адаптивность */
|
||||||
@media (max-width: 991px) {
|
@media (max-width: 991px) {
|
||||||
.col-lg-8, .col-lg-4 {
|
|
||||||
|
.col-lg-8,
|
||||||
|
.col-lg-4 {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -537,6 +561,10 @@
|
|||||||
<!-- Select2 инициализация -->
|
<!-- Select2 инициализация -->
|
||||||
{% include 'products/includes/select2-product-init.html' %}
|
{% include 'products/includes/select2-product-init.html' %}
|
||||||
|
|
||||||
|
{{ selected_products|default:"{}"|json_script:"selected-products-data" }}
|
||||||
|
{{ selected_variants|default:"{}"|json_script:"selected-variants-data" }}
|
||||||
|
{{ selected_sales_units|default:"{}"|json_script:"selected-sales-units-data" }}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
// ========== УПРАВЛЕНИЕ ЦЕНОЙ КОМПЛЕКТА ==========
|
// ========== УПРАВЛЕНИЕ ЦЕНОЙ КОМПЛЕКТА ==========
|
||||||
@@ -550,6 +578,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const finalPriceDisplay = document.getElementById('finalPriceDisplay');
|
const finalPriceDisplay = document.getElementById('finalPriceDisplay');
|
||||||
|
|
||||||
let basePrice = 0;
|
let basePrice = 0;
|
||||||
|
let activeUpdates = 0; // Счетчик активных обновлений
|
||||||
|
|
||||||
// Кэш цен товаров для быстрого доступа
|
// Кэш цен товаров для быстрого доступа
|
||||||
const priceCache = {};
|
const priceCache = {};
|
||||||
@@ -743,6 +772,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Функция для обновления списка единиц продажи при выборе товара
|
// Функция для обновления списка единиц продажи при выборе товара
|
||||||
async function updateSalesUnitsOptions(salesUnitSelect, productValue) {
|
async function updateSalesUnitsOptions(salesUnitSelect, productValue) {
|
||||||
|
activeUpdates++; // Начинаем обновление
|
||||||
|
try {
|
||||||
|
// Сохраняем текущее значение перед очисткой (важно для редактирования и копирования)
|
||||||
|
let targetValue = salesUnitSelect.value;
|
||||||
|
|
||||||
|
// Если значения нет, проверяем preloaded данные (фаллбэк для инициализации)
|
||||||
|
if (!targetValue) {
|
||||||
|
const fieldName = salesUnitSelect.name;
|
||||||
|
if (selectedSalesUnits && selectedSalesUnits[fieldName]) {
|
||||||
|
targetValue = selectedSalesUnits[fieldName].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Очищаем текущие опции
|
// Очищаем текущие опции
|
||||||
salesUnitSelect.innerHTML = '<option value="">---------</option>';
|
salesUnitSelect.innerHTML = '<option value="">---------</option>';
|
||||||
salesUnitSelect.disabled = true;
|
salesUnitSelect.disabled = true;
|
||||||
@@ -761,7 +803,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isNaN(productId) || productId <= 0) {
|
if (isNaN(productId) || productId <= 0) {
|
||||||
console.warn('updateSalesUnitsOptions: invalid product id', productValue);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,13 +824,25 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
salesUnitSelect.appendChild(option);
|
salesUnitSelect.appendChild(option);
|
||||||
});
|
});
|
||||||
salesUnitSelect.disabled = false;
|
salesUnitSelect.disabled = false;
|
||||||
// Обновляем Select2
|
|
||||||
|
// Восстанавливаем значение
|
||||||
|
if (targetValue) {
|
||||||
|
$(salesUnitSelect).val(targetValue).trigger('change');
|
||||||
|
} else {
|
||||||
|
// Обновляем Select2 без значения
|
||||||
$(salesUnitSelect).trigger('change');
|
$(salesUnitSelect).trigger('change');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching sales units:', error);
|
console.error('Error fetching sales units:', error);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
activeUpdates--; // Завершаем обновление
|
||||||
|
if (activeUpdates === 0) {
|
||||||
|
calculateFinalPrice();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем data-product-id и загружаем цену при выборе товара
|
// Обновляем data-product-id и загружаем цену при выборе товара
|
||||||
@@ -809,8 +862,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (salesUnitSelect) {
|
if (salesUnitSelect) {
|
||||||
await updateSalesUnitsOptions(salesUnitSelect, this.value);
|
await updateSalesUnitsOptions(salesUnitSelect, this.value);
|
||||||
}
|
}
|
||||||
calculateFinalPrice();
|
|
||||||
}
|
}
|
||||||
|
calculateFinalPrice();
|
||||||
}).on('select2:unselect', function () {
|
}).on('select2:unselect', function () {
|
||||||
const form = $(this).closest('.kititem-form');
|
const form = $(this).closest('.kititem-form');
|
||||||
// Очищаем список единиц продажи
|
// Очищаем список единиц продажи
|
||||||
@@ -885,6 +938,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Функция для расчета финальной цены
|
// Функция для расчета финальной цены
|
||||||
async function calculateFinalPrice() {
|
async function calculateFinalPrice() {
|
||||||
|
// Если идут обновления - не считаем, ждем их завершения
|
||||||
|
if (activeUpdates > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Получаем базовую цену (сумма всех компонентов)
|
// Получаем базовую цену (сумма всех компонентов)
|
||||||
let newBasePrice = 0;
|
let newBasePrice = 0;
|
||||||
const formsContainer = document.getElementById('kititem-forms');
|
const formsContainer = document.getElementById('kititem-forms');
|
||||||
@@ -1060,8 +1118,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Инициальный расчет (асинхронно)
|
// Инициальный расчет не нужен, так как он выполняется по событиям изменения полей
|
||||||
calculateFinalPrice();
|
// и после завершения загрузки единиц продажи
|
||||||
|
|
||||||
// ========== SELECT2 ИНИЦИАЛИЗАЦИЯ ==========
|
// ========== SELECT2 ИНИЦИАЛИЗАЦИЯ ==========
|
||||||
function initSelect2(element, type, preloadedData) {
|
function initSelect2(element, type, preloadedData) {
|
||||||
@@ -1072,9 +1130,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedProducts = {{ selected_products|default:"{}"|safe }};
|
const selectedProducts = JSON.parse(document.getElementById('selected-products-data').textContent || '{}');
|
||||||
const selectedVariants = {{ selected_variants|default:"{}"|safe }};
|
const selectedVariants = JSON.parse(document.getElementById('selected-variants-data').textContent || '{}');
|
||||||
const selectedSalesUnits = {{ selected_sales_units|default:"{}"|safe }};
|
const selectedSalesUnits = JSON.parse(document.getElementById('selected-sales-units-data').textContent || '{}');
|
||||||
|
|
||||||
$('[name$="-product"]').each(function () {
|
$('[name$="-product"]').each(function () {
|
||||||
const fieldName = $(this).attr('name');
|
const fieldName = $(this).attr('name');
|
||||||
|
|||||||
@@ -506,6 +506,9 @@
|
|||||||
<a href="{% url 'products:productkit-detail' object.pk %}" class="btn btn-outline-secondary">
|
<a href="{% url 'products:productkit-detail' object.pk %}" class="btn btn-outline-secondary">
|
||||||
Отмена
|
Отмена
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url 'products:productkit-create' %}?copy_from={{ object.pk }}" class="btn btn-warning text-white mx-2">
|
||||||
|
<i class="bi bi-files me-1"></i>Копировать комплект
|
||||||
|
</a>
|
||||||
<button type="submit" class="btn btn-primary px-4">
|
<button type="submit" class="btn btn-primary px-4">
|
||||||
<i class="bi bi-check-circle me-1"></i>Сохранить изменения
|
<i class="bi bi-check-circle me-1"></i>Сохранить изменения
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from django.shortcuts import redirect
|
|||||||
from django.db import transaction, IntegrityError
|
from django.db import transaction, IntegrityError
|
||||||
|
|
||||||
from user_roles.mixins import ManagerOwnerRequiredMixin
|
from user_roles.mixins import ManagerOwnerRequiredMixin
|
||||||
from ..models import ProductKit, ProductCategory, ProductTag, ProductKitPhoto, Product, ProductVariantGroup, BouquetName
|
from ..models import ProductKit, ProductCategory, ProductTag, ProductKitPhoto, Product, ProductVariantGroup, BouquetName, ProductSalesUnit
|
||||||
from ..forms import ProductKitForm, KitItemFormSetCreate, KitItemFormSetUpdate
|
from ..forms import ProductKitForm, KitItemFormSetCreate, KitItemFormSetUpdate
|
||||||
from .utils import handle_photos
|
from .utils import handle_photos
|
||||||
|
|
||||||
@@ -97,6 +97,28 @@ class ProductKitCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, Create
|
|||||||
form_class = ProductKitForm
|
form_class = ProductKitForm
|
||||||
template_name = 'products/productkit_create.html'
|
template_name = 'products/productkit_create.html'
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
initial = super().get_initial()
|
||||||
|
copy_id = self.request.GET.get('copy_from')
|
||||||
|
if copy_id:
|
||||||
|
try:
|
||||||
|
kit = ProductKit.objects.get(pk=copy_id)
|
||||||
|
initial.update({
|
||||||
|
'name': f"{kit.name} (Копия)",
|
||||||
|
'description': kit.description,
|
||||||
|
'short_description': kit.short_description,
|
||||||
|
'categories': list(kit.categories.values_list('pk', flat=True)),
|
||||||
|
'tags': list(kit.tags.values_list('pk', flat=True)),
|
||||||
|
'sale_price': kit.sale_price,
|
||||||
|
'price_adjustment_type': kit.price_adjustment_type,
|
||||||
|
'price_adjustment_value': kit.price_adjustment_value,
|
||||||
|
'external_category': kit.external_category,
|
||||||
|
'status': 'active', # Default to active for new kits
|
||||||
|
})
|
||||||
|
except ProductKit.DoesNotExist:
|
||||||
|
pass
|
||||||
|
return initial
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Обрабатываем POST данные и очищаем ID товаров/комплектов от префиксов.
|
Обрабатываем POST данные и очищаем ID товаров/комплектов от префиксов.
|
||||||
@@ -132,7 +154,6 @@ class ProductKitCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, Create
|
|||||||
context['kititem_formset'] = KitItemFormSetCreate(self.request.POST, prefix='kititem')
|
context['kititem_formset'] = KitItemFormSetCreate(self.request.POST, prefix='kititem')
|
||||||
|
|
||||||
# При ошибке валидации: извлекаем выбранные товары для предзагрузки в Select2
|
# При ошибке валидации: извлекаем выбранные товары для предзагрузки в Select2
|
||||||
from ..models import Product, ProductVariantGroup, ProductSalesUnit
|
|
||||||
selected_products = {}
|
selected_products = {}
|
||||||
selected_variants = {}
|
selected_variants = {}
|
||||||
selected_sales_units = {}
|
selected_sales_units = {}
|
||||||
@@ -194,9 +215,88 @@ class ProductKitCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, Create
|
|||||||
context['selected_products'] = selected_products
|
context['selected_products'] = selected_products
|
||||||
context['selected_variants'] = selected_variants
|
context['selected_variants'] = selected_variants
|
||||||
context['selected_sales_units'] = selected_sales_units
|
context['selected_sales_units'] = selected_sales_units
|
||||||
|
else:
|
||||||
|
# COPY KIT LOGIC
|
||||||
|
copy_id = self.request.GET.get('copy_from')
|
||||||
|
initial_items = []
|
||||||
|
selected_products = {}
|
||||||
|
selected_variants = {}
|
||||||
|
selected_sales_units = {}
|
||||||
|
|
||||||
|
if copy_id:
|
||||||
|
try:
|
||||||
|
source_kit = ProductKit.objects.get(pk=copy_id)
|
||||||
|
for item in source_kit.kit_items.all():
|
||||||
|
item_data = {
|
||||||
|
'quantity': item.quantity,
|
||||||
|
# Delete flag is false by default
|
||||||
|
}
|
||||||
|
|
||||||
|
form_prefix = f"kititem-{len(initial_items)}"
|
||||||
|
|
||||||
|
if item.product:
|
||||||
|
item_data['product'] = item.product
|
||||||
|
# Select2 prefill
|
||||||
|
product = item.product
|
||||||
|
text = product.name
|
||||||
|
if product.sku:
|
||||||
|
text += f" ({product.sku})"
|
||||||
|
actual_price = product.sale_price if product.sale_price else product.price
|
||||||
|
selected_products[f"{form_prefix}-product"] = {
|
||||||
|
'id': product.id,
|
||||||
|
'text': text,
|
||||||
|
'price': str(product.price) if product.price else None,
|
||||||
|
'actual_price': str(actual_price) if actual_price else '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.sales_unit:
|
||||||
|
item_data['sales_unit'] = item.sales_unit
|
||||||
|
# Select2 prefill
|
||||||
|
sales_unit = item.sales_unit
|
||||||
|
text = f"{sales_unit.name} ({sales_unit.product.name})"
|
||||||
|
actual_price = sales_unit.sale_price if sales_unit.sale_price else sales_unit.price
|
||||||
|
selected_sales_units[f"{form_prefix}-sales_unit"] = {
|
||||||
|
'id': sales_unit.id,
|
||||||
|
'text': text,
|
||||||
|
'price': str(sales_unit.price) if sales_unit.price else None,
|
||||||
|
'actual_price': str(actual_price) if actual_price else '0'
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.variant_group:
|
||||||
|
item_data['variant_group'] = item.variant_group
|
||||||
|
# Select2 prefill
|
||||||
|
variant_group = ProductVariantGroup.objects.prefetch_related(
|
||||||
|
'items__product'
|
||||||
|
).get(id=item.variant_group.id)
|
||||||
|
variant_price = variant_group.price or 0
|
||||||
|
count = variant_group.items.count()
|
||||||
|
selected_variants[f"{form_prefix}-variant_group"] = {
|
||||||
|
'id': variant_group.id,
|
||||||
|
'text': f"{variant_group.name} ({count} вариантов)",
|
||||||
|
'price': str(variant_price),
|
||||||
|
'actual_price': str(variant_price),
|
||||||
|
'type': 'variant',
|
||||||
|
'count': count
|
||||||
|
}
|
||||||
|
|
||||||
|
initial_items.append(item_data)
|
||||||
|
except ProductKit.DoesNotExist:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if initial_items:
|
||||||
|
context['kititem_formset'] = KitItemFormSetCreate(
|
||||||
|
prefix='kititem',
|
||||||
|
initial=initial_items
|
||||||
|
)
|
||||||
|
context['kititem_formset'].extra = len(initial_items)
|
||||||
else:
|
else:
|
||||||
context['kititem_formset'] = KitItemFormSetCreate(prefix='kititem')
|
context['kititem_formset'] = KitItemFormSetCreate(prefix='kititem')
|
||||||
|
|
||||||
|
# Pass Select2 data to context
|
||||||
|
context['selected_products'] = selected_products
|
||||||
|
context['selected_variants'] = selected_variants
|
||||||
|
context['selected_sales_units'] = selected_sales_units
|
||||||
|
|
||||||
# Количество названий букетов в базе
|
# Количество названий букетов в базе
|
||||||
context['bouquet_names_count'] = BouquetName.objects.count()
|
context['bouquet_names_count'] = BouquetName.objects.count()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user