1211 lines
45 KiB
Python
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
|