update
This commit is contained in:
199
inventory/views.py
Normal file
199
inventory/views.py
Normal file
@@ -0,0 +1,199 @@
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db.models import DecimalField, ExpressionWrapper, F, Q, Sum
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.views.generic import CreateView, DetailView, TemplateView, UpdateView
|
||||
|
||||
from .forms import ItemForm, ItemFilterForm, ItemNoteForm, ItemSoldForm, ItemTemplateForm
|
||||
from .models import Item, ItemNote, ItemTemplate
|
||||
from .services import PricingSuggestionService
|
||||
import decimal
|
||||
|
||||
|
||||
class DashboardView(LoginRequiredMixin, TemplateView):
|
||||
template_name = "inventory/dashboard.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
item_filter = ItemFilterForm(self.request.GET or None)
|
||||
items = Item.objects.select_related("created_by", "template").order_by("-created_at")
|
||||
|
||||
if item_filter.is_valid():
|
||||
query = item_filter.cleaned_data.get("query")
|
||||
status = item_filter.cleaned_data.get("status")
|
||||
category = item_filter.cleaned_data.get("category")
|
||||
created_by = item_filter.cleaned_data.get("created_by")
|
||||
min_purchase_price = item_filter.cleaned_data.get("min_purchase_price")
|
||||
max_purchase_price = item_filter.cleaned_data.get("max_purchase_price")
|
||||
min_profit = item_filter.cleaned_data.get("min_profit")
|
||||
|
||||
if query:
|
||||
items = items.filter(
|
||||
Q(title__icontains=query)
|
||||
| Q(brand__icontains=query)
|
||||
| Q(notes__icontains=query)
|
||||
| Q(category__icontains=query)
|
||||
)
|
||||
if status:
|
||||
items = items.filter(status=status)
|
||||
if category:
|
||||
items = items.filter(category__icontains=category)
|
||||
if min_purchase_price is not None:
|
||||
items = items.filter(purchase_price__gte=min_purchase_price)
|
||||
if max_purchase_price is not None:
|
||||
items = items.filter(purchase_price__lte=max_purchase_price)
|
||||
if min_profit is not None:
|
||||
items = items.annotate(
|
||||
calculated_profit=ExpressionWrapper(
|
||||
F("sold_price") - F("purchase_price") - F("ebay_fee") - F("shipping_cost"),
|
||||
output_field=DecimalField(max_digits=10, decimal_places=2),
|
||||
)
|
||||
).filter(
|
||||
sold_price__isnull=False,
|
||||
calculated_profit__gte=min_profit,
|
||||
)
|
||||
if created_by:
|
||||
items = items.filter(created_by__username__icontains=created_by)
|
||||
|
||||
context["items"] = items[:100]
|
||||
context["templates"] = ItemTemplate.objects.filter(is_active=True).order_by("name")
|
||||
context["item_filter"] = item_filter
|
||||
context["item_form"] = ItemForm()
|
||||
context["template_form"] = ItemTemplateForm()
|
||||
inventory_cost = Item.objects.aggregate(total=Sum("purchase_price"))["total"] or 0
|
||||
context["stats"] = {
|
||||
"item_count": Item.objects.count(),
|
||||
"profit_total": sum((item.profit or 0) for item in Item.objects.all()),
|
||||
"sold_count": Item.objects.filter(status=Item.Status.SOLD).count(),
|
||||
"inventory_value": inventory_cost,
|
||||
}
|
||||
return context
|
||||
|
||||
|
||||
class ItemCreateView(LoginRequiredMixin, CreateView):
|
||||
model = Item
|
||||
form_class = ItemForm
|
||||
template_name = "inventory/item_form.html"
|
||||
success_url = reverse_lazy("dashboard")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
template_id = self.request.GET.get("template") or self.request.POST.get("template")
|
||||
template_obj = None
|
||||
if template_id:
|
||||
try:
|
||||
template_obj = ItemTemplate.objects.get(pk=int(template_id))
|
||||
except Exception:
|
||||
template_obj = None
|
||||
context["template_obj"] = template_obj
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.created_by = self.request.user
|
||||
template = form.cleaned_data.get("template")
|
||||
properties = {}
|
||||
# If template defines extra fields, read them from POST and coerce types
|
||||
if template and template.field_definitions:
|
||||
for fd in template.field_definitions:
|
||||
key = fd.get("name")
|
||||
ftype = fd.get("type", "text")
|
||||
raw = self.request.POST.get(f"prop_{key}")
|
||||
if raw is None or raw == "":
|
||||
value = None
|
||||
else:
|
||||
if ftype == "number":
|
||||
try:
|
||||
value = decimal.Decimal(raw)
|
||||
# JSONField cannot store Decimal directly; store as string to preserve precision
|
||||
value = str(value)
|
||||
except Exception:
|
||||
value = raw
|
||||
elif ftype == "boolean":
|
||||
value = raw in ("on", "true", "1", "True")
|
||||
else:
|
||||
value = raw
|
||||
properties[key] = value
|
||||
form.instance.properties = properties
|
||||
if template:
|
||||
if not form.cleaned_data.get("category"):
|
||||
form.instance.category = template.category
|
||||
if not form.cleaned_data.get("purchase_price") and template.default_purchase_price is not None:
|
||||
form.instance.purchase_price = template.default_purchase_price
|
||||
if not form.cleaned_data.get("estimated_resale_price") and template.default_estimated_resale_price is not None:
|
||||
form.instance.estimated_resale_price = template.default_estimated_resale_price
|
||||
if not form.cleaned_data.get("notes") and template.default_notes:
|
||||
form.instance.notes = template.default_notes
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class ItemDetailView(LoginRequiredMixin, DetailView):
|
||||
model = Item
|
||||
template_name = "inventory/item_detail.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["edit_form"] = ItemForm(instance=self.object)
|
||||
context["sold_form"] = ItemSoldForm(instance=self.object)
|
||||
context["note_form"] = ItemNoteForm()
|
||||
context["notes"] = self.object.notes_history.select_related("created_by").order_by("-created_at")
|
||||
context["price_estimates"] = self.object.price_estimates.all()
|
||||
context["pricing_suggestion"] = PricingSuggestionService().suggest_for_item(self.object)
|
||||
return context
|
||||
|
||||
|
||||
class ItemUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = Item
|
||||
form_class = ItemForm
|
||||
template_name = "inventory/item_form.html"
|
||||
|
||||
def get_success_url(self):
|
||||
return self.object.get_absolute_url()
|
||||
|
||||
|
||||
class ItemMarkSoldView(LoginRequiredMixin, UpdateView):
|
||||
model = Item
|
||||
form_class = ItemSoldForm
|
||||
template_name = "inventory/item_sold_form.html"
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save(commit=False)
|
||||
self.object.status = Item.Status.SOLD
|
||||
if self.object.sold_at is None:
|
||||
self.object.sold_at = timezone.now()
|
||||
self.object.save()
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self):
|
||||
return self.object.get_absolute_url()
|
||||
|
||||
|
||||
class ItemNoteCreateView(LoginRequiredMixin, CreateView):
|
||||
model = ItemNote
|
||||
form_class = ItemNoteForm
|
||||
template_name = "inventory/note_form.html"
|
||||
success_url = reverse_lazy("dashboard")
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.item = get_object_or_404(Item, pk=self.kwargs["pk"])
|
||||
form.instance.created_by = self.request.user
|
||||
response = super().form_valid(form)
|
||||
return HttpResponseRedirect(self.object.item.get_absolute_url())
|
||||
|
||||
def get_success_url(self):
|
||||
return self.object.item.get_absolute_url()
|
||||
|
||||
|
||||
class ItemTemplateCreateView(LoginRequiredMixin, CreateView):
|
||||
model = ItemTemplate
|
||||
form_class = ItemTemplateForm
|
||||
template_name = "inventory/template_form.html"
|
||||
success_url = reverse_lazy("dashboard")
|
||||
|
||||
|
||||
class ItemTemplateUpdateView(LoginRequiredMixin, UpdateView):
|
||||
model = ItemTemplate
|
||||
form_class = ItemTemplateForm
|
||||
template_name = "inventory/template_form.html"
|
||||
success_url = reverse_lazy("dashboard")
|
||||
Reference in New Issue
Block a user