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")