haikal/inventory/override.py
2025-09-24 11:07:31 +03:00

1211 lines
45 KiB
Python

from django.utils import timezone
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,
InvoiceModel,
LedgerModel,
EntityModel,
)
from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView
from django_ledger.forms.chart_of_accounts import (
ChartOfAccountsModelCreateForm,
ChartOfAccountsModelUpdateForm,
)
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,
ChartOfAccountModel,
)
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 _
from django_ledger.forms.invoice import (
BaseInvoiceModelUpdateForm,
InvoiceModelCreateForEstimateForm,
get_invoice_itemtxs_formset_class,
DraftInvoiceModelUpdateForm,
InReviewInvoiceModelUpdateForm,
ApprovedInvoiceModelUpdateForm,
PaidInvoiceModelUpdateForm,
AccruedAndApprovedInvoiceModelUpdateForm,
InvoiceModelCreateForm,
)
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"])
po_model: PurchaseOrderModel = self.object
context = super().get_context_data(**kwargs)
context["entity_slug"] = dealer.entity.slug
context["po_ready_to_fulfill"] = [
item
for item in po_model.get_itemtxs_data()[0]
if item.po_item_status == "received"
]
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})."
)
if self.action_name == "mark_as_fulfilled":
try:
if po_model.can_fulfill():
po_model.mark_as_fulfilled()
if po_model.is_fulfilled():
po_model.date_fulfilled = timezone.now().date()
po_model.save()
messages.add_message(
request,
message="PO marked as fulfilled successfully.",
level=messages.SUCCESS,
)
logger.info(
f"User {user_username} successfully executed action '{self.action_name}' "
f"on Purchase Order ID: {po_model.pk}."
)
except Exception as e:
messages.add_message(
request,
message=f"Failed to mark PO {po_model.po_number} as fulfilled. {e}",
level=messages.ERROR,
)
logger.warning(
f"User {user_username} encountered an exception "
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
f"Error: {e}"
)
else:
try:
getattr(po_model, self.action_name)(commit=self.commit, **kwargs)
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}"
)
except Exception as e:
print(
f"User {user_username} encountered an exception "
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 exception "
f"while performing action '{self.action_name}' on Purchase Order ID: {po_model.pk}. "
f"Error: {e}"
)
messages.add_message(
request,
message=f"Failed to update PO {po_model.po_number}. {e}",
level=messages.ERROR,
)
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()
context["can_mark_as_paid"] = bill_model.amount_paid == bill_model.amount_due
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()
class InvoiceModelUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
slug_url_kwarg = "invoice_pk"
slug_field = "uuid"
context_object_name = "invoice"
# template_name = 'inventory/sales/invoices/invoice_update.html'
form_class = BaseInvoiceModelUpdateForm
http_method_names = ["get", "post"]
action_update_items = False
def get_form_class(self):
invoice_model: InvoiceModel = self.object
if invoice_model.is_draft():
return DraftInvoiceModelUpdateForm
elif invoice_model.is_review():
return InReviewInvoiceModelUpdateForm
elif invoice_model.is_approved():
if invoice_model.accrue:
return AccruedAndApprovedInvoiceModelUpdateForm
return ApprovedInvoiceModelUpdateForm
elif invoice_model.is_paid():
return PaidInvoiceModelUpdateForm
return BaseInvoiceModelUpdateForm
def get_form(self, form_class=None):
form_class = self.get_form_class()
if self.request.method == "POST" and self.action_update_items:
return form_class(
entity_slug=self.kwargs["entity_slug"],
user_model=self.request.dealer.user,
instance=self.object,
)
return form_class(
entity_slug=self.kwargs["entity_slug"],
user_model=self.request.dealer.user,
**self.get_form_kwargs(),
)
def get_context_data(self, itemtxs_formset=None, **kwargs):
context = super().get_context_data(**kwargs)
invoice_model: InvoiceModel = self.object
title = f"Invoice {invoice_model.invoice_number}"
context["page_title"] = title
context["header_title"] = title
ledger_model: LedgerModel = self.object.ledger
if not invoice_model.is_configured():
messages.add_message(
request=self.request,
message=f"Invoice {invoice_model.invoice_number} must have all accounts configured.",
level=messages.ERROR,
extra_tags="is-danger",
)
if not invoice_model.is_paid():
if ledger_model.locked:
messages.add_message(
self.request,
messages.ERROR,
f"Warning! This invoice 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 Invoice 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 Invoice has not been posted. Must post to see ledger changes.",
extra_tags="is-info",
)
if not itemtxs_formset:
itemtxs_qs = invoice_model.itemtransactionmodel_set.all().select_related(
"item_model"
)
itemtxs_qs, itemtxs_agg = invoice_model.get_itemtxs_data(
queryset=itemtxs_qs
)
invoice_itemtxs_formset_class = get_invoice_itemtxs_formset_class(
invoice_model
)
itemtxs_formset = invoice_itemtxs_formset_class(
entity_slug=self.kwargs["entity_slug"],
user_model=self.request.dealer.user,
invoice_model=invoice_model,
queryset=itemtxs_qs,
)
else:
itemtxs_qs, itemtxs_agg = invoice_model.get_itemtxs_data(
queryset=itemtxs_formset.queryset
)
context["itemtxs_formset"] = itemtxs_formset
context["total_amount__sum"] = itemtxs_agg["total_amount__sum"]
return context
def get_success_url(self):
entity_slug = self.kwargs["entity_slug"]
invoice_pk = self.kwargs["invoice_pk"]
return reverse(
"invoice_detail",
kwargs={
"dealer_slug": self.request.dealer.slug,
"entity_slug": entity_slug,
"pk": invoice_pk,
},
)
# def get_queryset(self):
# qs = super().get_queryset()
# return qs.prefetch_related('itemtransactionmodel_set')
def get_queryset(self):
if self.queryset is None:
self.queryset = (
InvoiceModel.objects.for_entity(
entity_slug=self.kwargs["entity_slug"], user_model=self.request.user
)
.select_related("customer", "ledger")
.order_by("-created")
)
return super().get_queryset().prefetch_related("itemtransactionmodel_set")
def form_valid(self, form):
invoice_model: InvoiceModel = form.save(commit=False)
if invoice_model.can_migrate():
invoice_model.migrate_state(
user_model=self.request.dealer.user,
entity_slug=self.kwargs["entity_slug"],
)
messages.add_message(
self.request,
messages.SUCCESS,
f"Invoice {self.object.invoice_number} successfully updated.",
extra_tags="is-success",
)
return super().form_valid(form)
def get(self, request, entity_slug, invoice_pk, *args, **kwargs):
if self.action_update_items:
return HttpResponseRedirect(
redirect_to=reverse(
"invoice_update",
kwargs={
"dealer_slug": request.dealer.slug,
"entity_slug": entity_slug,
"pk": invoice_pk,
},
)
)
return super(InvoiceModelUpdateView, self).get(request, *args, **kwargs)
def post(self, request, entity_slug, invoice_pk, *args, **kwargs):
if self.action_update_items:
if not request.user.is_authenticated:
return HttpResponseForbidden()
queryset = self.get_queryset()
invoice_model = self.get_object(queryset=queryset)
self.object = invoice_model
invoice_itemtxs_formset_class = get_invoice_itemtxs_formset_class(
invoice_model
)
itemtxs_formset = invoice_itemtxs_formset_class(
request.POST,
user_model=self.request.dealer.user,
invoice_model=invoice_model,
entity_slug=entity_slug,
)
if not invoice_model.can_edit_items():
messages.add_message(
request,
message=f"Cannot update items once Invoice is {invoice_model.get_invoice_status_display()}",
level=messages.ERROR,
extra_tags="is-danger",
)
context = self.get_context_data(itemtxs_formset=itemtxs_formset)
return self.render_to_response(context=context)
if itemtxs_formset.has_changed():
if itemtxs_formset.is_valid():
itemtxs_list = itemtxs_formset.save(commit=False)
entity_qs = EntityModel.objects.for_user(
user_model=self.request.dealer.user
)
entity_model: EntityModel = get_object_or_404(
entity_qs, slug__exact=entity_slug
)
for itemtxs in itemtxs_list:
itemtxs.invoice_model_id = invoice_model.uuid
itemtxs.clean()
itemtxs_list = itemtxs_formset.save()
itemtxs_qs = invoice_model.update_amount_due()
invoice_model.get_state(commit=True)
invoice_model.clean()
invoice_model.save(
update_fields=[
"amount_due",
"amount_receivable",
"amount_unearned",
"amount_earned",
"updated",
]
)
invoice_model.migrate_state(
entity_slug=entity_slug,
user_model=self.request.user,
raise_exception=False,
itemtxs_qs=itemtxs_qs,
)
messages.add_message(
request,
message=f"Items for Invoice {invoice_model.invoice_number} saved.",
level=messages.SUCCESS,
extra_tags="is-success",
)
return HttpResponseRedirect(
redirect_to=reverse(
"django_ledger:invoice-update",
kwargs={
"entity_slug": entity_slug,
"invoice_pk": invoice_pk,
},
)
)
# if not valid, return formset with errors...
return self.render_to_response(
context=self.get_context_data(itemtxs_formset=itemtxs_formset)
)
return super(InvoiceModelUpdateView, self).post(request, **kwargs)
class ChartOfAccountModelModelBaseViewMixIn(
LoginRequiredMixin, PermissionRequiredMixin
):
queryset = None
permission_required = []
def get_queryset(self):
if self.queryset is None:
entity_model = self.request.dealer.entity
self.queryset = entity_model.chartofaccountmodel_set.all().order_by(
"-updated"
)
return super().get_queryset()
def get_redirect_url(self, *args, **kwargs):
return reverse(
"coa-list",
kwargs={
"dealer_slug": self.request.dealer.slug,
"entity_slug": self.request.entity.slug,
},
)
class ChartOfAccountModelListView(ChartOfAccountModelModelBaseViewMixIn, ListView):
template_name = "chart_of_accounts/coa_list.html"
context_object_name = "coa_list"
inactive = False
def get_queryset(self):
qs = super().get_queryset()
if self.inactive:
return qs.filter(active=False)
return qs.active()
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=None, **kwargs)
context["inactive"] = self.inactive
context["header_subtitle"] = self.request.entity.name
context["header_subtitle_icon"] = "gravity-ui:hierarchy"
context["page_title"] = (
"Inactive Chart of Account List"
if self.inactive
else "Chart of Accounts List"
)
context["header_title"] = (
"Inactive Chart of Account List"
if self.inactive
else "Chart of Accounts List"
)
return context
class ChartOfAccountModelCreateView(ChartOfAccountModelModelBaseViewMixIn, CreateView):
template_name = "chart_of_accounts/coa_create.html"
extra_context = {
"header_title": _("Create Chart of Accounts"),
"page_title": _("Create Chart of Account"),
}
def get_initial(self):
return {
"entity": self.request.entity,
}
def get_form(self, form_class=None):
return ChartOfAccountsModelCreateForm(
entity_model=self.request.entity, **self.get_form_kwargs()
)
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=None, **kwargs)
context["header_subtitle"] = (
f"New Chart of Accounts: {self.request.entity.name}"
)
context["header_subtitle_icon"] = "gravity-ui:hierarchy"
return context
def get_success_url(self):
return reverse(
"coa-list",
kwargs={
"dealer_slug": self.request.dealer.slug,
"entity_slug": self.request.entity.slug,
},
)
class ChartOfAccountModelUpdateView(ChartOfAccountModelModelBaseViewMixIn, UpdateView):
context_object_name = "coa_model"
slug_url_kwarg = "coa_slug"
template_name = "chart_of_accounts/coa_update.html"
form_class = ChartOfAccountsModelUpdateForm
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
chart_of_accounts_model: ChartOfAccountModel = self.object
context["page_title"] = (
f"Update Chart of Account {chart_of_accounts_model.name}"
)
context["header_title"] = (
f"Update Chart of Account {chart_of_accounts_model.name}"
)
return context
def get_success_url(self):
return reverse(
"coa-list",
kwargs={
"dealer_slug": self.request.dealer.slug,
"entity_slug": self.request.entity.slug,
},
)
class CharOfAccountModelActionView(
ChartOfAccountModelModelBaseViewMixIn, RedirectView, SingleObjectMixin
):
http_method_names = ["get"]
slug_url_kwarg = "coa_slug"
action_name = None
commit = True
def get(self, request, *args, **kwargs):
kwargs["user_model"] = self.request.user
if not self.action_name:
raise ImproperlyConfigured("View attribute action_name is required.")
response = super(CharOfAccountModelActionView, self).get(
request, *args, **kwargs
)
coa_model: ChartOfAccountModel = self.get_object()
try:
getattr(coa_model, self.action_name)(commit=self.commit, **kwargs)
messages.add_message(
request,
level=messages.SUCCESS,
extra_tags="is-success",
message=_(
"Successfully updated {} Default Chart of Account to ".format(
request.entity.name
)
+ "{}".format(coa_model.name)
),
)
except ValidationError as e:
messages.add_message(
request, message=e.message, level=messages.ERROR, extra_tags="is-danger"
)
return response