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