feat: Add customer prefill from URL parameter in order creation
- Modified order_create view to read customer from GET parameter - Pass preselected_customer to template context - Template renders select with preselected option for Select2 - Fixed draft creation timing with callback after Select2 initialization - Auto-create draft when customer is preselected from URL - Graceful handling if customer not found or invalid ID
This commit is contained in:
@@ -126,7 +126,15 @@
|
||||
<label for="{{ form.customer.id_for_label }}" class="form-label">
|
||||
Клиент <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.customer }}
|
||||
{% if preselected_customer %}
|
||||
<select name="customer" class="form-select" id="id_customer">
|
||||
<option value="{{ preselected_customer.pk }}" selected data-name="{{ preselected_customer.name }}" data-phone="{{ preselected_customer.phone|default:'' }}" data-email="{{ preselected_customer.email|default:'' }}">
|
||||
{{ preselected_customer.name }}{% if preselected_customer.phone %} ({{ preselected_customer.phone }}){% endif %}
|
||||
</option>
|
||||
</select>
|
||||
{% else %}
|
||||
{{ form.customer }}
|
||||
{% endif %}
|
||||
{% if form.customer.errors %}
|
||||
<div class="text-danger">{{ form.customer.errors }}</div>
|
||||
{% endif %}
|
||||
@@ -565,11 +573,43 @@
|
||||
<div class="card mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Оплата</h5>
|
||||
<button type="button" class="btn btn-sm btn-success" id="add-payment-btn">
|
||||
<i class="bi bi-plus-circle"></i> Добавить платеж
|
||||
</button>
|
||||
<div>
|
||||
<span class="badge bg-{% if order.payment_status == 'paid' %}success{% elif order.payment_status == 'partial' %}warning{% else %}danger{% endif %} me-2">
|
||||
{{ order.get_payment_status_display }}
|
||||
</span>
|
||||
<button type="button" class="btn btn-sm btn-success" id="add-payment-btn">
|
||||
<i class="bi bi-plus-circle"></i> Добавить платеж
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<!-- Блок кошелька клиента -->
|
||||
{% if order.customer %}
|
||||
<div class="alert alert-info d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>Кошелёк клиента:</strong>
|
||||
{% if order.customer.wallet_balance > 0 %}
|
||||
<span class="text-success fw-bold">{{ order.customer.wallet_balance|floatformat:2 }} руб.</span>
|
||||
{% else %}
|
||||
<span class="text-muted">0.00 руб.</span>
|
||||
{% endif %}
|
||||
<span class="ms-3">Остаток к оплате: <strong>{{ order.amount_due|floatformat:2 }} руб.</strong></span>
|
||||
</div>
|
||||
{% if order.customer.wallet_balance > 0 and order.amount_due > 0 %}
|
||||
<div class="d-flex gap-2">
|
||||
<button type="button" class="btn btn-primary btn-sm" id="apply-wallet-max-btn">
|
||||
Учесть максимум
|
||||
</button>
|
||||
<div class="input-group" style="max-width: 280px;">
|
||||
<input type="number" step="0.01" min="0" class="form-control form-control-sm" id="apply-wallet-amount-input" placeholder="Сумма из кошелька">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" id="apply-wallet-amount-btn">Учесть сумму</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Скрытые поля для formset management -->
|
||||
{{ payment_formset.management_form }}
|
||||
|
||||
@@ -639,6 +679,9 @@
|
||||
<label class="form-label">Способ оплаты</label>
|
||||
<select name="payments-__prefix__-payment_method" class="form-select" id="id_payments-__prefix__-payment_method">
|
||||
<option value="">---------</option>
|
||||
{% for pm in payment_formset.forms.0.fields.payment_method.queryset %}
|
||||
<option value="{{ pm.id }}">{{ pm.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -675,6 +718,98 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const walletBalance = parseFloat("{{ order.customer.wallet_balance|default:'0' }}".replace(',', '.')) || 0;
|
||||
const amountDue = parseFloat("{{ order.amount_due|default:'0' }}".replace(',', '.')) || 0;
|
||||
|
||||
function addPaymentRow() {
|
||||
const totalFormsInput = document.querySelector('input[name="payments-TOTAL_FORMS"]');
|
||||
const idx = parseInt(totalFormsInput.value, 10);
|
||||
const tpl = document.getElementById('empty-payment-form-template').innerHTML.replace(/__prefix__/g, idx);
|
||||
const container = document.getElementById('payments-container');
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.innerHTML = tpl.trim();
|
||||
container.appendChild(wrapper.firstElementChild);
|
||||
totalFormsInput.value = String(idx + 1);
|
||||
return container.querySelector(`.payment-form[data-form-index="${idx}"]`);
|
||||
}
|
||||
|
||||
function selectAccountBalance(selectEl) {
|
||||
if (!selectEl) return;
|
||||
const options = Array.from(selectEl.options);
|
||||
const target = options.find(o => o.textContent.trim() === 'С баланса счёта');
|
||||
if (target) {
|
||||
selectEl.value = target.value;
|
||||
selectEl.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
}
|
||||
}
|
||||
|
||||
function applyWallet(amount) {
|
||||
if (!amount || amount <= 0) {
|
||||
alert('Введите сумму больше 0.');
|
||||
return;
|
||||
}
|
||||
if (amount > walletBalance) {
|
||||
alert(`Недостаточно средств в кошельке. Доступно ${walletBalance.toFixed(2)} руб.`);
|
||||
return;
|
||||
}
|
||||
if (amount > amountDue) {
|
||||
alert(`Сумма превышает остаток к оплате (${amountDue.toFixed(2)} руб.).`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Найти существующую форму платежа без метода, иначе добавить новую
|
||||
let formEl = document.querySelector('#payments-container .payment-form:last-child');
|
||||
if (!formEl) {
|
||||
formEl = addPaymentRow();
|
||||
}
|
||||
const sel = formEl.querySelector('select[id^="id_payments-"][id$="-payment_method"]');
|
||||
const amt = formEl.querySelector('input[id^="id_payments-"][id$="-amount"]');
|
||||
|
||||
selectAccountBalance(sel);
|
||||
if (amt) {
|
||||
amt.value = amount.toFixed(2);
|
||||
amt.setAttribute('max', Math.min(walletBalance, amountDue).toFixed(2));
|
||||
}
|
||||
}
|
||||
|
||||
const applyMaxBtn = document.getElementById('apply-wallet-max-btn');
|
||||
const applyAmountBtn = document.getElementById('apply-wallet-amount-btn');
|
||||
const amountInput = document.getElementById('apply-wallet-amount-input');
|
||||
|
||||
if (applyMaxBtn) {
|
||||
applyMaxBtn.addEventListener('click', function() {
|
||||
const maxUsable = Math.min(walletBalance, amountDue);
|
||||
applyWallet(maxUsable);
|
||||
});
|
||||
}
|
||||
if (applyAmountBtn && amountInput) {
|
||||
applyAmountBtn.addEventListener('click', function() {
|
||||
const val = parseFloat((amountInput.value || '0').replace(',', '.')) || 0;
|
||||
applyWallet(val);
|
||||
});
|
||||
}
|
||||
|
||||
// Автозаполнение при выборе "С баланса счёта"
|
||||
document.getElementById('payments-container').addEventListener('change', function(e) {
|
||||
const sel = e.target;
|
||||
if (sel.tagName === 'SELECT') {
|
||||
const label = sel.options[sel.selectedIndex]?.textContent?.trim() || '';
|
||||
if (label === 'С баланса счёта') {
|
||||
const wrap = sel.closest('.payment-form');
|
||||
const amt = wrap.querySelector('input[id^="id_payments-"][id$="-amount"]');
|
||||
if (amt) {
|
||||
const maxUsable = Math.min(walletBalance, amountDue);
|
||||
amt.value = maxUsable.toFixed(2);
|
||||
amt.setAttribute('max', maxUsable.toFixed(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">Дополнительно</h5>
|
||||
@@ -781,6 +916,14 @@ function initCustomerSelect2() {
|
||||
console.log('Значение восстановлено:', $customerSelect.val());
|
||||
}
|
||||
|
||||
// Уведомляем draft-creator.js что Select2 готов и есть предзаполненное значение
|
||||
if (currentValue && window.DraftCreator) {
|
||||
console.log('7. Уведомляем DraftCreator о предзаполненном клиенте');
|
||||
setTimeout(function() {
|
||||
window.DraftCreator.triggerDraftCreation();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Слушаем события
|
||||
$customerSelect.on('select2:open', function(e) {
|
||||
console.log('7. Dropdown открыт');
|
||||
|
||||
Reference in New Issue
Block a user