Рефакторинг: перенос логики создания временных комплектов в сервис

Изменения:
- Удалена функция create_temporary_kit из myproject/orders/views.py
- Перенесена в новый сервис myproject/products/services/kit_service.py
- Добавлен API endpoint products:api-temporary-kit-create для создания временных комплектов
- Обновлены URL-ы соответственно

Преимущества:
- Логика временных комплектов теперь находится в соответствующем приложении (products)
- Упрощена архитектура orders приложения
- Сервис может быть переиспользован в других контекстах
- Лучшее разделение ответственности между приложениями

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-10 23:44:05 +03:00
parent 3c0ba70bc8
commit 5d5de1fe31
15 changed files with 471 additions and 150 deletions

View File

@@ -10,6 +10,7 @@
{% for kititem_form in kititem_formset %}
<div class="card mb-2 kititem-form border"
data-form-index="{{ forloop.counter0 }}"
data-product-id="{% if kititem_form.instance.product %}{{ kititem_form.instance.product.id }}{% endif %}"
data-product-price="{% if kititem_form.instance.product %}{{ kititem_form.instance.product.actual_price|default:0 }}{% else %}0{% endif %}">
{{ kititem_form.id }}
<div class="card-body p-2">
@@ -61,7 +62,7 @@
<!-- УДАЛЕНИЕ -->
<div class="col-md-1 text-end">
{% if kititem_form.DELETE %}
<button type="button" class="btn btn-sm btn-link text-danger p-0" onclick="this.previousElementSibling.checked = true; this.closest('.kititem-form').style.display='none';" title="Удалить">
<button type="button" class="btn btn-sm btn-link text-danger p-0" onclick="this.nextElementSibling.checked = true; this.closest('.kititem-form').style.display='none'; if(typeof calculateFinalPrice === 'function') calculateFinalPrice();" title="Удалить">
<i class="bi bi-x-lg"></i>
</button>
{{ kititem_form.DELETE }}

View File

@@ -442,10 +442,23 @@ document.addEventListener('DOMContentLoaded', function() {
return 0;
}
const productId = parseInt(selectElement.value);
const rawValue = selectElement.value;
if (!rawValue) {
console.warn('getProductPrice: no value');
return 0;
}
if (!selectElement.value || isNaN(productId) || productId <= 0) {
console.warn('getProductPrice: no valid product id', selectElement.value);
// Извлекаем числовой ID из значения (может быть "product_123" или "123")
let productId;
if (rawValue.includes('_')) {
const parts = rawValue.split('_');
productId = parseInt(parts[1]);
} else {
productId = parseInt(rawValue);
}
if (isNaN(productId) || productId <= 0) {
console.warn('getProductPrice: invalid product id', rawValue);
return 0;
}
@@ -471,15 +484,19 @@ document.addEventListener('DOMContentLoaded', function() {
}
}
// Пытаемся получить из Select2 option data (сначала actual_price, потом price)
const selectedOption = $(selectElement).find('option:selected');
let priceData = selectedOption.data('actual_price') || selectedOption.data('price');
if (priceData) {
const price = parseFloat(priceData) || 0;
if (price > 0) {
priceCache[productId] = price;
console.log('getProductPrice: from select2 data', productId, price);
return price;
// Пытаемся получить из Select2 data (приоритет: actual_price > price)
const $select = $(selectElement);
const selectedData = $select.select2('data');
if (selectedData && selectedData.length > 0) {
const itemData = selectedData[0];
const priceData = itemData.actual_price || itemData.price;
if (priceData) {
const price = parseFloat(priceData) || 0;
if (price > 0) {
priceCache[productId] = price;
console.log('getProductPrice: from select2 data', productId, price);
return price;
}
}
}
@@ -514,7 +531,12 @@ document.addEventListener('DOMContentLoaded', function() {
$('[name$="-product"]').on('select2:select', async function() {
const form = $(this).closest('.kititem-form');
if (this.value) {
form.attr('data-product-id', this.value);
// Извлекаем числовой ID из "product_123"
let numericId = this.value;
if (this.value.includes('_')) {
numericId = this.value.split('_')[1];
}
form.attr('data-product-id', numericId);
// Загружаем цену и пересчитываем
await getProductPrice(this);
calculateFinalPrice();