haikal/inventory/override.py

699 lines
28 KiB
Python

import logging
from .models import Dealer
from django.core.exceptions import ImproperlyConfigured,ValidationError
from django.contrib.auth.mixins import LoginRequiredMixin,PermissionRequiredMixin
from django_ledger.forms.bill import (
BillModelCreateForm,
BaseBillModelUpdateForm,
DraftBillModelUpdateForm,
get_bill_itemtxs_formset_class,
BillModelConfigureForm,
InReviewBillModelUpdateForm,
ApprovedBillModelUpdateForm,
AccruedAndApprovedBillModelUpdateForm,
PaidBillModelUpdateForm
)
from django.http import HttpResponseForbidden
from django.utils.html import format_html
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django_ledger.models import ItemTransactionModel
from django.views.generic.detail import DetailView
from django_ledger.forms.purchase_order import (ApprovedPurchaseOrderModelUpdateForm,
BasePurchaseOrderModelUpdateForm,
DraftPurchaseOrderModelUpdateForm,
ReviewPurchaseOrderModelUpdateForm,
get_po_itemtxs_formset_class)
from django_ledger.views.purchase_order import PurchaseOrderModelModelViewQuerySetMixIn
from django_ledger.models import PurchaseOrderModel,EstimateModel,BillModel
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import UpdateView
from django.views.generic.base import RedirectView
from django.views.generic.list import ListView
from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
class PurchaseOrderModelUpdateView(LoginRequiredMixin,
PermissionRequiredMixin,
UpdateView):
slug_url_kwarg = 'po_pk'
slug_field = 'uuid'
context_object_name = 'po_model'
template_name = "purchase_orders/po_update.html"
context_object_name = "po_model"
permission_required = "django_ledger.change_purchaseordermodel"
extra_context = {
'header_subtitle_icon': 'uil:bill'
}
action_update_items = False
queryset = None
def get_context_data(self, itemtxs_formset=None, **kwargs):
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
context = super().get_context_data(**kwargs)
context["entity_slug"] = dealer.entity.slug
po_model: PurchaseOrderModel = self.object
if not itemtxs_formset:
itemtxs_qs = self.get_po_itemtxs_qs(po_model)
itemtxs_qs, itemtxs_agg = po_model.get_itemtxs_data(queryset=itemtxs_qs)
po_itemtxs_formset_class = get_po_itemtxs_formset_class(po_model)
itemtxs_formset = po_itemtxs_formset_class(
entity_slug=dealer.entity.slug,
user_model=dealer.entity.admin,
po_model=po_model,
queryset=itemtxs_qs,
)
else:
itemtxs_qs, itemtxs_agg = po_model.get_itemtxs_data()
context["itemtxs_qs"] = itemtxs_qs
context["itemtxs_formset"] = itemtxs_formset
return context
def get_queryset(self):
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
if self.queryset is None:
self.queryset = PurchaseOrderModel.objects.for_entity(
entity_slug=self.kwargs['entity_slug'],
user_model=dealer.entity.admin
).select_related('entity', 'ce_model')
return super().get_queryset()
def get_success_url(self):
return reverse(
"purchase_order_update",
kwargs={
"dealer_slug": self.kwargs["dealer_slug"],
"entity_slug": self.kwargs["entity_slug"],
"po_pk": self.kwargs["po_pk"],
},
)
def get(self, request, dealer_slug, entity_slug, po_pk, *args, **kwargs):
if self.action_update_items:
return HttpResponseRedirect(
redirect_to=reverse(
"purchase_order_update",
kwargs={
"dealer_slug": dealer_slug,
"entity_slug": entity_slug,
"po_pk": po_pk,
},
)
)
return super(PurchaseOrderModelUpdateView, self).get(
request, dealer_slug, entity_slug, po_pk, *args, **kwargs
)
def post(self, request, dealer_slug, entity_slug, *args, **kwargs):
if self.action_update_items:
if not request.user.is_authenticated:
return HttpResponseForbidden()
queryset = self.get_queryset()
po_model: PurchaseOrderModel = self.get_object(queryset=queryset)
self.object = po_model
po_itemtxs_formset_class = get_po_itemtxs_formset_class(po_model)
itemtxs_formset = po_itemtxs_formset_class(
request.POST,
user_model=request.dealer.entity.admin,
po_model=po_model,
entity_slug=entity_slug,
)
if itemtxs_formset.has_changed():
if itemtxs_formset.is_valid():
itemtxs_list = itemtxs_formset.save(commit=False)
create_bill_uuids = [
str(i["uuid"].uuid)
for i in itemtxs_formset.cleaned_data
if i and i["create_bill"] is True
]
if create_bill_uuids:
item_uuids = ",".join(create_bill_uuids)
redirect_url = reverse(
"bill-create-po",
kwargs={
"dealer_slug": self.kwargs["dealer_slug"],
"entity_slug": self.kwargs["entity_slug"],
"po_pk": po_model.uuid,
},
)
redirect_url += f"?item_uuids={item_uuids}"
return HttpResponseRedirect(redirect_url)
for itemtxs in itemtxs_list:
if not itemtxs.po_model_id:
itemtxs.po_model_id = po_model.uuid
itemtxs.clean()
itemtxs_list = itemtxs_formset.save()
po_model.update_state()
po_model.clean()
po_model.save(
update_fields=["po_amount", "po_amount_received", "updated"]
)
# if valid get saved formset from DB
messages.add_message(
request, messages.SUCCESS, "PO items updated successfully."
)
return self.render_to_response(context=self.get_context_data())
# if not valid, return formset with errors...
return self.render_to_response(
context=self.get_context_data(itemtxs_formset=itemtxs_formset)
)
return super(PurchaseOrderModelUpdateView, self).post(
request, dealer_slug, entity_slug, *args, **kwargs
)
def get_form(self, form_class=None):
po_model: PurchaseOrderModel = self.object
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
if po_model.is_draft():
return DraftPurchaseOrderModelUpdateForm(
entity_slug=self.kwargs["entity_slug"],
user_model=dealer.entity.admin,
**self.get_form_kwargs(),
)
elif po_model.is_review():
return ReviewPurchaseOrderModelUpdateForm(
entity_slug=self.kwargs["entity_slug"],
user_model=dealer.entity.admin,
**self.get_form_kwargs(),
)
elif po_model.is_approved():
return ApprovedPurchaseOrderModelUpdateForm(
entity_slug=self.kwargs["entity_slug"],
user_model=dealer.entity.admin,
**self.get_form_kwargs(),
)
return BasePurchaseOrderModelUpdateForm(
entity_slug=self.kwargs["entity_slug"],
user_model=dealer.entity.admin,
**self.get_form_kwargs(),
)
def get_form_kwargs(self):
if self.action_update_items:
return {
'initial': self.get_initial(),
'prefix': self.get_prefix(),
'instance': self.object
}
return super(PurchaseOrderModelUpdateView, self).get_form_kwargs()
def get_po_itemtxs_qs(self, po_model: PurchaseOrderModel):
return po_model.itemtransactionmodel_set.select_related('bill_model', 'po_model').order_by('created')
def form_valid(self, form: BasePurchaseOrderModelUpdateForm):
po_model: PurchaseOrderModel = form.save(commit=False)
if form.has_changed():
po_items_qs = ItemTransactionModel.objects.for_po(
entity_slug=self.kwargs['entity_slug'],
user_model=self.request.admin,
po_pk=po_model.uuid,
).select_related('bill_model')
if all(['po_status' in form.changed_data,
po_model.po_status == po_model.PO_STATUS_APPROVED]):
po_items_qs.update(po_item_status=ItemTransactionModel.STATUS_NOT_ORDERED)
if 'fulfilled' in form.changed_data:
if not all([i.bill_model for i in po_items_qs]):
messages.add_message(self.request,
messages.ERROR,
f'All PO items must be billed before marking'
f' PO: {po_model.po_number} as fulfilled.',
extra_tags='is-danger')
return self.get(self.request)
else:
if not all([i.bill_model.is_paid() for i in po_items_qs]):
messages.add_message(self.request,
messages.SUCCESS,
f'All bills must be paid before marking'
f' PO: {po_model.po_number} as fulfilled.',
extra_tags='is-success')
return self.get(self.request)
po_items_qs.update(po_item_status=ItemTransactionModel.STATUS_RECEIVED)
messages.add_message(self.request,
messages.SUCCESS,
f'{self.object.po_number} successfully updated.',
extra_tags='is-success')
return super().form_valid(form)
class BasePurchaseOrderActionActionView(LoginRequiredMixin,
PermissionRequiredMixin,
RedirectView,
SingleObjectMixin):
http_method_names = ['get']
pk_url_kwarg = 'po_pk'
action_name = None
commit = True
permission_required = None
queryset = None
def get_queryset(self):
dealer = get_object_or_404(Dealer, slug=self.kwargs['dealer_slug'])
if self.queryset is None:
self.queryset = PurchaseOrderModel.objects.for_entity(
entity_slug=self.kwargs['entity_slug'],
user_model=dealer.entity.admin
).select_related('entity', 'ce_model')
return super().get_queryset()
def get_redirect_url(self, dealer_slug, entity_slug, po_pk, *args, **kwargs):
return reverse(
"purchase_order_update",
kwargs={
"dealer_slug": dealer_slug,
"entity_slug": entity_slug,
"po_pk": po_pk,
},
)
def get(self, request, dealer_slug, entity_slug, po_pk, *args, **kwargs):
# kwargs["user_model"] = dealer.entity.admin
# Get user information for logging
user_username = request.user.username if request.user.is_authenticated else 'anonymous'
dealer = get_object_or_404(Dealer, slug=dealer_slug)
kwargs["user_model"] = dealer.entity.admin
if not self.action_name:
raise ImproperlyConfigured("View attribute action_name is required.")
response = super(BasePurchaseOrderActionActionView, self).get(
request, dealer_slug, entity_slug, po_pk, *args, **kwargs
)
po_model: PurchaseOrderModel = self.get_object()
# Log the attempt to perform the action
logger.debug(
f"User {user_username} attempting to call action '{self.action_name}' "
f"on Purchase Order ID: {po_model.pk} (Entity: {entity_slug})."
)
try:
getattr(po_model, self.action_name)(commit=self.commit, **kwargs)
# --- Single-line log for successful action ---
logger.info(
f"User {user_username} successfully executed action '{self.action_name}' "
f"on Purchase Order ID: {po_model.pk}."
)
messages.add_message(
request,
message="PO updated successfully.",
level=messages.SUCCESS,
)
except ValidationError as e:
# --- Single-line log for ValidationError ---
print(f"User {user_username} encountered a validation error "
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
f"Error: {e}")
logger.warning(
f"User {user_username} encountered a validation error "
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
f"Error: {e}"
)
except AttributeError as e:
print(f"User {user_username} encountered an AttributeError "
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
f"Error: {e}")
logger.warning(
f"User {user_username} encountered an AttributeError "
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
f"Error: {e}"
)
return response
class BillModelDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
slug_url_kwarg = 'bill_pk'
slug_field = 'uuid'
context_object_name = 'bill'
template_name = "bill/bill_detail.html"
extra_context = {
'header_subtitle_icon': 'uil:bill',
'hide_menu': True
}
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
context["dealer"] = self.request.dealer
bill_model: BillModel = self.object
title = f'Bill {bill_model.bill_number}'
context['page_title'] = title
context['header_title'] = title
bill_model: BillModel = self.object
bill_items_qs, item_data = bill_model.get_itemtxs_data()
context['itemtxs_qs'] = bill_items_qs
context['total_amount__sum'] = item_data['total_amount__sum']
if not bill_model.is_configured():
link = format_html(f"""
<a href="{reverse("bill-update", kwargs={
'dealer_slug': self.kwargs['dealer_slug'],
'entity_slug': self.kwargs['entity_slug'],
'bill_pk': bill_model.uuid
})}">here</a>
""")
msg = f'Bill {bill_model.bill_number} has not been fully set up. ' + \
f'Please update or assign associated accounts {link}.'
messages.add_message(self.request,
message=msg,
level=messages.WARNING,
extra_tags='is-danger')
return context
def get_queryset(self):
dealer = get_object_or_404(Dealer,slug=self.kwargs['dealer_slug'])
if self.queryset is None:
entity_model = dealer.entity
qs = entity_model.get_bills()
self.queryset = qs
return super().get_queryset()
######################################################3
#BILL
class BillModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
slug_url_kwarg = 'bill_pk'
slug_field = 'uuid'
context_object_name = 'bill_model'
template_name = "bill/bill_update.html"
extra_context = {
'header_subtitle_icon': 'uil:bill'
}
http_method_names = ['get', 'post']
action_update_items = False
queryset = None
def get_queryset(self):
dealer = get_object_or_404(Dealer,slug=self.kwargs['dealer_slug'])
if self.queryset is None:
entity_model = dealer.entity
qs = entity_model.get_bills()
self.queryset = qs
return super().get_queryset().select_related(
'ledger',
'ledger__entity',
'vendor',
'cash_account',
'prepaid_account',
'unearned_account',
'cash_account__coa_model',
'prepaid_account__coa_model',
'unearned_account__coa_model'
)
def get_form(self, form_class=None):
form_class = self.get_form_class()
entity_model = self.request.dealer.entity
if self.request.method == 'POST' and self.action_update_items:
return form_class(
entity_model=entity_model,
user_model=self.request.admin,
instance=self.object
)
form = form_class(
entity_model=entity_model,
user_model=self.request.admin,
**self.get_form_kwargs()
)
try:
form.initial['amount_paid'] = self.object.get_itemtxs_data()[1]["total_amount__sum"]
except Exception as e:
print(e)
return form
def get_form_class(self):
bill_model: BillModel = self.object
if not bill_model.is_configured():
return BillModelConfigureForm
if bill_model.is_draft():
return DraftBillModelUpdateForm
elif bill_model.is_review():
return InReviewBillModelUpdateForm
elif bill_model.is_approved() and not bill_model.accrue:
return ApprovedBillModelUpdateForm
elif bill_model.is_approved() and bill_model.accrue:
return AccruedAndApprovedBillModelUpdateForm
elif bill_model.is_paid():
return PaidBillModelUpdateForm
return BaseBillModelUpdateForm
def get_context_data(self,
*,
object_list=None,
itemtxs_formset=None,
**kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
dealer = get_object_or_404(Dealer,slug=self.kwargs['dealer_slug'])
entity_model = dealer.entity
bill_model: BillModel = self.object
ledger_model = bill_model.ledger
title = f'Bill {bill_model.bill_number}'
context['page_title'] = title
context['header_title'] = title
context['header_subtitle'] = bill_model.get_bill_status_display()
if not bill_model.is_configured():
messages.add_message(
request=self.request,
message=f'Bill {bill_model.bill_number} must have all accounts configured.',
level=messages.ERROR,
extra_tags='is-danger'
)
if not bill_model.is_paid():
if ledger_model.locked:
messages.add_message(self.request,
messages.ERROR,
f'Warning! This bill is locked. Must unlock before making any changes.',
extra_tags='is-danger')
if ledger_model.locked:
messages.add_message(self.request,
messages.ERROR,
f'Warning! This bill is locked. Must unlock before making any changes.',
extra_tags='is-danger')
if not ledger_model.is_posted():
messages.add_message(self.request,
messages.INFO,
f'This bill has not been posted. Must post to see ledger changes.',
extra_tags='is-info')
itemtxs_qs = itemtxs_formset.get_queryset() if itemtxs_formset else None
if not itemtxs_formset:
itemtxs_formset_class = get_bill_itemtxs_formset_class(bill_model)
itemtxs_formset = itemtxs_formset_class(entity_model=entity_model, bill_model=bill_model)
itemtxs_qs, itemtxs_agg = bill_model.get_itemtxs_data(queryset=itemtxs_qs)
has_po = any(i.po_model_id for i in itemtxs_qs)
if has_po:
itemtxs_formset.can_delete = False
itemtxs_formset.has_po = has_po
context['itemtxs_formset'] = itemtxs_formset
context['total_amount__sum'] = itemtxs_agg['total_amount__sum']
context['has_po'] = has_po
return context
def get_success_url(self):
return reverse(
"bill-update",
kwargs={
"dealer_slug": self.kwargs["dealer_slug"],
"entity_slug": self.kwargs["entity_slug"],
"bill_pk": self.kwargs["bill_pk"],
},
)
def form_valid(self, form):
form.save(commit=False)
messages.add_message(self.request,
messages.SUCCESS,
f'Bill {self.object.bill_number} successfully updated.',
extra_tags='is-success')
return super().form_valid(form)
def get(self, request,dealer_slug,entity_slug,bill_pk, *args, **kwargs):
if self.action_update_items:
return HttpResponseRedirect(
redirect_to=reverse('bill-update',
kwargs={
'dealer_slug': dealer_slug,
'entity_slug': entity_slug,
'bill_pk': bill_pk
})
)
return super(BillModelUpdateView, self).get(request, *args, **kwargs)
def post(self, request, dealer_slug, entity_slug, bill_pk, *args, **kwargs):
if self.action_update_items:
if not request.user.is_authenticated:
return HttpResponseForbidden()
queryset = self.get_queryset()
dealer = get_object_or_404(Dealer, slug=dealer_slug)
entity_model = dealer.entity
bill_model: BillModel = self.get_object(queryset=queryset)
bill_pk = bill_model.uuid
self.object = bill_model
bill_itemtxs_formset_class = get_bill_itemtxs_formset_class(bill_model)
itemtxs_formset = bill_itemtxs_formset_class(
request.POST, bill_model=bill_model, entity_model=entity_model
)
if itemtxs_formset.has_changed():
if itemtxs_formset.is_valid():
itemtxs_list = itemtxs_formset.save(commit=False)
for itemtxs in itemtxs_list:
itemtxs.bill_model_id = bill_model.uuid
itemtxs.clean()
itemtxs_formset.save()
itemtxs_qs = bill_model.update_amount_due()
bill_model.get_state(commit=True)
bill_model.clean()
bill_model.save(
update_fields=[
"amount_due",
"amount_receivable",
"amount_unearned",
"amount_earned",
"updated",
]
)
bill_model.migrate_state(
entity_slug=self.kwargs["entity_slug"],
user_model=self.request.user,
itemtxs_qs=itemtxs_qs,
raise_exception=False,
)
messages.add_message(
request,
message=f"Items for Invoice {bill_model.bill_number} saved.",
level=messages.SUCCESS,
)
# if valid get saved formset from DB
return HttpResponseRedirect(
redirect_to=reverse(
"bill-update",
kwargs={
"dealer_slug": dealer_slug,
"entity_slug": entity_model.slug,
"bill_pk": bill_pk,
},
)
)
context = self.get_context_data(itemtxs_formset=itemtxs_formset)
return self.render_to_response(context=context)
return super(BillModelUpdateView, self).post(
request, dealer_slug, entity_slug, bill_pk, **kwargs
)
class BaseBillActionView(LoginRequiredMixin,PermissionRequiredMixin, RedirectView, SingleObjectMixin):
http_method_names = ['get']
pk_url_kwarg = 'bill_pk'
action_name = None
commit = True
permission_required = "django_ledger.change_billmodel"
queryset = None
def get_queryset(self):
if self.queryset is None:
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
entity_model = dealer.entity
qs = entity_model.get_bills()
self.queryset = qs
return super().get_queryset()
def get_redirect_url(self, dealer_slug, entity_slug, bill_pk, *args, **kwargs):
return reverse(
"bill-update",
kwargs={
"dealer_slug": dealer_slug,
"entity_slug": entity_slug,
"bill_pk": bill_pk,
},
)
def get(self, request, *args, **kwargs):
dealer = get_object_or_404(Dealer, slug=self.kwargs["dealer_slug"])
kwargs['user_model'] = dealer.entity.admin
if not self.action_name:
raise ImproperlyConfigured('View attribute action_name is required.')
response = super(BaseBillActionView, self).get(request, *args, **kwargs)
bill_model: BillModel = self.get_object()
try:
getattr(bill_model, self.action_name)(commit=self.commit, **kwargs)
except ValidationError as e:
messages.add_message(request,
message=e.message,
level=messages.ERROR,
extra_tags='is-danger')
return response
class InventoryListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
template_name = 'django_ledger/inventory/inventory_list.html'
context_object_name = 'inventory_list'
http_method_names = ['get']
def get_context_data(self, *, object_list=None, **kwargs):
context = super(InventoryListView, self).get_context_data(**kwargs)
qs = self.get_queryset()
# evaluates the queryset...
context['qs_count'] = qs.count()
# ordered inventory...
ordered_qs = qs.is_ordered()
context['inventory_ordered'] = ordered_qs
# in transit inventory...
in_transit_qs = qs.in_transit()
context['inventory_in_transit'] = in_transit_qs
# on hand inventory...
received_qs = qs.is_received()
context['inventory_received'] = received_qs
context['page_title'] = _('Inventory')
context['header_title'] = _('Inventory Status')
context['header_subtitle'] = _('Ordered/In Transit/On Hand')
context['header_subtitle_icon'] = 'ic:round-inventory'
return context
def get_queryset(self):
if self.queryset is None:
self.queryset = ItemTransactionModel.objects.inventory_pipeline_aggregate(
entity_slug=self.kwargs['entity_slug'],
)
return super().get_queryset()