Fix: количество — только целое 0–99 (модели, формы, JS)
Made-with: Cursor
This commit is contained in:
10
HISTORY.md
10
HISTORY.md
@@ -1,5 +1,15 @@
|
||||
# История изменений ERP WaterSurf
|
||||
|
||||
## 2025-02-26 15:50 UTC – Количество: только целое число 0–99
|
||||
|
||||
**Проблема**: Поле «Количество» допускало дробные значения и числа больше двухзначного; требовалось ограничить целыми числами от 0 до 99 везде.
|
||||
|
||||
**Решение**: В моделях **CustomerOrderItem** и **SupplierOrderItem** поле `quantity` переведено на `decimal_places=0` и добавлены валидаторы `MinValueValidator(0)` и `MaxValueValidator(99)`. В формах заказа клиента и заказа поставщику поле «Количество» переопределено на `IntegerField(min_value=0, max_value=99)` с виджетом `step=1`. В шаблонах заказов в JS добавлена функция `parseQty()` (целое 0–99), расчёт суммы строки использует её для количества; при потере фокуса поле количества приводится к целому 0–99; при добавлении новой строки количество устанавливается в 1.
|
||||
|
||||
**Изменения**: documents/models.py (quantity: decimal_places=0, validators), documents/forms.py (IntegerField для quantity), order_form.html и supplier_order_form.html (parseQty, focusout, клон с quantity=1), миграция 0003_quantity_integer_0_99.
|
||||
|
||||
---
|
||||
|
||||
## 2025-02-26 00:50 UTC – Форма заказа клиента: сетка, компактные поля, ширина
|
||||
|
||||
**Проблема**: На форме заказа клиента поле «Номер» визуально смещено вправо; высота полей и отступы между строками слишком большие; поля «Вид заказа», «Организация», «Клиент», «Автор» узкие.
|
||||
|
||||
@@ -13,22 +13,32 @@ from django.forms import inlineformset_factory
|
||||
|
||||
|
||||
class CustomerOrderItemForm(forms.ModelForm):
|
||||
quantity = forms.IntegerField(
|
||||
min_value=0,
|
||||
max_value=99,
|
||||
widget=forms.NumberInput(attrs={"size": 3, "min": 0, "max": 99, "step": 1, "style": "width: 4ch"}),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CustomerOrderItem
|
||||
fields = ("product", "price", "currency", "quantity")
|
||||
widgets = {
|
||||
"price": forms.TextInput(attrs={"inputmode": "decimal", "class": "ws-price-input"}),
|
||||
"quantity": forms.NumberInput(attrs={"size": 3, "min": 0, "max": 99, "style": "width: 4ch"}),
|
||||
}
|
||||
|
||||
|
||||
class SupplierOrderItemForm(forms.ModelForm):
|
||||
quantity = forms.IntegerField(
|
||||
min_value=0,
|
||||
max_value=99,
|
||||
widget=forms.NumberInput(attrs={"size": 3, "min": 0, "max": 99, "step": 1, "style": "width: 4ch"}),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SupplierOrderItem
|
||||
fields = ("product", "price", "currency", "quantity")
|
||||
widgets = {
|
||||
"price": forms.TextInput(attrs={"inputmode": "decimal", "class": "ws-price-input"}),
|
||||
"quantity": forms.NumberInput(attrs={"size": 3, "min": 0, "max": 99, "style": "width: 4ch"}),
|
||||
}
|
||||
|
||||
|
||||
|
||||
25
app/documents/migrations/0003_quantity_integer_0_99.py
Normal file
25
app/documents/migrations/0003_quantity_integer_0_99.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.2.11 on 2026-02-26 15:47
|
||||
|
||||
import django.core.validators
|
||||
from decimal import Decimal
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('documents', '0002_add_customer_order_links'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='customerorderitem',
|
||||
name='quantity',
|
||||
field=models.DecimalField(decimal_places=0, default=Decimal('1'), max_digits=18, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(99)], verbose_name='Количество'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplierorderitem',
|
||||
name='quantity',
|
||||
field=models.DecimalField(decimal_places=0, default=Decimal('1'), max_digits=18, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(99)], verbose_name='Количество'),
|
||||
),
|
||||
]
|
||||
@@ -3,6 +3,7 @@
|
||||
from decimal import Decimal
|
||||
from django.db import models
|
||||
from django.db.models import Sum
|
||||
from django.core.validators import MinValueValidator, MaxValueValidator
|
||||
from references.models import Currency, OrderKind, Client, Organization, Supplier, Employee, CashAccount, Product
|
||||
|
||||
class CustomerOrder(models.Model):
|
||||
@@ -43,7 +44,13 @@ class CustomerOrderItem(models.Model):
|
||||
product = models.ForeignKey(Product, on_delete=models.PROTECT, verbose_name="Товар")
|
||||
price = models.DecimalField("Цена", max_digits=18, decimal_places=2, default=Decimal("0"))
|
||||
currency = models.ForeignKey(Currency, on_delete=models.PROTECT, verbose_name="Валюта", null=True, blank=True)
|
||||
quantity = models.DecimalField("Количество", max_digits=18, decimal_places=4, default=Decimal("1"))
|
||||
quantity = models.DecimalField(
|
||||
"Количество",
|
||||
max_digits=18,
|
||||
decimal_places=0,
|
||||
default=Decimal("1"),
|
||||
validators=[MinValueValidator(0), MaxValueValidator(99)],
|
||||
)
|
||||
amount = models.DecimalField("Стоимость", max_digits=18, decimal_places=2, default=Decimal("0"), editable=False)
|
||||
class Meta:
|
||||
verbose_name = "Строка заказа клиента"
|
||||
@@ -77,7 +84,13 @@ class SupplierOrderItem(models.Model):
|
||||
product = models.ForeignKey(Product, on_delete=models.PROTECT, verbose_name="Товар")
|
||||
price = models.DecimalField("Цена", max_digits=18, decimal_places=2, default=Decimal("0"))
|
||||
currency = models.ForeignKey(Currency, on_delete=models.PROTECT, verbose_name="Валюта", null=True, blank=True)
|
||||
quantity = models.DecimalField("Количество", max_digits=18, decimal_places=4, default=Decimal("1"))
|
||||
quantity = models.DecimalField(
|
||||
"Количество",
|
||||
max_digits=18,
|
||||
decimal_places=0,
|
||||
default=Decimal("1"),
|
||||
validators=[MinValueValidator(0), MaxValueValidator(99)],
|
||||
)
|
||||
amount = models.DecimalField("Стоимость", max_digits=18, decimal_places=2, default=Decimal("0"), editable=False)
|
||||
class Meta:
|
||||
verbose_name = "Строка заказа поставщику"
|
||||
|
||||
@@ -112,11 +112,16 @@
|
||||
function parseNum(s) {
|
||||
return parseFloat(String(s).replace(/\s/g, '').replace(',', '.')) || 0;
|
||||
}
|
||||
function parseQty(s) {
|
||||
var n = parseInt(String(s).replace(/\s/g, ''), 10);
|
||||
if (isNaN(n) || n < 0) return 0;
|
||||
return n > 99 ? 99 : n;
|
||||
}
|
||||
|
||||
function updateRowAmounts() {
|
||||
document.querySelectorAll('#order-items .item-row').forEach(function(row) {
|
||||
var priceInput = row.querySelector('input[name$="-price"]');
|
||||
var qty = parseNum(row.querySelector('input[name$="-quantity"]')?.value);
|
||||
var qty = parseQty(row.querySelector('input[name$="-quantity"]')?.value);
|
||||
var price = parseNum(priceInput?.value);
|
||||
var el = row.querySelector('.row-amount');
|
||||
if (el) el.textContent = formatNum(price * qty);
|
||||
@@ -143,6 +148,10 @@
|
||||
var n = parseNum(e.target.value);
|
||||
e.target.value = isNaN(n) ? '' : formatNum(n);
|
||||
}
|
||||
if (e.target.name && e.target.name.indexOf('-quantity') !== -1) {
|
||||
var q = parseQty(e.target.value);
|
||||
e.target.value = String(q);
|
||||
}
|
||||
});
|
||||
|
||||
updateRowAmounts();
|
||||
@@ -199,6 +208,8 @@
|
||||
el.value = '';
|
||||
}
|
||||
});
|
||||
var qtyInput = clone.querySelector('input[name$="-quantity"]');
|
||||
if (qtyInput) qtyInput.value = '1';
|
||||
var amountCell = clone.querySelector('.row-amount');
|
||||
if (amountCell) amountCell.textContent = '—';
|
||||
tbody.appendChild(clone);
|
||||
|
||||
@@ -124,11 +124,16 @@
|
||||
function parseNum(s) {
|
||||
return parseFloat(String(s).replace(/\s/g, '').replace(',', '.')) || 0;
|
||||
}
|
||||
function parseQty(s) {
|
||||
var n = parseInt(String(s).replace(/\s/g, ''), 10);
|
||||
if (isNaN(n) || n < 0) return 0;
|
||||
return n > 99 ? 99 : n;
|
||||
}
|
||||
|
||||
function updateRowAmounts() {
|
||||
document.querySelectorAll('#supplier-order-items .item-row').forEach(function(row) {
|
||||
var priceInput = row.querySelector('input[name$="-price"]');
|
||||
var qty = parseNum(row.querySelector('input[name$="-quantity"]')?.value);
|
||||
var qty = parseQty(row.querySelector('input[name$="-quantity"]')?.value);
|
||||
var price = parseNum(priceInput?.value);
|
||||
var el = row.querySelector('.row-amount');
|
||||
if (el) el.textContent = formatNum(price * qty);
|
||||
@@ -155,6 +160,10 @@
|
||||
var n = parseNum(e.target.value);
|
||||
e.target.value = isNaN(n) ? '' : formatNum(n);
|
||||
}
|
||||
if (e.target.name && e.target.name.indexOf('-quantity') !== -1) {
|
||||
var q = parseQty(e.target.value);
|
||||
e.target.value = String(q);
|
||||
}
|
||||
});
|
||||
|
||||
updateRowAmounts();
|
||||
@@ -209,6 +218,8 @@
|
||||
el.value = '';
|
||||
}
|
||||
});
|
||||
var qtyInput = clone.querySelector('input[name$="-quantity"]');
|
||||
if (qtyInput) qtyInput.value = '1';
|
||||
var amountCell = clone.querySelector('.row-amount');
|
||||
if (amountCell) amountCell.textContent = '—';
|
||||
tbody.appendChild(clone);
|
||||
|
||||
Reference in New Issue
Block a user