102 lines
3.7 KiB
Python
102 lines
3.7 KiB
Python
from decimal import Decimal
|
|
|
|
from django import forms
|
|
from django.core.exceptions import ValidationError
|
|
|
|
from shop.models import ProductPrice, ProductVariant, Tax
|
|
|
|
|
|
def price_without_tax(price_with_tax, tax):
|
|
"""El staff introduce el precio con impuestos (lo que paga el cliente);
|
|
ProductPrice.price se guarda sin impuestos, que es lo que usa el resto de
|
|
la aplicación (carritos, pedidos, shop/utils.py) para calcular bases
|
|
imponibles/impuestos. ProductPrice.save() recalcula price_with_tax a
|
|
partir de este valor, así que no hace falta guardarlo aquí."""
|
|
return round(price_with_tax / (1 + Decimal(tax.value) / 100), 2)
|
|
|
|
|
|
class ProductInitialPriceForm(forms.Form):
|
|
"""Precio opcional que se puede fijar al crear un producto, junto con el
|
|
resto de datos, en el mismo envío (ver backoffice/views/products.py:ProductCreateView)."""
|
|
|
|
price = forms.DecimalField(
|
|
max_digits=11,
|
|
decimal_places=2,
|
|
required=False,
|
|
label='Precio con impuestos',
|
|
widget=forms.NumberInput(
|
|
attrs={'class': 'input input-bordered w-full', 'step': '0.01', 'placeholder': 'Precio con impuestos'}
|
|
),
|
|
)
|
|
tax = forms.ModelChoiceField(
|
|
queryset=Tax.objects.all(),
|
|
required=False,
|
|
widget=forms.Select(attrs={'class': 'select select-bordered w-full'}),
|
|
)
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
|
|
if cleaned_data.get('price') is not None and not cleaned_data.get('tax'):
|
|
raise ValidationError('Selecciona un impuesto para el precio.')
|
|
|
|
if cleaned_data.get('tax') and cleaned_data.get('price') is None:
|
|
raise ValidationError('Indica un precio para el impuesto seleccionado.')
|
|
|
|
return cleaned_data
|
|
|
|
def get_price_without_tax(self):
|
|
return price_without_tax(self.cleaned_data['price'], self.cleaned_data['tax'])
|
|
|
|
|
|
class ProductPriceForm(forms.ModelForm):
|
|
"""El campo `price` del formulario representa el precio CON impuestos (lo
|
|
que introduce el staff); al guardar se convierte al precio sin impuestos,
|
|
que es lo que almacena ProductPrice.price."""
|
|
|
|
price = forms.DecimalField(
|
|
max_digits=11,
|
|
decimal_places=2,
|
|
label='Precio con impuestos',
|
|
widget=forms.NumberInput(attrs={'step': '0.01'}),
|
|
)
|
|
|
|
class Meta:
|
|
model = ProductPrice
|
|
fields = ('price', 'tax', 'current')
|
|
|
|
def save(self, commit=True):
|
|
self.instance.price = price_without_tax(self.cleaned_data['price'], self.cleaned_data['tax'])
|
|
return super().save(commit=commit)
|
|
|
|
|
|
class ProductVariantForm(forms.ModelForm):
|
|
class Meta:
|
|
model = ProductVariant
|
|
fields = ('sku', 'stock', 'attribute_values')
|
|
widgets = {'attribute_values': forms.CheckboxSelectMultiple}
|
|
|
|
def __init__(self, *args, product=None, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.product = product or self.instance.product
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
attribute_values = cleaned_data.get('attribute_values')
|
|
|
|
if attribute_values is not None:
|
|
combination = set(attribute_values.values_list('pk', flat=True))
|
|
siblings = ProductVariant.objects.filter(product=self.product).exclude(pk=self.instance.pk)
|
|
|
|
for sibling in siblings:
|
|
if set(sibling.attribute_values.values_list('pk', flat=True)) == combination:
|
|
raise ValidationError(
|
|
'Ya existe una variante de este producto con la misma combinación de atributos.'
|
|
)
|
|
|
|
return cleaned_data
|
|
|
|
def save(self, commit=True):
|
|
self.instance.product = self.product
|
|
return super().save(commit=commit)
|