update in the bill and po
This commit is contained in:
parent
2a397c3a2b
commit
6aa2ed130c
@ -1325,9 +1325,9 @@ class BillModelCreateForm(BillModelCreateFormBase):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
self.fields["cash_account"].widget = forms.HiddenInput()
|
# self.fields["cash_account"].widget = forms.HiddenInput()
|
||||||
self.fields["prepaid_account"].widget = forms.HiddenInput()
|
# self.fields["prepaid_account"].widget = forms.HiddenInput()
|
||||||
self.fields["unearned_account"].widget = forms.HiddenInput()
|
# self.fields["unearned_account"].widget = forms.HiddenInput()
|
||||||
|
|
||||||
|
|
||||||
class SaleOrderForm(forms.ModelForm):
|
class SaleOrderForm(forms.ModelForm):
|
||||||
|
|||||||
@ -380,3 +380,13 @@ def po_item_formset_table(context, po_model, itemtxs_formset):
|
|||||||
'po_model': po_model,
|
'po_model': po_model,
|
||||||
'itemtxs_formset': itemtxs_formset,
|
'itemtxs_formset': itemtxs_formset,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@register.inclusion_tag('bill/tags/bill_item_formset.html', takes_context=True)
|
||||||
|
def bill_item_formset_table(context, item_formset):
|
||||||
|
return {
|
||||||
|
'entity_slug': context['view'].kwargs['entity_slug'],
|
||||||
|
'bill_pk': context['view'].kwargs['bill_pk'],
|
||||||
|
'total_amount__sum': context['total_amount__sum'],
|
||||||
|
'item_formset': item_formset,
|
||||||
|
}
|
||||||
|
|||||||
@ -693,7 +693,53 @@ path(
|
|||||||
),
|
),
|
||||||
# Bills
|
# Bills
|
||||||
path("items/bills/", views.BillListView.as_view(), name="bill_list"),
|
path("items/bills/", views.BillListView.as_view(), name="bill_list"),
|
||||||
path("items/bills/create/", views.BillModelCreateViewView.as_view(), name="bill_create"),
|
# path("items/bills/create/", views.BillModelCreateViewView.as_view(), name="bill_create"),
|
||||||
|
path('items/bills/<slug:entity_slug>/create/',
|
||||||
|
views.BillModelCreateViewView.as_view(),
|
||||||
|
name='bill-create'),
|
||||||
|
path('items/bills/<slug:entity_slug>/create/purchase-order/<uuid:po_pk>/',
|
||||||
|
views.BillModelCreateViewView.as_view(for_purchase_order=True),
|
||||||
|
name='bill-create-po'),
|
||||||
|
path('items/bills/<slug:entity_slug>/create/estimate/<uuid:ce_pk>/',
|
||||||
|
views.BillModelCreateViewView.as_view(for_estimate=True),
|
||||||
|
name='bill-create-estimate'),
|
||||||
|
path('items/bills/<slug:entity_slug>/detail/<uuid:bill_pk>/',
|
||||||
|
views.BillModelDetailViewView.as_view(),
|
||||||
|
name='bill-detail'),
|
||||||
|
path('items/bills/<slug:entity_slug>/update/<uuid:bill_pk>/',
|
||||||
|
views.BillModelUpdateViewView.as_view(),
|
||||||
|
name='bill-update'),
|
||||||
|
path('items/bills/<slug:entity_slug>/update/<uuid:bill_pk>/items/',
|
||||||
|
views.BillModelUpdateViewView.as_view(action_update_items=True),
|
||||||
|
name='bill-update-items'),
|
||||||
|
############################################################
|
||||||
|
path('items/bills/<slug:entity_slug>/actions/<uuid:bill_pk>/mark-as-draft/',
|
||||||
|
views.BillModelActionMarkAsDraftView.as_view(),
|
||||||
|
name='bill-action-mark-as-draft'),
|
||||||
|
path('items/bills/<slug:entity_slug>/actions/<uuid:bill_pk>/mark-as-review/',
|
||||||
|
views.BillModelActionMarkAsInReviewView.as_view(),
|
||||||
|
name='bill-action-mark-as-review'),
|
||||||
|
path('items/bills/<slug:entity_slug>/actions/<uuid:bill_pk>/mark-as-approved/',
|
||||||
|
views.BillModelActionMarkAsApprovedView.as_view(),
|
||||||
|
name='bill-action-mark-as-approved'),
|
||||||
|
path('items/bills/<slug:entity_slug>/actions/<uuid:bill_pk>/mark-as-paid/',
|
||||||
|
views.BillModelActionMarkAsPaidView.as_view(),
|
||||||
|
name='bill-action-mark-as-paid'),
|
||||||
|
path('items/bills/<slug:entity_slug>/actions/<uuid:bill_pk>/mark-as-void/',
|
||||||
|
views.BillModelActionVoidView.as_view(),
|
||||||
|
name='bill-action-mark-as-void'),
|
||||||
|
path('items/bills/<slug:entity_slug>/actions/<uuid:bill_pk>/mark-as-canceled/',
|
||||||
|
views.BillModelActionCanceledView.as_view(),
|
||||||
|
name='bill-action-mark-as-canceled'),
|
||||||
|
path('items/bills/<slug:entity_slug>/actions/<uuid:bill_pk>/lock-ledger/',
|
||||||
|
views.BillModelActionLockLedgerView.as_view(),
|
||||||
|
name='bill-action-lock-ledger'),
|
||||||
|
path('items/bills/<slug:entity_slug>/actions/<uuid:bill_pk>/unlock-ledger/',
|
||||||
|
views.BillModelActionUnlockLedgerView.as_view(),
|
||||||
|
name='bill-action-unlock-ledger'),
|
||||||
|
path('items/bills/<slug:entity_slug>/actions/<uuid:bill_pk>/force-migration/',
|
||||||
|
views.BillModelActionForceMigrateView.as_view(),
|
||||||
|
name='bill-action-force-migrate'),
|
||||||
# path("items/bills/create/", views.bill_create, name="bill_create"),
|
# path("items/bills/create/", views.bill_create, name="bill_create"),
|
||||||
path(
|
path(
|
||||||
"items/bills/<uuid:pk>/bill_detail/",
|
"items/bills/<uuid:pk>/bill_detail/",
|
||||||
@ -818,8 +864,8 @@ path(
|
|||||||
path('purchase_orders/<slug:entity_slug>/update/<uuid:po_pk>/update-items/',
|
path('purchase_orders/<slug:entity_slug>/update/<uuid:po_pk>/update-items/',
|
||||||
views.PurchaseOrderUpdateView.as_view(action_update_items=True),
|
views.PurchaseOrderUpdateView.as_view(action_update_items=True),
|
||||||
name='purchase_order_update_items'),
|
name='purchase_order_update_items'),
|
||||||
path('purchase_orders/inventory_item/<uuid:pk>/create/', views.InventoryItemCreateView, name='inventory_item_create'),
|
path('purchase_orders/inventory_item/create/', views.InventoryItemCreateView, name='inventory_item_create'),
|
||||||
path('purchase_orders/<uuid:po_pk>/inventory_items_filter/', views.inventory_items_filter, name='inventory_items_filter'),
|
path('purchase_orders/inventory_items_filter/', views.inventory_items_filter, name='inventory_items_filter'),
|
||||||
path('purchase_orders/<slug:entity_slug>/delete/<uuid:po_pk>/',
|
path('purchase_orders/<slug:entity_slug>/delete/<uuid:po_pk>/',
|
||||||
views.PurchaseOrderModelDeleteView.as_view(),
|
views.PurchaseOrderModelDeleteView.as_view(),
|
||||||
name='po-delete'),
|
name='po-delete'),
|
||||||
|
|||||||
@ -26,7 +26,7 @@ from django.db.models import Q
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Func
|
from django.db.models import Func
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.http import Http404, HttpResponseRedirect, JsonResponse, HttpResponseForbidden
|
from django.http import Http404, HttpResponseNotFound, HttpResponseRedirect, JsonResponse, HttpResponseForbidden
|
||||||
from django.forms import HiddenInput, ValidationError
|
from django.forms import HiddenInput, ValidationError
|
||||||
from django.shortcuts import HttpResponse
|
from django.shortcuts import HttpResponse
|
||||||
|
|
||||||
@ -84,12 +84,16 @@ from django_ledger.forms.bank_account import (
|
|||||||
BankAccountUpdateForm,
|
BankAccountUpdateForm,
|
||||||
)
|
)
|
||||||
from django_ledger.views.bill import (
|
from django_ledger.views.bill import (
|
||||||
BillModelCreateView
|
BillModelCreateView,
|
||||||
# BillModelUpdateView as BillModelUpdateViewBase
|
BillModelDetailView,
|
||||||
|
BillModelUpdateView,
|
||||||
|
BaseBillActionView as BaseBillActionViewBase,
|
||||||
)
|
)
|
||||||
from django_ledger.forms.bill import (
|
from django_ledger.forms.bill import (
|
||||||
ApprovedBillModelUpdateForm,
|
ApprovedBillModelUpdateForm,
|
||||||
InReviewBillModelUpdateForm,
|
InReviewBillModelUpdateForm,
|
||||||
|
get_bill_itemtxs_formset_class,
|
||||||
|
|
||||||
)
|
)
|
||||||
from django_ledger.forms.invoice import (
|
from django_ledger.forms.invoice import (
|
||||||
DraftInvoiceModelUpdateForm,
|
DraftInvoiceModelUpdateForm,
|
||||||
@ -6067,6 +6071,10 @@ class BillListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
|
|||||||
qs = dealer.entity.get_bills()
|
qs = dealer.entity.get_bills()
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context["entity"] = get_user_type(self.request).entity
|
||||||
|
return context
|
||||||
|
|
||||||
class BillDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
class BillDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||||
"""
|
"""
|
||||||
@ -6282,139 +6290,216 @@ def bill_mark_as_paid(request, pk):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
class BillModelCreateViewView(BillModelCreateView):
|
||||||
@permission_required("django_ledger.add_billmodel", raise_exception=True)
|
template_name = 'bill/bill_create.html'
|
||||||
def bill_create(request):
|
def get_context_data(self, **kwargs):
|
||||||
"""
|
context = super().get_context_data(**kwargs)
|
||||||
Handles creation of a bill in the system, including the validation of input data,
|
context["entity"] = get_user_type(self.request).entity
|
||||||
creation of transactions associated with the bill, and rendering of the appropriate
|
return context
|
||||||
response or form. Ensures the user creating the bill has the necessary permissions and
|
class BillModelDetailViewView(BillModelDetailView):
|
||||||
correct input parameters for successful bill creation and itemization.
|
template_name = 'bill/bill_detail.html'
|
||||||
|
class BillModelUpdateViewView(BillModelUpdateView):
|
||||||
|
template_name = 'bill/bill_update.html'
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
if self.action_update_items:
|
||||||
|
|
||||||
:param request: Django HttpRequest object containing metadata and data of the HTTP request.
|
if not request.user.is_authenticated:
|
||||||
:type request: HttpRequest
|
return HttpResponseForbidden()
|
||||||
:return: JsonResponse with success/error information if the request is processed,
|
|
||||||
or HttpResponse rendering the form for bill creation.
|
|
||||||
:rtype: JsonResponse or HttpResponse
|
|
||||||
"""
|
|
||||||
dealer = get_user_type(request)
|
|
||||||
entity = dealer.entity
|
|
||||||
|
|
||||||
if request.method == "POST":
|
queryset = self.get_queryset()
|
||||||
data = json.loads(request.body)
|
entity_model: EntityModel = self.get_authorized_entity_instance()
|
||||||
vendor_id = data.get("vendor")
|
bill_model: BillModel = self.get_object(queryset=queryset)
|
||||||
terms = data.get("terms")
|
bill_pk = bill_model.uuid
|
||||||
vendor = entity.get_vendors().filter(pk=vendor_id).first()
|
|
||||||
|
|
||||||
items = data.get("item", [])
|
self.object = bill_model
|
||||||
quantities = data.get("quantity", [])
|
|
||||||
|
|
||||||
if not all([items, quantities]):
|
bill_itemtxs_formset_class = get_bill_itemtxs_formset_class(bill_model)
|
||||||
return JsonResponse(
|
itemtxs_formset = bill_itemtxs_formset_class(
|
||||||
{"status": "error", "message": _("Items and Quantities are required")},
|
request.POST,
|
||||||
status=400,
|
bill_model=bill_model,
|
||||||
|
entity_model=entity_model
|
||||||
)
|
)
|
||||||
if isinstance(quantities, list):
|
|
||||||
if "0" in quantities:
|
|
||||||
return JsonResponse(
|
|
||||||
{
|
|
||||||
"status": "error",
|
|
||||||
"message": _("Quantity must be greater than zero"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if int(quantities) <= 0:
|
|
||||||
return JsonResponse(
|
|
||||||
{
|
|
||||||
"status": "error",
|
|
||||||
"message": _("Quantity must be greater than zero"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
bill = entity.create_bill(vendor_model=vendor, terms=terms)
|
if itemtxs_formset.has_changed():
|
||||||
if isinstance(items, list):
|
if itemtxs_formset.is_valid():
|
||||||
item_quantity_map = {}
|
itemtxs_list = itemtxs_formset.save(commit=False)
|
||||||
for item, quantity in zip(items, quantities):
|
|
||||||
if item in item_quantity_map:
|
|
||||||
item_quantity_map[item] += int(quantity)
|
|
||||||
else:
|
|
||||||
item_quantity_map[item] = int(quantity)
|
|
||||||
item_list = list(item_quantity_map.keys())
|
|
||||||
quantity_list = list(item_quantity_map.values())
|
|
||||||
|
|
||||||
items_list = [
|
for itemtxs in itemtxs_list:
|
||||||
{"item_id": item_list[i], "quantity": quantity_list[i]}
|
itemtxs.bill_model_id = bill_model.uuid
|
||||||
for i in range(len(item_list))
|
itemtxs.clean()
|
||||||
]
|
|
||||||
items_txs = []
|
|
||||||
for item in items_list:
|
|
||||||
item_instance = ItemModel.objects.get(pk=item.get("item_id"))
|
|
||||||
car = models.Car.objects.get(vin=item_instance.name)
|
|
||||||
quantity = Decimal(item.get("quantity"))
|
|
||||||
items_txs.append(
|
|
||||||
{
|
|
||||||
"item_number": item_instance.item_number,
|
|
||||||
"quantity": quantity,
|
|
||||||
"unit_cost": car.finances.cost_price,
|
|
||||||
"total_amount": car.finances.cost_price * quantity,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
bill_itemtxs = {
|
itemtxs_formset.save()
|
||||||
item.get("item_number"): {
|
itemtxs_qs = bill_model.update_amount_due()
|
||||||
"unit_cost": item.get("unit_cost"),
|
bill_model.get_state(commit=True)
|
||||||
"quantity": item.get("quantity"),
|
bill_model.clean()
|
||||||
"total_amount": item.get("total_amount"),
|
bill_model.save(
|
||||||
}
|
update_fields=[
|
||||||
for item in items_txs
|
'amount_due',
|
||||||
}
|
'amount_receivable',
|
||||||
else:
|
'amount_unearned',
|
||||||
item = entity.get_items_all().filter(pk=items).first()
|
'amount_earned',
|
||||||
instance = models.Car.objects.get(vin=item.name)
|
'updated'
|
||||||
bill_itemtxs = {
|
])
|
||||||
item.item_number: {
|
|
||||||
"unit_cost": instance.finances.cost_price,
|
|
||||||
"quantity": Decimal(quantities),
|
|
||||||
"total_amount": instance.finances.cost_price * Decimal(quantities),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bill_itemtxs = bill.migrate_itemtxs(
|
bill_model.migrate_state(
|
||||||
itemtxs=bill_itemtxs,
|
entity_slug=self.kwargs['entity_slug'],
|
||||||
commit=True,
|
user_model=self.request.user,
|
||||||
operation=BillModel.ITEMIZE_APPEND,
|
itemtxs_qs=itemtxs_qs,
|
||||||
)
|
raise_exception=False
|
||||||
|
)
|
||||||
|
|
||||||
url = reverse("bill_detail", kwargs={"pk": bill.pk})
|
messages.add_message(request,
|
||||||
return JsonResponse(
|
message=f'Items for Invoice {bill_model.bill_number} saved.',
|
||||||
{
|
level=messages.SUCCESS,)
|
||||||
"status": "success",
|
|
||||||
"message": _("Bill created successfully"),
|
|
||||||
"url": f"{url}",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
form = forms.BillModelCreateForm(entity_model=entity)
|
# if valid get saved formset from DB
|
||||||
form.initial.update(
|
return HttpResponseRedirect(
|
||||||
{
|
redirect_to=reverse('bill-update',
|
||||||
"cash_account": dealer.settings.bill_cash_account,
|
kwargs={
|
||||||
"prepaid_account": dealer.settings.bill_prepaid_account,
|
'entity_slug': entity_model.slug,
|
||||||
"unearned_account": dealer.settings.bill_unearned_account,
|
'bill_pk': bill_pk
|
||||||
}
|
})
|
||||||
)
|
)
|
||||||
car_list = models.Car.objects.filter(dealer=dealer)
|
context = self.get_context_data(itemtxs_formset=itemtxs_formset)
|
||||||
context = {
|
return self.render_to_response(context=context)
|
||||||
"form": form,
|
return super(BillModelUpdateViewView, self).post(request, **kwargs)
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"car": x,
|
|
||||||
"product": entity.get_items_products().filter(name=x.vin).first(),
|
|
||||||
}
|
|
||||||
for x in car_list
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, "ledger/bills/bill_form.html", context)
|
def get_success_url(self):
|
||||||
|
return reverse("bill-update", kwargs={"entity_slug": self.kwargs["entity_slug"], "bill_pk": self.kwargs["bill_pk"]})
|
||||||
|
|
||||||
|
# @login_required
|
||||||
|
# @permission_required("django_ledger.add_billmodel", raise_exception=True)
|
||||||
|
# def bill_create(request):
|
||||||
|
# """
|
||||||
|
# Handles creation of a bill in the system, including the validation of input data,
|
||||||
|
# creation of transactions associated with the bill, and rendering of the appropriate
|
||||||
|
# response or form. Ensures the user creating the bill has the necessary permissions and
|
||||||
|
# correct input parameters for successful bill creation and itemization.
|
||||||
|
|
||||||
|
# :param request: Django HttpRequest object containing metadata and data of the HTTP request.
|
||||||
|
# :type request: HttpRequest
|
||||||
|
# :return: JsonResponse with success/error information if the request is processed,
|
||||||
|
# or HttpResponse rendering the form for bill creation.
|
||||||
|
# :rtype: JsonResponse or HttpResponse
|
||||||
|
# """
|
||||||
|
# dealer = get_user_type(request)
|
||||||
|
# entity = dealer.entity
|
||||||
|
|
||||||
|
# if request.method == "POST":
|
||||||
|
# data = json.loads(request.body)
|
||||||
|
# vendor_id = data.get("vendor")
|
||||||
|
# terms = data.get("terms")
|
||||||
|
# vendor = entity.get_vendors().filter(pk=vendor_id).first()
|
||||||
|
|
||||||
|
# items = data.get("item", [])
|
||||||
|
# quantities = data.get("quantity", [])
|
||||||
|
|
||||||
|
# if not all([items, quantities]):
|
||||||
|
# return JsonResponse(
|
||||||
|
# {"status": "error", "message": _("Items and Quantities are required")},
|
||||||
|
# status=400,
|
||||||
|
# )
|
||||||
|
# if isinstance(quantities, list):
|
||||||
|
# if "0" in quantities:
|
||||||
|
# return JsonResponse(
|
||||||
|
# {
|
||||||
|
# "status": "error",
|
||||||
|
# "message": _("Quantity must be greater than zero"),
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
# else:
|
||||||
|
# if int(quantities) <= 0:
|
||||||
|
# return JsonResponse(
|
||||||
|
# {
|
||||||
|
# "status": "error",
|
||||||
|
# "message": _("Quantity must be greater than zero"),
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
|
||||||
|
# bill = entity.create_bill(vendor_model=vendor, terms=terms)
|
||||||
|
# if isinstance(items, list):
|
||||||
|
# item_quantity_map = {}
|
||||||
|
# for item, quantity in zip(items, quantities):
|
||||||
|
# if item in item_quantity_map:
|
||||||
|
# item_quantity_map[item] += int(quantity)
|
||||||
|
# else:
|
||||||
|
# item_quantity_map[item] = int(quantity)
|
||||||
|
# item_list = list(item_quantity_map.keys())
|
||||||
|
# quantity_list = list(item_quantity_map.values())
|
||||||
|
|
||||||
|
# items_list = [
|
||||||
|
# {"item_id": item_list[i], "quantity": quantity_list[i]}
|
||||||
|
# for i in range(len(item_list))
|
||||||
|
# ]
|
||||||
|
# items_txs = []
|
||||||
|
# for item in items_list:
|
||||||
|
# item_instance = ItemModel.objects.get(pk=item.get("item_id"))
|
||||||
|
# car = models.Car.objects.get(vin=item_instance.name)
|
||||||
|
# quantity = Decimal(item.get("quantity"))
|
||||||
|
# items_txs.append(
|
||||||
|
# {
|
||||||
|
# "item_number": item_instance.item_number,
|
||||||
|
# "quantity": quantity,
|
||||||
|
# "unit_cost": car.finances.cost_price,
|
||||||
|
# "total_amount": car.finances.cost_price * quantity,
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
|
||||||
|
# bill_itemtxs = {
|
||||||
|
# item.get("item_number"): {
|
||||||
|
# "unit_cost": item.get("unit_cost"),
|
||||||
|
# "quantity": item.get("quantity"),
|
||||||
|
# "total_amount": item.get("total_amount"),
|
||||||
|
# }
|
||||||
|
# for item in items_txs
|
||||||
|
# }
|
||||||
|
# else:
|
||||||
|
# item = entity.get_items_all().filter(pk=items).first()
|
||||||
|
# instance = models.Car.objects.get(vin=item.name)
|
||||||
|
# bill_itemtxs = {
|
||||||
|
# item.item_number: {
|
||||||
|
# "unit_cost": instance.finances.cost_price,
|
||||||
|
# "quantity": Decimal(quantities),
|
||||||
|
# "total_amount": instance.finances.cost_price * Decimal(quantities),
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
# bill_itemtxs = bill.migrate_itemtxs(
|
||||||
|
# itemtxs=bill_itemtxs,
|
||||||
|
# commit=True,
|
||||||
|
# operation=BillModel.ITEMIZE_APPEND,
|
||||||
|
# )
|
||||||
|
|
||||||
|
# url = reverse("bill_detail", kwargs={"pk": bill.pk})
|
||||||
|
# return JsonResponse(
|
||||||
|
# {
|
||||||
|
# "status": "success",
|
||||||
|
# "message": _("Bill created successfully"),
|
||||||
|
# "url": f"{url}",
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
|
||||||
|
# form = forms.BillModelCreateForm(entity_model=entity)
|
||||||
|
# form.initial.update(
|
||||||
|
# {
|
||||||
|
# "cash_account": dealer.settings.bill_cash_account,
|
||||||
|
# "prepaid_account": dealer.settings.bill_prepaid_account,
|
||||||
|
# "unearned_account": dealer.settings.bill_unearned_account,
|
||||||
|
# }
|
||||||
|
# )
|
||||||
|
# car_list = models.Car.objects.filter(dealer=dealer)
|
||||||
|
# context = {
|
||||||
|
# "form": form,
|
||||||
|
# "items": [
|
||||||
|
# {
|
||||||
|
# "car": x,
|
||||||
|
# "product": entity.get_items_products().filter(name=x.vin).first(),
|
||||||
|
# }
|
||||||
|
# for x in car_list
|
||||||
|
# ],
|
||||||
|
# }
|
||||||
|
|
||||||
|
# return render(request, "ledger/bills/bill_form.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -8312,34 +8397,73 @@ def PurchaseOrderCreateView(request):
|
|||||||
form = PurchaseOrderModelCreateForm(entity_slug=entity.slug, user_model=entity.admin)
|
form = PurchaseOrderModelCreateForm(entity_slug=entity.slug, user_model=entity.admin)
|
||||||
return render(request, "purchase_orders/po_form.html", {"form": form})
|
return render(request, "purchase_orders/po_form.html", {"form": form})
|
||||||
|
|
||||||
def InventoryItemCreateView(request,pk):
|
def InventoryItemCreateView(request):
|
||||||
po = get_object_or_404(PurchaseOrderModel, pk=pk)
|
|
||||||
dealer = get_user_type(request)
|
dealer = get_user_type(request)
|
||||||
entity = dealer.entity
|
entity = dealer.entity
|
||||||
coa = entity.get_default_coa()
|
coa = entity.get_default_coa()
|
||||||
inventory_accounts = entity.get_coa_all().get(name='ASSET_CA_INVENTORY')
|
|
||||||
|
inventory_accounts = entity.get_coa_accounts().filter(role='asset_ca_inv')
|
||||||
|
cogs_accounts = entity.get_coa_accounts().filter(role='cogs_regular')
|
||||||
|
|
||||||
if(request.method == "POST"):
|
if(request.method == "POST"):
|
||||||
make = request.POST.get("make")
|
name = request.POST.get("name")
|
||||||
model = request.POST.get("model")
|
account = request.POST.get("account")
|
||||||
serie = request.POST.get("serie")
|
account = inventory_accounts.get(pk=account)
|
||||||
trim = request.POST.get("trim")
|
inventory_name = None
|
||||||
|
if name:
|
||||||
|
inventory_name = name
|
||||||
|
else:
|
||||||
|
make = request.POST.get("make")
|
||||||
|
model = request.POST.get("model")
|
||||||
|
serie = request.POST.get("serie")
|
||||||
|
trim = request.POST.get("trim")
|
||||||
|
|
||||||
make_name = models.CarMake.objects.get(pk=make)
|
make_name = models.CarMake.objects.get(pk=make)
|
||||||
model_name = models.CarModel.objects.get(pk=model)
|
model_name = models.CarModel.objects.get(pk=model)
|
||||||
serie_name = models.CarSerie.objects.get(pk=serie)
|
serie_name = models.CarSerie.objects.get(pk=serie)
|
||||||
trim_name = models.CarTrim.objects.get(pk=trim)
|
trim_name = models.CarTrim.objects.get(pk=trim)
|
||||||
|
|
||||||
inventory_name = f"{make_name.name} - {model_name.name} - {serie_name.name} - {trim_name.name}"
|
inventory_name = f"{make_name.name} - {model_name.name} - {serie_name.name} - {trim_name.name}"
|
||||||
uom = entity.get_uom_all().get(name='Unit')
|
uom = entity.get_uom_all().get(name='Unit')
|
||||||
entity.create_item_inventory(
|
entity.create_item_inventory(
|
||||||
name=inventory_name,
|
name=inventory_name,
|
||||||
uom_model=uom,
|
uom_model=uom,
|
||||||
item_type=ItemModel.ITEM_TYPE_MATERIAL
|
item_type=ItemModel.ITEM_TYPE_MATERIAL,
|
||||||
|
inventory_account=account,
|
||||||
|
coa_model=coa
|
||||||
)
|
)
|
||||||
messages.success(request, _("Inventory item created successfully"))
|
messages.success(request, _("Inventory item created successfully"))
|
||||||
return redirect('purchase_order_detail', pk=po.pk)
|
return redirect('purchase_order_list')
|
||||||
|
return render(request,'purchase_orders/inventory_item_form.html',{"make_data":models.CarMake.objects.all(),"inventory_accounts":inventory_accounts,"cogs_accounts":cogs_accounts})
|
||||||
|
|
||||||
|
|
||||||
|
def inventory_items_filter(request):
|
||||||
|
dealer = get_user_type(request)
|
||||||
|
make = request.GET.get('make')
|
||||||
|
model = request.GET.get('model')
|
||||||
|
serie = request.GET.get('serie')
|
||||||
|
|
||||||
|
model_data = models.CarModel.objects.none()
|
||||||
|
serie_data = models.CarSerie.objects.none()
|
||||||
|
trim_data = models.CarTrim.objects.none()
|
||||||
|
if make:
|
||||||
|
make = models.CarMake.objects.get(pk=make)
|
||||||
|
model_data = make.carmodel_set.all()
|
||||||
|
elif model:
|
||||||
|
model = models.CarModel.objects.get(pk=model)
|
||||||
|
serie_data = model.carserie_set.all()
|
||||||
|
elif serie:
|
||||||
|
serie = models.CarSerie.objects.get(pk=serie)
|
||||||
|
trim_data = serie.cartrim_set.all()
|
||||||
|
context = {
|
||||||
|
'model_data': model_data,
|
||||||
|
'serie_data': serie_data,
|
||||||
|
'trim_data': trim_data,
|
||||||
|
# 'inventory_items': dealer.entity.get_items_inventory(),
|
||||||
|
# 'entity_slug': dealer.entity.slug,
|
||||||
|
}
|
||||||
|
return render(request, "purchase_orders/car_inventory_item_form.html", context)
|
||||||
|
|
||||||
class PurchaseOrderDetailView(PurchaseOrderModelDetailViewBase):
|
class PurchaseOrderDetailView(PurchaseOrderModelDetailViewBase):
|
||||||
template_name = 'purchase_orders/po_detail.html'
|
template_name = 'purchase_orders/po_detail.html'
|
||||||
context_object_name = 'po_model'
|
context_object_name = 'po_model'
|
||||||
@ -8360,37 +8484,6 @@ class PurchaseOrderDetailView(PurchaseOrderModelDetailViewBase):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
def inventory_items_filter(request,po_pk):
|
|
||||||
dealer = get_user_type(request)
|
|
||||||
make = request.GET.get('make')
|
|
||||||
model = request.GET.get('model')
|
|
||||||
serie = request.GET.get('serie')
|
|
||||||
make_data = models.CarMake.objects.all()
|
|
||||||
model_data = models.CarModel.objects.none()
|
|
||||||
serie_data = models.CarSerie.objects.none()
|
|
||||||
trim_data = models.CarTrim.objects.none()
|
|
||||||
if make:
|
|
||||||
make = models.CarMake.objects.get(pk=make)
|
|
||||||
model_data = make.carmodel_set.all()
|
|
||||||
elif model:
|
|
||||||
model = models.CarModel.objects.get(pk=model)
|
|
||||||
serie_data = model.carserie_set.all()
|
|
||||||
elif serie:
|
|
||||||
serie = models.CarSerie.objects.get(pk=serie)
|
|
||||||
trim_data = serie.cartrim_set.all()
|
|
||||||
context = {
|
|
||||||
'make_data': make_data,
|
|
||||||
'model_data': model_data,
|
|
||||||
'serie_data': serie_data,
|
|
||||||
'trim_data': trim_data,
|
|
||||||
'inventory_accounts': dealer.entity.get_coa_accounts().filter(role="asset_ca_inv"),
|
|
||||||
'inventory_items': dealer.entity.get_items_inventory(),
|
|
||||||
'entity_slug': dealer.entity.slug,
|
|
||||||
'po_model': get_object_or_404(PurchaseOrderModel, pk=po_pk)
|
|
||||||
}
|
|
||||||
return render(request, "purchase_orders/po_detail.html", context)
|
|
||||||
|
|
||||||
|
|
||||||
# def PurchaseOrderDetailView(request, pk):
|
# def PurchaseOrderDetailView(request, pk):
|
||||||
# po = get_object_or_404(PurchaseOrderModel, pk=pk)
|
# po = get_object_or_404(PurchaseOrderModel, pk=pk)
|
||||||
# dealer = get_user_type(request)
|
# dealer = get_user_type(request)
|
||||||
@ -8497,7 +8590,7 @@ class PurchaseOrderUpdateView(PurchaseOrderModelUpdateViewBase):
|
|||||||
if create_bill_uuids:
|
if create_bill_uuids:
|
||||||
item_uuids = ','.join(create_bill_uuids)
|
item_uuids = ','.join(create_bill_uuids)
|
||||||
redirect_url = reverse(
|
redirect_url = reverse(
|
||||||
'django_ledger:bill-create-po',
|
'bill-create-po',
|
||||||
kwargs={
|
kwargs={
|
||||||
'entity_slug': self.kwargs['entity_slug'],
|
'entity_slug': self.kwargs['entity_slug'],
|
||||||
'po_pk': po_model.uuid,
|
'po_pk': po_model.uuid,
|
||||||
@ -8604,8 +8697,50 @@ class PurchaseOrderMarkAsCanceledView(BasePurchaseOrderActionActionView):
|
|||||||
class PurchaseOrderMarkAsVoidView(BasePurchaseOrderActionActionView):
|
class PurchaseOrderMarkAsVoidView(BasePurchaseOrderActionActionView):
|
||||||
action_name = 'mark_as_void'
|
action_name = 'mark_as_void'
|
||||||
|
|
||||||
|
|
||||||
##############################bil
|
##############################bil
|
||||||
|
class BaseBillActionView(BaseBillActionViewBase):
|
||||||
|
def get_redirect_url(self, entity_slug, bill_pk, *args, **kwargs):
|
||||||
|
return reverse('bill-update',
|
||||||
|
kwargs={
|
||||||
|
'entity_slug': entity_slug,
|
||||||
|
'bill_pk': bill_pk
|
||||||
|
})
|
||||||
|
|
||||||
class BillModelCreateViewView(BillModelCreateView):
|
class BillModelActionMarkAsDraftView(BaseBillActionView):
|
||||||
template_name = 'ledger/bills/bill_form.html'
|
action_name = 'mark_as_draft'
|
||||||
|
|
||||||
|
|
||||||
|
class BillModelActionMarkAsInReviewView(BaseBillActionView):
|
||||||
|
action_name = 'mark_as_review'
|
||||||
|
|
||||||
|
|
||||||
|
class BillModelActionMarkAsApprovedView(BaseBillActionView):
|
||||||
|
action_name = 'mark_as_approved'
|
||||||
|
|
||||||
|
|
||||||
|
class BillModelActionMarkAsPaidView(BaseBillActionView):
|
||||||
|
action_name = 'mark_as_paid'
|
||||||
|
|
||||||
|
|
||||||
|
class BillModelActionDeleteView(BaseBillActionView):
|
||||||
|
action_name = 'mark_as_delete'
|
||||||
|
|
||||||
|
|
||||||
|
class BillModelActionVoidView(BaseBillActionView):
|
||||||
|
action_name = 'mark_as_void'
|
||||||
|
|
||||||
|
|
||||||
|
class BillModelActionCanceledView(BaseBillActionView):
|
||||||
|
action_name = 'mark_as_canceled'
|
||||||
|
|
||||||
|
|
||||||
|
class BillModelActionLockLedgerView(BaseBillActionView):
|
||||||
|
action_name = 'lock_ledger'
|
||||||
|
|
||||||
|
|
||||||
|
class BillModelActionUnlockLedgerView(BaseBillActionView):
|
||||||
|
action_name = 'unlock_ledger'
|
||||||
|
|
||||||
|
|
||||||
|
class BillModelActionForceMigrateView(BaseBillActionView):
|
||||||
|
action_name = 'migrate_state'
|
||||||
@ -2,6 +2,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load django_ledger %}
|
{% load django_ledger %}
|
||||||
|
{% load crispy_forms_filters %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
@ -27,7 +28,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
{{ form|add_class:"form-control" }}
|
{{ form|crispy }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -37,7 +38,7 @@
|
|||||||
id="djl-bill-create-button"
|
id="djl-bill-create-button"
|
||||||
class="btn btn-primary btn-lg">{% trans 'Create' %}
|
class="btn btn-primary btn-lg">{% trans 'Create' %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'django_ledger:bill-list' entity_slug=view.kwargs.entity_slug %}"
|
<a href="{{request.META.HTTP_REFERER}}"
|
||||||
id="djl-bill-create-back-button"
|
id="djl-bill-create-back-button"
|
||||||
class="btn btn-outline-secondary">{% trans 'Cancel' %}</a>
|
class="btn btn-outline-secondary">{% trans 'Cancel' %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
275
templates/bill/bill_detail.html
Normal file
275
templates/bill/bill_detail.html
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% load django_ledger %}
|
||||||
|
|
||||||
|
{% block title %}Bill Details - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
|
{% block customCSS %}
|
||||||
|
<style>
|
||||||
|
/* Optional custom overrides for Bootstrap 5 */
|
||||||
|
.table th,
|
||||||
|
.table td {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header i {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xs {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-xxs {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<div class="row g-4">
|
||||||
|
<!-- Left Sidebar -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card h-100 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
{% include 'bill/includes/card_bill.html' with bill=bill entity_slug=view.kwargs.entity_slug style='bill-detail' %}
|
||||||
|
<hr class="my-4">
|
||||||
|
{% include 'django_ledger/vendor/includes/card_vendor.html' with vendor=bill.vendor %}
|
||||||
|
<div class="d-grid mt-4">
|
||||||
|
<a href="{% url 'django_ledger:bill-list' entity_slug=view.kwargs.entity_slug %}"
|
||||||
|
class="btn btn-outline-primary">
|
||||||
|
<i class="fas fa-arrow-left me-1"></i> {% trans 'Bill List' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="col-lg-8">
|
||||||
|
{% if bill.is_configured %}
|
||||||
|
<div class="card mb-4 shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row text-center g-3">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
|
{% trans 'Cash Account' %}:
|
||||||
|
<a href="{% url 'django_ledger:account-detail' account_pk=bill.cash_account.uuid coa_slug=bill.cash_account.coa_model.slug entity_slug=view.kwargs.entity_slug %}"
|
||||||
|
class="text-decoration-none ms-1">
|
||||||
|
{{ bill.cash_account.code }}
|
||||||
|
</a>
|
||||||
|
</h6>
|
||||||
|
<h4 class="mb-0" id="djl-bill-detail-amount-paid">
|
||||||
|
{% currency_symbol %}{{ bill.get_amount_cash | absolute | currency_format }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if bill.accrue %}
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
|
{% trans 'Prepaid Account' %}:
|
||||||
|
<a href="{% url 'django_ledger:account-detail' account_pk=bill.prepaid_account.uuid coa_slug=bill.prepaid_account.coa_model.slug entity_slug=view.kwargs.entity_slug %}"
|
||||||
|
class="text-decoration-none ms-1">
|
||||||
|
{{ bill.prepaid_account.code }}
|
||||||
|
</a>
|
||||||
|
</h6>
|
||||||
|
<h4 class="text-success mb-0" id="djl-bill-detail-amount-prepaid">
|
||||||
|
{% currency_symbol %}{{ bill.get_amount_prepaid | currency_format }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
|
{% trans 'Accounts Payable' %}:
|
||||||
|
<a href="{% url 'django_ledger:account-detail' account_pk=bill.unearned_account.uuid coa_slug=bill.unearned_account.coa_model.slug entity_slug=view.kwargs.entity_slug %}"
|
||||||
|
class="text-decoration-none ms-1">
|
||||||
|
{{ bill.unearned_account.code }}
|
||||||
|
</a>
|
||||||
|
</h6>
|
||||||
|
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-unearned">
|
||||||
|
{% currency_symbol %}{{ bill.get_amount_unearned | currency_format }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
|
{% trans 'Accrued' %} {{ bill.get_progress | percentage }}
|
||||||
|
</h6>
|
||||||
|
<h4 class="mb-0">
|
||||||
|
{% currency_symbol %}{{ bill.get_amount_earned | currency_format }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-md-3 offset-md-6">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<h6 class="text-uppercase text-xs text-muted mb-2">
|
||||||
|
{% trans 'You Still Owe' %}
|
||||||
|
</h6>
|
||||||
|
<h4 class="text-danger mb-0" id="djl-bill-detail-amount-owed">
|
||||||
|
{% currency_symbol %}{{ bill.get_amount_open | currency_format }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Bill Items Card -->
|
||||||
|
<div class="card mb-4 shadow-sm">
|
||||||
|
<div class="card-header pb-0">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="fas fa-receipt me-3 text-primary"></i>
|
||||||
|
<h5 class="mb-0">{% trans 'Bill Items' %}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body px-0 pt-0 pb-2">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-items-center mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7">{% trans 'Item' %}</th>
|
||||||
|
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7">{% trans 'Entity Unit' %}</th>
|
||||||
|
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7 text-end">{% trans 'Unit Cost' %}</th>
|
||||||
|
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7 text-end">{% trans 'Quantity' %}</th>
|
||||||
|
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7 text-end">{% trans 'Total' %}</th>
|
||||||
|
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7 text-end">{% trans 'PO' %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for bill_item in itemtxs_qs %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex px-2 py-1">
|
||||||
|
<div class="d-flex flex-column justify-content-center">
|
||||||
|
<h6 class="mb-0 text-sm">{{ bill_item.item_model }}</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="text-xs font-weight-bold">
|
||||||
|
{% if bill_item.entity_unit %}
|
||||||
|
{{ bill_item.entity_unit }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<span class="text-xs font-weight-bold">
|
||||||
|
{% currency_symbol %}{{ bill_item.unit_cost | currency_format }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<span class="text-xs font-weight-bold">{{ bill_item.quantity }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<span class="text-xs font-weight-bold">
|
||||||
|
{% currency_symbol %}{{ bill_item.total_amount | currency_format }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
{% if bill_item.po_model_id %}
|
||||||
|
<a class="btn btn-sm btn-outline-info"
|
||||||
|
href="{% url 'purchase_order_detail' bill_item.po_model_id %}">
|
||||||
|
{% trans 'View PO' %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
<td class="text-end"><strong>{% trans 'Total' %}</strong></td>
|
||||||
|
<td class="text-end">
|
||||||
|
<strong>
|
||||||
|
{% currency_symbol %}{{ total_amount__sum | currency_format }}
|
||||||
|
</strong>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if bill.is_active %}
|
||||||
|
<!-- Financial Statements Buttons -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-center gap-2 flex-wrap">
|
||||||
|
<a href="{% url 'django_ledger:ledger-bs' entity_slug=view.kwargs.entity_slug ledger_pk=bill.ledger_id %}"
|
||||||
|
class="btn btn-outline-info">
|
||||||
|
{% trans 'Balance Sheet' %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'django_ledger:ledger-ic' entity_slug=view.kwargs.entity_slug ledger_pk=bill.ledger_id %}"
|
||||||
|
class="btn btn-outline-info">
|
||||||
|
{% trans 'Income Statement' %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'django_ledger:ledger-cf' entity_slug=view.kwargs.entity_slug ledger_pk=bill.ledger_id %}"
|
||||||
|
class="btn btn-outline-info">
|
||||||
|
{% trans 'Cash Flow Statement' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-center gap-2 flex-wrap">
|
||||||
|
<a href="{% url 'django_ledger:ledger-bs-year' entity_slug=view.kwargs.entity_slug ledger_pk=bill.ledger_id year=bill.get_status_action_date.year %}?format=pdf&report_subtitle={{ bill.generate_descriptive_title | safe }}"
|
||||||
|
class="btn btn-outline-success">
|
||||||
|
{% trans 'Balance Sheet PDF' %} <i class="fas fa-download ms-1"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'django_ledger:ledger-ic-year' entity_slug=view.kwargs.entity_slug ledger_pk=bill.ledger_id year=bill.get_status_action_date.year %}?format=pdf&report_subtitle={{ bill.generate_descriptive_title | safe }}"
|
||||||
|
class="btn btn-outline-success">
|
||||||
|
{% trans 'Income Statement PDF' %} <i class="fas fa-download ms-1"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'django_ledger:ledger-cf-year' entity_slug=view.kwargs.entity_slug ledger_pk=bill.ledger_id year=bill.get_status_action_date.year %}?format=pdf&report_subtitle={{ bill.generate_descriptive_title | safe }}"
|
||||||
|
class="btn btn-outline-success">
|
||||||
|
{% trans 'Cash Flow Statement PDF' %} <i class="fas fa-download ms-1"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Bill Transactions Card -->
|
||||||
|
<div class="card mb-4 shadow-sm">
|
||||||
|
<div class="card-header pb-0">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="fas fa-exchange-alt me-3 text-primary"></i>
|
||||||
|
<h5 class="mb-0">{% trans 'Bill Transactions' %}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body px-0 pt-0 pb-2">
|
||||||
|
{% transactions_table bill %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bill Notes Card -->
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-header pb-0">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="fas fa-sticky-note me-3 text-primary"></i>
|
||||||
|
<h5 class="mb-0">{% trans 'Bill Notes' %}</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% include 'bill/includes/card_markdown.html' with style='card_1' title='' notes_html=bill.notes_html %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
57
templates/bill/bill_update.html
Normal file
57
templates/bill/bill_update.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% load django_ledger %}
|
||||||
|
{% load custom_filters %}
|
||||||
|
{% load widget_tweaks crispy_forms_filters %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container py-4">
|
||||||
|
<div class="row g-4">
|
||||||
|
<!-- Vendor Card -->
|
||||||
|
<div class="col-12">
|
||||||
|
{% include 'bill/includes/card_vendor.html' with vendor=bill_model.vendor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bill Form -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
{% include 'bill/includes/card_bill.html' with bill=bill_model style='bill-detail' entity_slug=view.kwargs.entity_slug %}
|
||||||
|
|
||||||
|
<form action="{% url 'bill-update' entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
{{ form|crispy }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary w-100 mb-2">
|
||||||
|
<i class="fas fa-save me-2"></i>{% trans 'Save Bill' %}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<a href="{% url 'bill-detail' entity_slug=view.kwargs.entity_slug bill_pk=bill_model.uuid %}"
|
||||||
|
class="btn btn-dark w-100 mb-2">
|
||||||
|
<i class="fas fa-arrow-left me-2"></i>{% trans 'Back to Bill Detail' %}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="{% url 'bill_list' %}"
|
||||||
|
class="btn btn-info w-100 mb-2">
|
||||||
|
<i class="fas fa-list me-2"></i>{% trans 'Bill List' %}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Bill Item Formset -->
|
||||||
|
<div class="col-12">
|
||||||
|
{% bill_item_formset_table itemtxs_formset %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include "bill/includes/mark_as.html" %}
|
||||||
|
{% endblock %}
|
||||||
307
templates/bill/includes/card_bill.html
Normal file
307
templates/bill/includes/card_bill.html
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
{% load django_ledger %}
|
||||||
|
{% load i18n %}
|
||||||
|
<div id="djl-bill-card-widget">
|
||||||
|
{% if not create_bill %}
|
||||||
|
{% if style == 'dashboard' %}
|
||||||
|
<!-- Dashboard Style Card -->
|
||||||
|
<div class="">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h6 class="text-uppercase text-secondary mb-0">
|
||||||
|
<i class="fas fa-file-invoice me-2"></i>{% trans 'Bill' %}
|
||||||
|
</h6>
|
||||||
|
<span class="badge bg-{{ bill.get_status_badge_color }}">{{ bill.get_bill_status_display }}</span>
|
||||||
|
</div>
|
||||||
|
<h4 class="card-title">{{ bill.vendor.vendor_name }}</h4>
|
||||||
|
<p class="text-sm text-muted mb-4">{{ bill.vendor.address_1 }}</p>
|
||||||
|
|
||||||
|
{% if not bill.is_past_due %}
|
||||||
|
<p class="text-info mb-2">
|
||||||
|
<i class="fas fa-clock me-2"></i>{% trans 'Due in' %}: {{ bill.date_due | timeuntil }}
|
||||||
|
</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-danger fw-bold mb-2">
|
||||||
|
<i class="fas fa-exclamation-triangle me-2"></i>{% trans 'Past Due' %}: {{ bill.date_due | timesince }} {% trans 'ago' %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<span class="me-2">{% trans 'Accrued' %}:</span>
|
||||||
|
{% if bill.accrue %}
|
||||||
|
<i class="fas fa-check-circle text-success me-2"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-times-circle text-danger me-2"></i>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="text-danger fw-bold mb-1">
|
||||||
|
{% trans 'Amount Due' %}: {% currency_symbol %}{{ bill.get_amount_open | currency_format }}
|
||||||
|
</p>
|
||||||
|
<p class="text-success mb-1">
|
||||||
|
{% trans 'Amount Paid' %}: {% currency_symbol %}{{ bill.amount_paid | currency_format }}
|
||||||
|
</p>
|
||||||
|
<p class="mb-1">
|
||||||
|
{% trans 'Progress' %}: {{ bill.get_progress | percentage }}
|
||||||
|
</p>
|
||||||
|
<div class="progress mt-2">
|
||||||
|
<div class="progress-bar bg-success"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{ bill.get_progress_percent }}%"
|
||||||
|
aria-valuenow="{{ bill.get_progress_percent }}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Action -->
|
||||||
|
{% modal_action bill 'get' entity_slug %}
|
||||||
|
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||||
|
|
||||||
|
<a href="{% url 'django_ledger:bill-detail' entity_slug=entity_slug bill_pk=bill.uuid %}"
|
||||||
|
class="btn btn-sm btn-outline-primary me-md-2">
|
||||||
|
{% trans 'View' %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'django_ledger:bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}"
|
||||||
|
class="btn btn-sm btn-outline-warning me-md-2">
|
||||||
|
{% trans 'Update' %}
|
||||||
|
</a>
|
||||||
|
{% if bill.can_pay %}
|
||||||
|
|
||||||
|
<button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')"
|
||||||
|
class="btn btn-sm btn-outline-info">
|
||||||
|
{% trans 'Mark as Paid' %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
{% if bill.can_cancel %}
|
||||||
|
<button onclick="djLedger.toggleModal('{{ bill.get_html_id }}')"
|
||||||
|
class="btn btn-sm btn-outline-danger">
|
||||||
|
{% trans 'Cancel' %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% elif style == 'bill-detail' %}
|
||||||
|
<!-- Detail Style Card -->
|
||||||
|
<div class="">
|
||||||
|
<div class="card-header p-4 bg-{{ bill.get_status_badge_color }}">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="fas fa-file-invoice me-3 text-white"></i>
|
||||||
|
<h2 class="mb-0 text-white">
|
||||||
|
{% trans 'Bill' %} {{ bill.bill_number }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-4 text-center">
|
||||||
|
{% if bill.is_draft %}
|
||||||
|
<h3 class="text-warning fw-bold mb-4">{% trans 'This bill is' %} {{ bill.get_bill_status_display }}</h3>
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<p class="mb-2">
|
||||||
|
<span class="fw-bold">{% trans 'Amount Due' %}:</span>
|
||||||
|
{% currency_symbol %}{{ bill.amount_due | currency_format }}
|
||||||
|
</p>
|
||||||
|
<p class="mb-2">
|
||||||
|
<span class="fw-bold">{% trans 'Due Date' %}:</span>
|
||||||
|
{{ bill.date_due | timeuntil }}
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
<span class="fw-bold">{% trans 'Is Accrued' %}:</span>
|
||||||
|
{% if bill.accrue %}
|
||||||
|
<i class="fas fa-check-circle text-success ms-2"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-times-circle text-danger ms-2"></i>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% elif bill.is_review %}
|
||||||
|
<h3 class="text-warning fw-bold mb-4">{% trans 'This bill is' %} {{ bill.get_bill_status_display }}</h3>
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<p class="mb-2">
|
||||||
|
<span class="fw-bold">{% trans 'Amount Due' %}:</span>
|
||||||
|
{% currency_symbol %}{{ bill.amount_due | currency_format }}
|
||||||
|
</p>
|
||||||
|
<p class="mb-2">
|
||||||
|
<span class="fw-bold">{% trans 'Due Date' %}:</span>
|
||||||
|
{{ bill.date_due | timeuntil }}
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
<span class="fw-bold">{% trans 'Is Accrued' %}:</span>
|
||||||
|
{% if bill.accrue %}
|
||||||
|
<i class="fas fa-check-circle text-success ms-2"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-times-circle text-danger ms-2"></i>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% if bill.xref %}
|
||||||
|
<p class="text-muted fst-italic">{% trans 'External Ref' %}: {{ bill.xref }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% elif bill.is_approved %}
|
||||||
|
<h3 class="text-info fw-bold mb-4">{% trans 'This bill is' %} {{ bill.get_bill_status_display }}</h3>
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<p class="mb-2">
|
||||||
|
<span class="fw-bold">{% trans 'Amount Due' %}:</span>
|
||||||
|
{% currency_symbol %}{{ bill.amount_due | currency_format }}
|
||||||
|
</p>
|
||||||
|
<p class="mb-2">
|
||||||
|
<span class="fw-bold">{% trans 'Due Date' %}:</span>
|
||||||
|
{{ bill.date_due | timeuntil }}
|
||||||
|
</p>
|
||||||
|
<p class="mb-2">
|
||||||
|
<span class="fw-bold">{% trans 'Amount Paid' %}:</span>
|
||||||
|
{% currency_symbol %}{{ bill.amount_paid | currency_format }}
|
||||||
|
</p>
|
||||||
|
<p class="mb-2">
|
||||||
|
<span class="fw-bold">{% trans 'Progress' %}:</span>
|
||||||
|
{{ bill.get_progress | percentage }}
|
||||||
|
</p>
|
||||||
|
<div class="progress mt-3">
|
||||||
|
<div class="progress-bar bg-success"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{ bill.get_progress_percent }}%"
|
||||||
|
aria-valuenow="{{ bill.get_progress_percent }}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if bill.xref %}
|
||||||
|
<p class="text-muted fst-italic">{% trans 'External Ref' %}: {{ bill.xref }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% elif bill.is_paid %}
|
||||||
|
<h3 class="text-success fw-bold mb-4">{% trans 'This bill is' %} {{ bill.get_bill_status_display }}</h3>
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<p class="mb-2">
|
||||||
|
<span class="fw-bold">{% trans 'Amount Paid' %}:</span>
|
||||||
|
{% currency_symbol %}{{ bill.amount_paid | currency_format }}
|
||||||
|
</p>
|
||||||
|
<p class="mb-0">
|
||||||
|
<span class="fw-bold">{% trans 'Paid Date' %}:</span>
|
||||||
|
{{ bill.date_paid | date }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% if bill.xref %}
|
||||||
|
<p class="text-muted fst-italic">{% trans 'External Ref' %}: {{ bill.xref }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<div class="border-bottom pb-4 mb-4">
|
||||||
|
<p class="mb-2">
|
||||||
|
<span class="fw-bold">{% trans 'Bill Amount' %}:</span>
|
||||||
|
{% currency_symbol %}{{ bill.amount_due | currency_format }}
|
||||||
|
</p>
|
||||||
|
<p class="text-danger fw-bold">
|
||||||
|
{{ bill.get_bill_status_display | upper }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="card-footer p-0">
|
||||||
|
<div class="d-flex flex-wrap">
|
||||||
|
<!-- Update Button -->
|
||||||
|
<a href="{% url 'bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}"
|
||||||
|
class="btn btn-link text-primary w-100 w-md-auto border-end">
|
||||||
|
{% trans 'Update' %}
|
||||||
|
</a>
|
||||||
|
<!-- Mark as Draft -->
|
||||||
|
{% if bill.can_draft %}
|
||||||
|
<button class="btn btn-outline-success"
|
||||||
|
onclick="showPOModal('Mark as Draft', '{% url 'bill-action-mark-as-draft' entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Draft')">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Draft' %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<!-- Mark as Review -->
|
||||||
|
{% if bill.can_review %}
|
||||||
|
<button class="btn btn-outline-success"
|
||||||
|
onclick="showPOModal('Mark as Review', '{% url 'bill-action-mark-as-review' entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Review')">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Review' %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<!-- Mark as Approved -->
|
||||||
|
{% if bill.can_approve %}
|
||||||
|
<button class="btn btn-outline-success"
|
||||||
|
onclick="showPOModal('Mark as Approved', '{% url 'bill-action-mark-as-approved' entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Approved')">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Approved' %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<!-- Mark as Paid -->
|
||||||
|
{% if bill.can_pay %}
|
||||||
|
<button class="btn btn-outline-success"
|
||||||
|
onclick="showPOModal('Mark as Paid', '{% url 'bill-action-mark-as-paid' entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Paid')">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Paid' %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<!-- Void Button -->
|
||||||
|
{% if bill.can_void %}
|
||||||
|
<button class="btn btn-outline-success"
|
||||||
|
onclick="showPOModal('Mark as Void', '{% url 'bill-action-mark-as-void' entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Void')">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Void' %}
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<!-- Cancel Button -->
|
||||||
|
{% if bill.can_cancel %}
|
||||||
|
<button class="btn btn-outline-success"
|
||||||
|
onclick="showPOModal('Mark as Canceled', '{% url 'bill-action-mark-as-canceled' entity_slug=entity_slug bill_pk=bill.pk %}', 'Mark as Canceled')">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>{% trans 'Mark as Canceled' %}
|
||||||
|
</button>
|
||||||
|
{% modal_action_v2 bill bill.get_mark_as_canceled_url bill.get_mark_as_canceled_message bill.get_mark_as_canceled_html_id %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<!-- Create Bill Card -->
|
||||||
|
<div class=" bg-light">
|
||||||
|
<div class="card-body text-center p-5">
|
||||||
|
<a href="{% url 'django_ledger:bill-create' entity_slug=entity_slug %}"
|
||||||
|
class="text-primary">
|
||||||
|
<i class="fas fa-plus-circle fa-4x mb-3"></i>
|
||||||
|
<h3 class="h4">{% trans 'New Bill' %}</h3>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.card-footer .btn-link {
|
||||||
|
padding: 1rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.card-footer .btn-link:hover {
|
||||||
|
background-color: rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
window.showPOModal = function(title, actionUrl, buttonText) {
|
||||||
|
const modalEl = document.getElementById('POModal');
|
||||||
|
if (!modalEl) {
|
||||||
|
console.error('Modal element not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
||||||
|
document.getElementById('POModalTitle').textContent = title;
|
||||||
|
|
||||||
|
document.getElementById('POModalBody').innerHTML = `
|
||||||
|
<div class="d-flex justify-content-center gap-3 py-3">
|
||||||
|
<a class="btn btn-primary px-4" href="${actionUrl}">
|
||||||
|
<i class="fas fa-check-circle me-2"></i>${buttonText}
|
||||||
|
</a>
|
||||||
|
<button class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||||
|
<i class="fas fa-times me-2"></i>Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
modal.show();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
25
templates/bill/includes/card_markdown.html
Normal file
25
templates/bill/includes/card_markdown.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{% load trans from i18n %}
|
||||||
|
{% load django_ledger %}
|
||||||
|
|
||||||
|
{% if style == 'card_1' %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<div class="card-header-title">
|
||||||
|
<h1 class="is-size-3 has-text-weight-light">{% if title %}{{ title }}{% else %}
|
||||||
|
{% trans 'Notes' %}
|
||||||
|
{% endif %}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
{% if notes_html %}
|
||||||
|
{% autoescape off %}
|
||||||
|
{{ notes_html | safe }}
|
||||||
|
{% endautoescape %}
|
||||||
|
{% else %}
|
||||||
|
<p>{% trans 'No available notes to display...' %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
30
templates/bill/includes/card_vendor.html
Normal file
30
templates/bill/includes/card_vendor.html
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load django_ledger %}
|
||||||
|
|
||||||
|
<div class="card" id="djl-vendor-card-widget">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title d-flex align-items-center text-primary">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-person-lines-fill me-2" viewBox="0 0 16 16">
|
||||||
|
<path d="M6 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6zm-5 6s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zM11 3.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zm-1 0a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1-.5-.5zm-5 0a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5zM2 3a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 0 1h-4A.5.5 0 0 1 2 3zm9.854 2.854a.5.5 0 0 1 0-.708l3-3a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0zM2.5 14.5c0-.827.673-1.5 1.5-1.5h7c.827 0 1.5.673 1.5 1.5s-.673 1.5-1.5 1.5h-7c-.827 0-1.5-.673-1.5-1.5z"/>
|
||||||
|
</svg>
|
||||||
|
{% trans 'Vendor Info' %}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<h4 class="card-title fw-bold mb-3">{{ vendor.vendor_name }}</h4>
|
||||||
|
|
||||||
|
<p class="card-text mb-0">
|
||||||
|
{% if vendor.address_1 %}<span class="d-block">{{ vendor.address_1 }}</span>{% endif %}
|
||||||
|
{% if vendor.address_2 %}<span class="d-block">{{ vendor.address_2 }}</span>{% endif %}
|
||||||
|
{% if vendor.get_cszc %}<span class="d-block">{{ vendor.get_cszc }}</span>{% endif %}
|
||||||
|
{% if vendor.phone %}<span class="d-block">{{ vendor.phone }}</span>{% endif %}
|
||||||
|
{% if vendor.email %}<span class="d-block">{{ vendor.email }}</span>{% endif %}
|
||||||
|
{% if vendor.website %}<span class="d-block">{{ vendor.website }}</span>{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-footer bg-white">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
14
templates/bill/includes/mark_as.html
Normal file
14
templates/bill/includes/mark_as.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<!-- Modal Template (keep this in your base template) -->
|
||||||
|
<div class="modal fade" id="POModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-md">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="POModalTitle"></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="POModalBody">
|
||||||
|
<!-- Content will be inserted here by JavaScript -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
159
templates/bill/tags/bill_item_formset.html
Normal file
159
templates/bill/tags/bill_item_formset.html
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% load django_ledger %}
|
||||||
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
|
<form action="{% url 'bill-update-items' entity_slug=entity_slug bill_pk=bill_pk %}" method="post">
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<h2 class="text-primary mb-0 d-flex align-items-center">
|
||||||
|
<i class="fas fa-receipt me-2"></i>
|
||||||
|
{% trans 'Bill Items' %}
|
||||||
|
</h2>
|
||||||
|
<hr class="my-3">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Form Content -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ item_formset.non_form_errors }}
|
||||||
|
{{ item_formset.management_form }}
|
||||||
|
|
||||||
|
<!-- Card Container -->
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<!-- Responsive Table -->
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th class="text-uppercase text-xxs font-weight-bolder">{% trans 'Item' %}</th>
|
||||||
|
<th class="text-uppercase text-xxs font-weight-bolder text-center">{% trans 'PO Qty' %}</th>
|
||||||
|
<th class="text-uppercase text-xxs font-weight-bolder text-center">{% trans 'PO Amount' %}</th>
|
||||||
|
<th class="text-uppercase text-xxs font-weight-bolder text-center">{% trans 'Quantity' %}</th>
|
||||||
|
<th class="text-uppercase text-xxs font-weight-bolder text-center">{% trans 'Unit Cost' %}</th>
|
||||||
|
<th class="text-uppercase text-xxs font-weight-bolder text-center">{% trans 'Unit' %}</th>
|
||||||
|
<th class="text-uppercase text-xxs font-weight-bolder text-end">{% trans 'Total' %}</th>
|
||||||
|
<th class="text-uppercase text-xxs font-weight-bolder text-center">{% trans 'Delete' %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for f in item_formset %}
|
||||||
|
<tr class="align-middle">
|
||||||
|
<!-- Item Column -->
|
||||||
|
<td>
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
{% for hidden_field in f.hidden_fields %}
|
||||||
|
{{ hidden_field }}
|
||||||
|
{% endfor %}
|
||||||
|
{{ f.item_model|add_class:"form-control" }}
|
||||||
|
{% if f.errors %}
|
||||||
|
<span class="text-danger text-xs">{{ f.errors }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- PO Quantity -->
|
||||||
|
<td class="text-center">
|
||||||
|
<span class="text-muted text-xs">
|
||||||
|
{% if f.instance.po_quantity %}{{ f.instance.po_quantity }}{% endif %}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- PO Amount -->
|
||||||
|
<td class="text-center">
|
||||||
|
{% if f.instance.po_total_amount %}
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<span class="text-xs font-weight-bold">
|
||||||
|
{% currency_symbol %}{{ f.instance.po_total_amount | currency_format }}
|
||||||
|
</span>
|
||||||
|
<a class="btn btn-sm btn-outline-info mt-1"
|
||||||
|
href="{% url 'purchase_order_detail' f.instance.po_model_id %}">
|
||||||
|
{% trans 'View PO' %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Quantity -->
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="input-group input-group-sm w-100">
|
||||||
|
{{ f.quantity|add_class:"form-control" }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Unit Cost -->
|
||||||
|
<td class="text-center">
|
||||||
|
<div class="input-group input-group-sm w-100">
|
||||||
|
{{ f.unit_cost|add_class:"form-control" }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Entity Unit -->
|
||||||
|
<td class="text-center">
|
||||||
|
{{ f.entity_unit|add_class:"form-control" }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Total Amount -->
|
||||||
|
<td class="text-end">
|
||||||
|
<span class="text-xs font-weight-bold">
|
||||||
|
{% currency_symbol %}{{ f.instance.total_amount | currency_format }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Delete Checkbox -->
|
||||||
|
<td class="text-center">
|
||||||
|
{% if item_formset.can_delete %}
|
||||||
|
<div class="form-check d-flex justify-content-center">
|
||||||
|
{{ f.DELETE }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
<!-- Footer Total -->
|
||||||
|
<tfoot class="total-row">
|
||||||
|
<tr>
|
||||||
|
<td colspan="5"></td>
|
||||||
|
<td class="text-end"><strong>{% trans 'Total' %}</strong></td>
|
||||||
|
<td class="text-end">
|
||||||
|
<strong>
|
||||||
|
{% currency_symbol %}{{ total_amount__sum | currency_format }}
|
||||||
|
</strong>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="d-flex justify-content-end gap-2">
|
||||||
|
{% if not item_formset.has_po %}
|
||||||
|
<a href="{% url 'django_ledger:product-create' entity_slug=entity_slug %}"
|
||||||
|
class="btn btn-outline-primary">
|
||||||
|
<i class="fas fa-plus me-1"></i>
|
||||||
|
{% trans 'New Item' %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fas fa-save me-1"></i>
|
||||||
|
{% trans 'Save Changes' %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
60
templates/bill/tags/bill_table.html
Normal file
60
templates/bill/tags/bill_table.html
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{% load django_ledger %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
|
||||||
|
<table class="table is-fullwidth is-narrow is-striped is-bordered django-ledger-table-bottom-margin-75">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans 'Number' %}</th>
|
||||||
|
<th>{% trans 'Status' %}</th>
|
||||||
|
<th>{% trans 'Status Date' %}</th>
|
||||||
|
<th>{% trans 'Vendor' %}</th>
|
||||||
|
<th>{% trans 'Amount Due' %}</th>
|
||||||
|
<th>{% trans 'Payments' %}</th>
|
||||||
|
<th>{% trans 'Past Due' %}</th>
|
||||||
|
<th>{% trans 'Actions' %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for bill in bills %}
|
||||||
|
<tr id="{{ bill.get_html_id }}">
|
||||||
|
<td>{{ bill.bill_number }}</td>
|
||||||
|
<td>{{ bill.get_bill_status_display }}</td>
|
||||||
|
<td>{{ bill.get_status_action_date }}</td>
|
||||||
|
<td>{{ bill.vendor.vendor_name }}</td>
|
||||||
|
<td id="{{ bill.get_html_amount_due_id }}">
|
||||||
|
{% currency_symbol %}{{ bill.amount_due | currency_format }}</td>
|
||||||
|
<td id="{{ bill.get_html_amount_paid_id }}">
|
||||||
|
{% currency_symbol %}{{ bill.amount_paid | currency_format }}</td>
|
||||||
|
<td class="has-text-centered">
|
||||||
|
{% if bill.is_past_due %}
|
||||||
|
<span class="icon is-small has-text-danger">{% icon 'bi:check-circle-fill' 24 %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="has-text-centered">
|
||||||
|
<div class="dropdown is-right is-hoverable" id="bill-action-{{ bill.uuid }}">
|
||||||
|
<div class="dropdown-trigger">
|
||||||
|
<button class="button is-small is-rounded is-outlined is-dark"
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-controls="dropdown-menu">
|
||||||
|
<span>Actions</span>
|
||||||
|
<span class="icon is-small">{% icon 'bi:arrow-down' 24 %}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-menu" id="dropdown-menu-{{ bill.uuid }}" role="menu">
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<a href="{% url 'django_ledger:bill-detail' entity_slug=entity_slug bill_pk=bill.uuid %}"
|
||||||
|
class="dropdown-item has-text-success">Details</a>
|
||||||
|
<a href="{% url 'django_ledger:bill-update' entity_slug=entity_slug bill_pk=bill.uuid %}"
|
||||||
|
class="dropdown-item has-text-warning-dark">Update</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
160
templates/ledger/bills/bill_form-copy.html
Normal file
160
templates/ledger/bills/bill_form-copy.html
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load crispy_forms_filters %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block title %}{{ _("Create Bill") }}{% endblock title %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row mt-4">
|
||||||
|
<h3 class="text-center">{% trans "Create Bill" %}</h3>
|
||||||
|
<form id="mainForm" method="post" class="needs-validation">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="row g-3">
|
||||||
|
{{ form|crispy }}
|
||||||
|
<div class="row mt-5">
|
||||||
|
<div id="formrow">
|
||||||
|
<h3 class="text-start">Unit Items</h3>
|
||||||
|
<div class="form-row row g-3 mb-3 mt-5">
|
||||||
|
<div class="mb-2 col-sm-4">
|
||||||
|
<select class="form-control item" name="item[]" required>
|
||||||
|
{% for item in items %}
|
||||||
|
<option value="{{ item.product.pk }}">{{item.car.vin}} - {{ item.car }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2 col-sm-2">
|
||||||
|
<input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2 col-sm-1">
|
||||||
|
<button class="btn btn-danger removeBtn">Remove</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<button id="addMoreBtn" class="btn btn-primary">Add More</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
|
|
||||||
|
<div class="d-flex mt-5 justify-content-center">
|
||||||
|
<button class="btn btn-sm btn-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
|
||||||
|
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
|
|
||||||
|
{% block customJS %}
|
||||||
|
<script>
|
||||||
|
const Toast = Swal.mixin({
|
||||||
|
toast: true,
|
||||||
|
position: "top-end",
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 2000,
|
||||||
|
timerProgressBar: false,
|
||||||
|
didOpen: (toast) => {
|
||||||
|
toast.onmouseenter = Swal.stopTimer;
|
||||||
|
toast.onmouseleave = Swal.resumeTimer;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Add new form fields
|
||||||
|
document.getElementById('addMoreBtn').addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const formrow = document.getElementById('formrow');
|
||||||
|
const newForm = document.createElement('div');
|
||||||
|
newForm.className = 'form-row row g-3 mb-3 mt-5';
|
||||||
|
newForm.innerHTML = `
|
||||||
|
<div class="mb-2 col-sm-2">
|
||||||
|
<select class="form-control item" name="item[]" required>
|
||||||
|
{% for item in items %}
|
||||||
|
<option value="{{ item.product.pk }}">{{ item.car.id_car_model }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2 col-sm-2">
|
||||||
|
<input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2 col-sm-1">
|
||||||
|
<button class="btn btn-danger removeBtn">Remove</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
formrow.appendChild(newForm);
|
||||||
|
|
||||||
|
// Add remove button functionality
|
||||||
|
newForm.querySelector('.removeBtn').addEventListener('click', function() {
|
||||||
|
newForm.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add remove button functionality to the initial form
|
||||||
|
document.querySelectorAll('.form-row').forEach(row => {
|
||||||
|
row.querySelector('.removeBtn').addEventListener('click', function() {
|
||||||
|
row.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
|
document.getElementById('mainForm').addEventListener('submit', async function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
/*const titleInput = document.querySelector('[name="title"]');
|
||||||
|
if (titleInput.value.length < 5) {
|
||||||
|
notify("error", "Customer billte Title must be at least 5 characters long.");
|
||||||
|
return; // Stop form submission
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Collect all form data
|
||||||
|
const formData = {
|
||||||
|
csrfmiddlewaretoken: document.querySelector('[name=csrfmiddlewaretoken]').value,
|
||||||
|
vendor: document.querySelector('[name=vendor]').value,
|
||||||
|
xref: document.querySelector('[name=xref]').value,
|
||||||
|
date_draft: document.querySelector('[name=date_draft]').value,
|
||||||
|
terms: document.querySelector('[name=terms]').value,
|
||||||
|
item: [],
|
||||||
|
quantity: []
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Collect multi-value fields (e.g., item[], quantity[])
|
||||||
|
document.querySelectorAll('[name="item[]"]').forEach(input => {
|
||||||
|
formData.item.push(input.value);
|
||||||
|
});
|
||||||
|
document.querySelectorAll('[name="quantity[]"]').forEach(input => {
|
||||||
|
formData.quantity.push(input.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Send data to the server using fetch
|
||||||
|
const response = await fetch("{% url 'bill_create' %}", {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': formData.csrfmiddlewaretoken,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(formData)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Parse the JSON response
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Handle the response
|
||||||
|
if (data.status === "error") {
|
||||||
|
notify("error", data.message); // Display an error message
|
||||||
|
} else if (data.status === "success") {
|
||||||
|
notify("success","Bill created successfully");
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.assign(data.url); // Redirect to the provided URL
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
notify("error","Unexpected response from the server");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
notify("error", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock customJS %}
|
||||||
@ -11,33 +11,8 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<div class="row mt-5">
|
|
||||||
<div id="formrow">
|
|
||||||
<h3 class="text-start">Unit Items</h3>
|
|
||||||
<div class="form-row row g-3 mb-3 mt-5">
|
|
||||||
<div class="mb-2 col-sm-4">
|
|
||||||
<select class="form-control item" name="item[]" required>
|
|
||||||
{% for item in items %}
|
|
||||||
<option value="{{ item.product.pk }}">{{item.car.vin}} - {{ item.car }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2 col-sm-2">
|
|
||||||
<input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2 col-sm-1">
|
|
||||||
<button class="btn btn-danger removeBtn">Remove</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<button id="addMoreBtn" class="btn btn-primary">Add More</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
|
|
||||||
<div class="d-flex mt-5 justify-content-center">
|
<div class="d-flex mt-5 justify-content-center">
|
||||||
<button class="btn btn-sm btn-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
|
<button class="btn btn-sm btn-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>{{ _("Save") }}</button>
|
||||||
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
||||||
@ -45,116 +20,4 @@
|
|||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
{% block customJS %}
|
|
||||||
<script>
|
|
||||||
const Toast = Swal.mixin({
|
|
||||||
toast: true,
|
|
||||||
position: "top-end",
|
|
||||||
showConfirmButton: false,
|
|
||||||
timer: 2000,
|
|
||||||
timerProgressBar: false,
|
|
||||||
didOpen: (toast) => {
|
|
||||||
toast.onmouseenter = Swal.stopTimer;
|
|
||||||
toast.onmouseleave = Swal.resumeTimer;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Add new form fields
|
|
||||||
document.getElementById('addMoreBtn').addEventListener('click', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const formrow = document.getElementById('formrow');
|
|
||||||
const newForm = document.createElement('div');
|
|
||||||
newForm.className = 'form-row row g-3 mb-3 mt-5';
|
|
||||||
newForm.innerHTML = `
|
|
||||||
<div class="mb-2 col-sm-2">
|
|
||||||
<select class="form-control item" name="item[]" required>
|
|
||||||
{% for item in items %}
|
|
||||||
<option value="{{ item.product.pk }}">{{ item.car.id_car_model }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2 col-sm-2">
|
|
||||||
<input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-2 col-sm-1">
|
|
||||||
<button class="btn btn-danger removeBtn">Remove</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
formrow.appendChild(newForm);
|
|
||||||
|
|
||||||
// Add remove button functionality
|
|
||||||
newForm.querySelector('.removeBtn').addEventListener('click', function() {
|
|
||||||
newForm.remove();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add remove button functionality to the initial form
|
|
||||||
document.querySelectorAll('.form-row').forEach(row => {
|
|
||||||
row.querySelector('.removeBtn').addEventListener('click', function() {
|
|
||||||
row.remove();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle form submission
|
|
||||||
document.getElementById('mainForm').addEventListener('submit', async function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
/*const titleInput = document.querySelector('[name="title"]');
|
|
||||||
if (titleInput.value.length < 5) {
|
|
||||||
notify("error", "Customer billte Title must be at least 5 characters long.");
|
|
||||||
return; // Stop form submission
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Collect all form data
|
|
||||||
const formData = {
|
|
||||||
csrfmiddlewaretoken: document.querySelector('[name=csrfmiddlewaretoken]').value,
|
|
||||||
vendor: document.querySelector('[name=vendor]').value,
|
|
||||||
xref: document.querySelector('[name=xref]').value,
|
|
||||||
date_draft: document.querySelector('[name=date_draft]').value,
|
|
||||||
terms: document.querySelector('[name=terms]').value,
|
|
||||||
item: [],
|
|
||||||
quantity: []
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Collect multi-value fields (e.g., item[], quantity[])
|
|
||||||
document.querySelectorAll('[name="item[]"]').forEach(input => {
|
|
||||||
formData.item.push(input.value);
|
|
||||||
});
|
|
||||||
document.querySelectorAll('[name="quantity[]"]').forEach(input => {
|
|
||||||
formData.quantity.push(input.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Send data to the server using fetch
|
|
||||||
const response = await fetch("{% url 'bill_create' %}", {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'X-CSRFToken': formData.csrfmiddlewaretoken,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(formData)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Parse the JSON response
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Handle the response
|
|
||||||
if (data.status === "error") {
|
|
||||||
notify("error", data.message); // Display an error message
|
|
||||||
} else if (data.status === "success") {
|
|
||||||
notify("success","Bill created successfully");
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.assign(data.url); // Redirect to the provided URL
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
|
||||||
notify("error","Unexpected response from the server");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
|
|
||||||
notify("error", error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock customJS %}
|
|
||||||
@ -14,11 +14,9 @@
|
|||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="d-flex justify-content-between mb-2">
|
<div class="d-flex justify-content-between mb-2">
|
||||||
<h3 class="">{% trans "Bills" %}</h3>
|
<h3 class="">{% trans "Bills" %}</h3>
|
||||||
<a href="{% url 'bill_create' %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Bill' %}</a>
|
<a href="{% url 'bill-create' entity.slug %}" class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{% trans 'New Bill' %}</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<form method="get" class=" mb-4">
|
<form method="get" class=" mb-4">
|
||||||
<div class="input-group input-group-sm">
|
<div class="input-group input-group-sm">
|
||||||
@ -40,9 +38,9 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="table-responsive px-1 scrollbar mt-3">
|
<div class="table-responsive px-1 scrollbar mt-3">
|
||||||
<table class="table align-items-center table-flush">
|
<table class="table align-items-center table-flush">
|
||||||
<thead>
|
<thead>
|
||||||
@ -53,9 +51,9 @@
|
|||||||
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Action'%}</th>
|
<th class="sort white-space-nowrap align-middle" scope="col">{% trans 'Action'%}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody class="list">
|
<tbody class="list">
|
||||||
{% for bill in bills %}
|
{% for bill in bills %}
|
||||||
|
|
||||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||||
<td class="align-middle product white-space-nowrap">
|
<td class="align-middle product white-space-nowrap">
|
||||||
@ -84,7 +82,7 @@
|
|||||||
<a href="{% url 'bill_detail' bill.pk %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a>
|
<a href="{% url 'bill_detail' bill.pk %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -107,5 +105,5 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
21
templates/purchase_orders/car_inventory_item_form.html
Normal file
21
templates/purchase_orders/car_inventory_item_form.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static i18n crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||||
|
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||||
|
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||||
|
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="account">Account</label>
|
||||||
|
<select class="form-control" name="account" id="account">
|
||||||
|
{% for account in inventory_accounts %}
|
||||||
|
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Add New Item To Inventory</button>
|
||||||
|
</form>
|
||||||
|
{% endblock content %}
|
||||||
@ -1,21 +1,27 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static i18n crispy_forms_tags %}
|
||||||
|
|
||||||
<form action="{% url 'inventory_item_create' po_model.pk %}" method="post">
|
{% block content %}
|
||||||
{% csrf_token %}
|
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
<form action="{% url 'inventory_item_create' po_model.pk %}" method="post">
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
{% csrf_token %}
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||||
<div class="form-group">
|
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||||
<label for="account">Account</label>
|
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||||
<select class="form-control" name="account" id="account">
|
<div class="form-group">
|
||||||
{% for account in inventory_accounts %}
|
<label for="account">Account</label>
|
||||||
<option value="{{ account.pk }}">{{ account }}"></option>
|
<select class="form-control" name="account" id="account">
|
||||||
{% endfor %}
|
{% for account in inventory_accounts %}
|
||||||
</select>
|
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||||
</div>
|
{% endfor %}
|
||||||
<div class="form-group">
|
</select>
|
||||||
<label for="quantity">Quantity</label>
|
</div>
|
||||||
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
<div class="form-group">
|
||||||
</div>
|
<label for="quantity">Quantity</label>
|
||||||
<button type="submit" class="btn btn-primary">Add New Item To Inventory</button>
|
<input type="number" class="form-control" id="quantity" name="quantity" required>
|
||||||
</form>
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Add New Item To Inventory</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock content %}
|
||||||
@ -58,7 +58,7 @@
|
|||||||
{{ f.create_bill|add_class:"form-check-input" }}
|
{{ f.create_bill|add_class:"form-check-input" }}
|
||||||
{% elif f.instance.bill_model %}
|
{% elif f.instance.bill_model %}
|
||||||
<a class="btn btn-sm btn-outline-secondary"
|
<a class="btn btn-sm btn-outline-secondary"
|
||||||
href="{% url 'django_ledger:bill-detail' entity_slug=entity_slug bill_pk=f.instance.bill_model_id %}">
|
href="{% url 'bill-detail' entity_slug=entity_slug bill_pk=f.instance.bill_model_id %}">
|
||||||
{% trans 'View Bill' %}
|
{% trans 'View Bill' %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,53 +1,23 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n %}
|
{% load static i18n crispy_forms_tags %}
|
||||||
{% load crispy_forms_filters %}
|
|
||||||
{% block title %}
|
|
||||||
{# Check if an 'object' exists in the context #}
|
|
||||||
{% if object %}
|
|
||||||
{% trans 'Update Inventory Item'%}
|
|
||||||
{% else %}
|
|
||||||
{% trans 'Add New Inventory Item'%}
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="container-fluid m-0">
|
||||||
<div class="row">
|
<form action="" method="post">
|
||||||
<div class="col-xl-9">
|
{% csrf_token %}
|
||||||
<div class="d-sm-flex justify-content-between">
|
<div class="form-group">
|
||||||
|
<label for="account">Name</label>
|
||||||
<h3 class="mb-3">
|
<input type="text" class="form-control" id="name" name="name" required>
|
||||||
{% if vendor.created %}
|
|
||||||
<!--<i class="bi bi-pencil-square"></i>-->
|
|
||||||
{{ _("Edit Inventory Item") }}
|
|
||||||
{% else %}
|
|
||||||
<!--<i class="bi bi-person-plus"></i> -->
|
|
||||||
{{ _("Add New Inventory Item") }}
|
|
||||||
{% endif %}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-xl-9">
|
|
||||||
|
|
||||||
<form class="row g-3 mb-9" method="post" class="form" enctype="multipart/form-data" novalidate >
|
|
||||||
{% csrf_token %}
|
|
||||||
{{ redirect_field }}
|
|
||||||
{{ form|crispy }}
|
|
||||||
{% for error in form.errors %}
|
|
||||||
<div class="text-danger">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
<div class="d-flex justify-content-start">
|
|
||||||
<button class="btn btn-sm btn-success me-2" type="submit"><i class="fa-solid fa-floppy-disk me-1"></i>
|
|
||||||
<!--<i class="bi bi-save"></i> -->
|
|
||||||
{{ _("Save") }}
|
|
||||||
</button>
|
|
||||||
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger"><i class="fa-solid fa-ban me-1"></i>{% trans "Cancel" %}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
<div class="form-group">
|
||||||
|
<label for="account">Account</label>
|
||||||
|
<select class="form-control" name="account" id="account">
|
||||||
|
{% for account in inventory_accounts %}
|
||||||
|
<option value="{{ account.pk }}">{{ account }}"></option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Add New Item To Inventory</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock content %}
|
||||||
@ -2,10 +2,13 @@
|
|||||||
<label for="{{name}}">{{name|capfirst}}</label>
|
<label for="{{name}}">{{name|capfirst}}</label>
|
||||||
<select class="form-control"
|
<select class="form-control"
|
||||||
name="{{name}}" id="{{name}}"
|
name="{{name}}" id="{{name}}"
|
||||||
hx-get="{% url 'inventory_items_filter' po_pk=po_model.pk %}"
|
{% if name != "trim" %}
|
||||||
hx-target="#form-{{target}}"
|
hx-get="{% url 'inventory_items_filter' %}"
|
||||||
hx-select="#form-{{target}}"
|
hx-target="#form-{{target}}"
|
||||||
hx-swap="outerHTML">
|
hx-select="#form-{{target}}"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
{% endif %}
|
||||||
|
>
|
||||||
{% for item in data %}
|
{% for item in data %}
|
||||||
<option value="{{ item.pk }}">{{ item.name }}</option>
|
<option value="{{ item.pk }}">{{ item.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user