This commit is contained in:
gitea 2025-01-16 17:31:44 +00:00
parent f84c07b8e3
commit c277e73dec
8 changed files with 1264 additions and 478 deletions

View File

@ -9,6 +9,7 @@ from phonenumber_field.phonenumber import PhoneNumber
from .mixins import AddClassMixin from .mixins import AddClassMixin
from django.forms.models import inlineformset_factory from django.forms.models import inlineformset_factory
from django_ledger.forms.invoice import InvoiceModelCreateForm as InvoiceModelCreateFormBase from django_ledger.forms.invoice import InvoiceModelCreateForm as InvoiceModelCreateFormBase
from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase
from .models import ( from .models import (
Dealer, Dealer,
# Branch, # Branch,
@ -634,4 +635,14 @@ class InvoiceModelCreateForm(InvoiceModelCreateFormBase):
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()
self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'})) self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'}))
class BillModelCreateForm(BillModelCreateFormBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['cash_account'].widget = forms.HiddenInput()
self.fields['prepaid_account'].widget = forms.HiddenInput()
self.fields['unearned_account'].widget = forms.HiddenInput()
self.fields['date_draft'] = forms.DateField(widget=DateInput(attrs={'type': 'date'}))

View File

@ -0,0 +1,25 @@
# Generated by Django 4.2.17 on 2025-01-16 09:46
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('django_ledger', '0017_alter_accountmodel_unique_together_and_more'),
('inventory', '0003_alter_carmake_car_type'),
]
operations = [
migrations.CreateModel(
name='InvoiceModelBase',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('django_ledger.invoicemodel',),
),
]

View File

@ -1,196 +1,491 @@
from django.urls import path from django.urls import path
from . import views from . import views
from allauth.account import views as allauth_views from allauth.account import views as allauth_views
from django.conf.urls import ( from django.conf.urls import handler400, handler403, handler404, handler500
handler400, handler403, handler404, handler500
)
urlpatterns = [ urlpatterns = [
# main URLs # main URLs
path('', views.HomeView.as_view(), name='landing_page'), path("", views.HomeView.as_view(), name="landing_page"),
path('welcome/', views.WelcomeView.as_view(), name='welcome'), path("welcome/", views.WelcomeView.as_view(), name="welcome"),
# Accounts URLs # Accounts URLs
path('login/', views.Login.as_view(), name='account_login'), path("login/", views.Login.as_view(), name="account_login"),
path('logout/', allauth_views.LogoutView.as_view(template_name='account/logout.html'), name='account_logout'), path(
# path('signup/', allauth_views.SignupView.as_view(template_name='account/signup.html'), name='account_signup'), "logout/",
path('signup/', views.dealer_signup, name='account_signup'), allauth_views.LogoutView.as_view(template_name="account/logout.html"),
path('password/change/', name="account_logout",
allauth_views.PasswordChangeView.as_view(template_name='account/password_change.html'), ),
name='account_change_password'), # path('signup/', allauth_views.SignupView.as_view(template_name='account/signup.html'), name='account_signup'),
path('password/reset/', path("signup/", views.dealer_signup, name="account_signup"),
allauth_views.PasswordResetView.as_view(template_name='account/password_reset.html'), path(
name='account_reset_password'), "password/change/",
path('password/reset/done/', allauth_views.PasswordChangeView.as_view(
allauth_views.PasswordResetDoneView.as_view(template_name='account/password_reset_done.html'), template_name="account/password_change.html"
name='account_password_reset_done'), ),
path('login/code/', allauth_views.RequestLoginCodeView.as_view(template_name='account/request_login_code.html')), name="account_change_password",
#Dashboards ),
path('dashboards/accounting/', views.AccountingDashboard.as_view(), name='accounting'), path(
path('test/', views.TestView.as_view(), name='test'), "password/reset/",
allauth_views.PasswordResetView.as_view(
template_name="account/password_reset.html"
),
name="account_reset_password",
),
path(
"password/reset/done/",
allauth_views.PasswordResetDoneView.as_view(
template_name="account/password_reset_done.html"
),
name="account_password_reset_done",
),
path(
"login/code/",
allauth_views.RequestLoginCodeView.as_view(
template_name="account/request_login_code.html"
),
),
# Dashboards
path(
"dashboards/accounting/", views.AccountingDashboard.as_view(), name="accounting"
),
path("test/", views.TestView.as_view(), name="test"),
# Dealer URLs # Dealer URLs
path('dealers/<int:pk>/', views.DealerDetailView.as_view(), name='dealer_detail'), path("dealers/<int:pk>/", views.DealerDetailView.as_view(), name="dealer_detail"),
path('dealers/<int:pk>/update/', views.DealerUpdateView.as_view(), name='dealer_update'), path(
path('dealers/activity/', views.UserActivityLogListView.as_view(), name='dealer_activity'), "dealers/<int:pk>/update/",
views.DealerUpdateView.as_view(),
name="dealer_update",
),
path(
"dealers/activity/",
views.UserActivityLogListView.as_view(),
name="dealer_activity",
),
# path('dealers/<int:pk>/delete/', views.DealerDeleteView.as_view(), name='dealer_delete'), # path('dealers/<int:pk>/delete/', views.DealerDeleteView.as_view(), name='dealer_delete'),
# CRM URLs # CRM URLs
path('customers/', views.CustomerListView.as_view(), name='customer_list'), path("customers/", views.CustomerListView.as_view(), name="customer_list"),
path('customers/<int:pk>/', views.CustomerDetailView.as_view(), name='customer_detail'), path(
path('customers/create/', views.CustomerCreateView.as_view(), name='customer_create'), "customers/<int:pk>/",
path('customers/<int:pk>/update/', views.CustomerUpdateView.as_view(), name='customer_update'), views.CustomerDetailView.as_view(),
path('customers/<int:pk>/delete/', views.delete_customer, name='customer_delete'), name="customer_detail",
path('customers/<int:customer_id>/opportunities/create/', views.OpportunityCreateView.as_view(), name='create_opportunity'), ),
path('customers/<int:pk>/add-note/', views.add_note_to_customer, name='add_note_to_customer'), path(
"customers/create/", views.CustomerCreateView.as_view(), name="customer_create"
path('crm/leads/', views.LeadListView.as_view(), name='lead_list'), ),
path('crm/leads/<int:pk>/view/', views.LeadDetailView.as_view(), name='lead_detail'), path(
path('crm/leads/create/', views.LeadCreateView.as_view(), name='lead_create'), "customers/<int:pk>/update/",
path('crm/leads/<int:pk>/update/', views.LeadUpdateView.as_view(), name='lead_update'), views.CustomerUpdateView.as_view(),
path('crm/leads/<int:pk>/delete/', views.LeadDeleteView.as_view(), name='lead_delete'), name="customer_update",
path('crm/leads/<int:pk>/add-note/', views.add_note_to_lead, name='add_note'), ),
path('crm/leads/<int:pk>/add-activity/', views.add_activity_to_lead, name='add_activity'), path("customers/<int:pk>/delete/", views.delete_customer, name="customer_delete"),
path('crm/opportunities/create/', views.OpportunityCreateView.as_view(), name='opportunity_create'), path(
path('crm/opportunities/<int:pk>/', views.OpportunityDetailView.as_view(), name='opportunity_detail'), "customers/<int:customer_id>/opportunities/create/",
path('crm/opportunities/<int:pk>/edit/', views.OpportunityUpdateView.as_view(), name='update_opportunity'), views.OpportunityCreateView.as_view(),
path('crm/opportunities/', views.OpportunityListView.as_view(), name='opportunity_list'), name="create_opportunity",
path('crm/opportunities/<int:pk>/delete/', views.delete_opportunity, name='delete_opportunity'), ),
path(
"customers/<int:pk>/add-note/",
views.add_note_to_customer,
name="add_note_to_customer",
),
path("crm/leads/", views.LeadListView.as_view(), name="lead_list"),
path(
"crm/leads/<int:pk>/view/", views.LeadDetailView.as_view(), name="lead_detail"
),
path("crm/leads/create/", views.LeadCreateView.as_view(), name="lead_create"),
path(
"crm/leads/<int:pk>/update/", views.LeadUpdateView.as_view(), name="lead_update"
),
path(
"crm/leads/<int:pk>/delete/", views.LeadDeleteView.as_view(), name="lead_delete"
),
path("crm/leads/<int:pk>/add-note/", views.add_note_to_lead, name="add_note"),
path(
"crm/leads/<int:pk>/add-activity/",
views.add_activity_to_lead,
name="add_activity",
),
path(
"crm/opportunities/create/",
views.OpportunityCreateView.as_view(),
name="opportunity_create",
),
path(
"crm/opportunities/<int:pk>/",
views.OpportunityDetailView.as_view(),
name="opportunity_detail",
),
path(
"crm/opportunities/<int:pk>/edit/",
views.OpportunityUpdateView.as_view(),
name="update_opportunity",
),
path(
"crm/opportunities/",
views.OpportunityListView.as_view(),
name="opportunity_list",
),
path(
"crm/opportunities/<int:pk>/delete/",
views.delete_opportunity,
name="delete_opportunity",
),
# path('crm/opportunities/<int:pk>/logs/', views.OpportunityLogsView.as_view(), name='opportunity_logs'), # path('crm/opportunities/<int:pk>/logs/', views.OpportunityLogsView.as_view(), name='opportunity_logs'),
path('crm/notifications/', views.NotificationListView.as_view(), name='notifications_history'), path(
path('crm/fetch_notifications/', views.fetch_notifications, name='fetch_notifications'), "crm/notifications/",
path('crm/notifications/<int:pk>/mark_as_read/', views.mark_notification_as_read, name='mark_notification_as_read'), views.NotificationListView.as_view(),
name="notifications_history",
#Vendor URLs ),
path('vendors', views.VendorListView.as_view(), name='vendor_list'), path(
path('vendors/<int:pk>/', views.VendorDetailView.as_view(), name='vendor_detail'), "crm/fetch_notifications/",
path('vendors/create/', views.VendorCreateView.as_view(), name='vendor_create'), views.fetch_notifications,
path('vendors/<int:pk>/update/', views.VendorUpdateView.as_view(), name='vendor_update'), name="fetch_notifications",
path('vendors/<int:pk>/delete/', views.VendorDetailView.as_view(), name='vendor_delete'), ),
path(
"crm/notifications/<int:pk>/mark_as_read/",
views.mark_notification_as_read,
name="mark_notification_as_read",
),
# Vendor URLs
path("vendors", views.VendorListView.as_view(), name="vendor_list"),
path("vendors/<int:pk>/", views.VendorDetailView.as_view(), name="vendor_detail"),
path("vendors/create/", views.VendorCreateView.as_view(), name="vendor_create"),
path(
"vendors/<int:pk>/update/",
views.VendorUpdateView.as_view(),
name="vendor_update",
),
path(
"vendors/<int:pk>/delete/",
views.VendorDetailView.as_view(),
name="vendor_delete",
),
# Car URLs # Car URLs
path('cars/inventory/', views.CarInventory.as_view(), name='car_inventory_all'), path("cars/inventory/", views.CarInventory.as_view(), name="car_inventory_all"),
path('cars/inventory/<int:make_id>/<int:model_id>/<int:trim_id>/', views.CarInventory.as_view(), name='car_inventory'), path(
path('cars/inventory/stats', views.inventory_stats_view, name='inventory_stats'), "cars/inventory/<int:make_id>/<int:model_id>/<int:trim_id>/",
path('cars/<int:pk>/', views.CarDetailView.as_view(), name='car_detail'), views.CarInventory.as_view(),
path('cars/<int:pk>/update/', views.CarUpdateView.as_view(), name='car_update'), name="car_inventory",
path('cars/<int:pk>/delete/', views.CarDeleteView.as_view(), name='car_delete'), ),
path('cars/<int:car_pk>/finance/create/', views.CarFinanceCreateView.as_view(), name='car_finance_create'), path("cars/inventory/stats", views.inventory_stats_view, name="inventory_stats"),
path('cars/finance/<int:pk>/update/', views.CarFinanceUpdateView.as_view(), name='car_finance_update'), path("cars/<int:pk>/", views.CarDetailView.as_view(), name="car_detail"),
path('cars/add/', views.CarCreateView.as_view(), name='car_add'), path("cars/<int:pk>/update/", views.CarUpdateView.as_view(), name="car_update"),
path('ajax/', views.AjaxHandlerView.as_view(), name='ajax_handler'), path("cars/<int:pk>/delete/", views.CarDeleteView.as_view(), name="car_delete"),
path('cars/get-car-models/', views.get_car_models, name='get_car_models'), path(
path('cars/<int:car_pk>/add-color/', views.CarColorCreate.as_view(), name='add_color'), "cars/<int:car_pk>/finance/create/",
path('cars/<int:car_pk>/location/add/', views.CarLocationCreateView.as_view(), name='add_car_location'), views.CarFinanceCreateView.as_view(),
path('cars/<int:pk>/location/update/', views.CarLocationUpdateView.as_view(), name='transfer'), name="car_finance_create",
path('cars/inventory/search/', views.SearchCodeView.as_view(), name='car_search'), ),
path(
"cars/finance/<int:pk>/update/",
views.CarFinanceUpdateView.as_view(),
name="car_finance_update",
),
path("cars/add/", views.CarCreateView.as_view(), name="car_add"),
path("ajax/", views.AjaxHandlerView.as_view(), name="ajax_handler"),
path("cars/get-car-models/", views.get_car_models, name="get_car_models"),
path(
"cars/<int:car_pk>/add-color/", views.CarColorCreate.as_view(), name="add_color"
),
path(
"cars/<int:car_pk>/location/add/",
views.CarLocationCreateView.as_view(),
name="add_car_location",
),
path(
"cars/<int:pk>/location/update/",
views.CarLocationUpdateView.as_view(),
name="transfer",
),
path("cars/inventory/search/", views.SearchCodeView.as_view(), name="car_search"),
# path('cars/<int:car_pk>/colors/<int:pk>/update/',views.CarColorUpdateView.as_view(),name='color_update'), # path('cars/<int:car_pk>/colors/<int:pk>/update/',views.CarColorUpdateView.as_view(),name='color_update'),
path("cars/reserve/<int:car_id>/", views.reserve_car_view, name="reserve_car"),
path('cars/reserve/<int:car_id>/', views.reserve_car_view, name='reserve_car'), path(
path('reservations/<int:reservation_id>/', views.manage_reservation, name='reservations'), "reservations/<int:reservation_id>/",
path('cars/<int:car_pk>/add-custom-card/', views.CustomCardCreateView.as_view(), name='add_custom_card'), views.manage_reservation,
name="reservations",
),
path(
"cars/<int:car_pk>/add-custom-card/",
views.CustomCardCreateView.as_view(),
name="add_custom_card",
),
# Sales URLs quotation_create # Sales URLs quotation_create
path('sales/quotations/create/', views.QuotationCreateView.as_view(), name='quotation_create'), path(
path('sales/quotations/<int:pk>/', views.QuotationDetailView.as_view(), name='quotation_detail'), "sales/quotations/create/",
path('sales/quotations/', views.QuotationListView.as_view(), name='quotation_list'), views.QuotationCreateView.as_view(),
path('sales/quotations/<int:pk>/confirm/', views.confirm_quotation, name='confirm_quotation'), name="quotation_create",
path('sales/orders/detail/<int:order_id>/', views.SalesOrderDetailView.as_view(), name='order_detail'), ),
path('quotation/<int:quotation_id>/pdf/', views.download_quotation_pdf, name='quotation_pdf'), path(
path('generate_invoice/<int:pk>/', views.generate_invoice, name='generate_invoice'), "sales/quotations/<int:pk>/",
path('sales/quotations/<int:pk>/mark_quotation/', views.mark_quotation, name='mark_quotation'), views.QuotationDetailView.as_view(),
path('sales/quotations/<int:pk>/post_quotation/', views.post_quotation, name='post_quotation'), name="quotation_detail",
path('sales/quotations/<int:pk>/invoice_detail/', views.invoice_detail, name='invoice_detail'), ),
path('subscriptions', views.SubscriptionPlans.as_view(), name='subscriptions'), path("sales/quotations/", views.QuotationListView.as_view(), name="quotation_list"),
#Payment URLs path(
# path('sales/quotations/<int:pk>/payment/', views.PaymentCreateView.as_view(), name='payment_create'), "sales/quotations/<int:pk>/confirm/",
path('sales/quotations/<int:pk>/payment/', views.payment_create, name='payment_create'), views.confirm_quotation,
name="confirm_quotation",
),
path(
"sales/orders/detail/<int:order_id>/",
views.SalesOrderDetailView.as_view(),
name="order_detail",
),
path(
"quotation/<int:quotation_id>/pdf/",
views.download_quotation_pdf,
name="quotation_pdf",
),
path("generate_invoice/<int:pk>/", views.generate_invoice, name="generate_invoice"),
path(
"sales/quotations/<int:pk>/mark_quotation/",
views.mark_quotation,
name="mark_quotation",
),
path(
"sales/quotations/<int:pk>/post_quotation/",
views.post_quotation,
name="post_quotation",
),
path(
"sales/quotations/<int:pk>/invoice_detail/",
views.InvoiceDetailView.as_view(),
name="invoice_detail",
),
path("subscriptions", views.SubscriptionPlans.as_view(), name="subscriptions"),
# Payment URLs
# path('sales/quotations/<int:pk>/payment/', views.PaymentCreateView.as_view(), name='payment_create'),
path(
"sales/quotations/<int:pk>/payment/",
views.PaymentCreateView,
name="payment_create",
),
# Users URLs # Users URLs
path('user/create/', views.UserCreateView.as_view(), name='user_create'), path("user/create/", views.UserCreateView.as_view(), name="user_create"),
path('user/<int:pk>/update/', views.UserUpdateView.as_view(), name='user_update'), path("user/<int:pk>/update/", views.UserUpdateView.as_view(), name="user_update"),
path('user/<int:pk>/', views.UserDetailView.as_view(), name='user_detail'), path("user/<int:pk>/", views.UserDetailView.as_view(), name="user_detail"),
path('user/', views.UserListView.as_view(), name='user_list'), path("user/", views.UserListView.as_view(), name="user_list"),
path('user/<int:pk>/confirm/', views.UserDeleteview, name='user_delete'), path("user/<int:pk>/confirm/", views.UserDeleteview, name="user_delete"),
# Organization URLs # Organization URLs
path('organizations/', views.OrganizationListView.as_view(), name='organization_list'), path(
path('organizations/<int:pk>/', views.OrganizationDetailView.as_view(), name='organization_detail'), "organizations/", views.OrganizationListView.as_view(), name="organization_list"
path('organizations/create/', views.OrganizationCreateView.as_view(), name='organization_create'), ),
path('organizations/<int:pk>/update/', views.OrganizationUpdateView.as_view(), name='organization_update'), path(
path('organizations/<int:pk>/delete/', views.OrganizationDeleteView.as_view(), name='organization_delete'), "organizations/<int:pk>/",
views.OrganizationDetailView.as_view(),
name="organization_detail",
),
path(
"organizations/create/",
views.OrganizationCreateView.as_view(),
name="organization_create",
),
path(
"organizations/<int:pk>/update/",
views.OrganizationUpdateView.as_view(),
name="organization_update",
),
path(
"organizations/<int:pk>/delete/",
views.OrganizationDeleteView.as_view(),
name="organization_delete",
),
# Representative URLs # Representative URLs
path('representatives/', views.RepresentativeListView.as_view(), name='representative_list'), path(
path('representatives/<int:pk>/', views.RepresentativeDetailView.as_view(), name='representative_detail'), "representatives/",
path('representatives/create/', views.RepresentativeCreateView.as_view(), name='representative_create'), views.RepresentativeListView.as_view(),
path('representatives/<int:pk>/update/', views.RepresentativeUpdateView.as_view(), name='representative_update'), name="representative_list",
path('representatives/<int:pk>/delete/', views.RepresentativeDeleteView.as_view(), name='representative_delete'), ),
path(
#Ledger URLS "representatives/<int:pk>/",
#Bank Account views.RepresentativeDetailView.as_view(),
path('bank_accounts/', views.BankAccountListView.as_view(), name='bank_account_list'), name="representative_detail",
path('bank_accounts/<uuid:pk>/', views.BankAccountDetailView.as_view(), name='bank_account_detail'), ),
path('bank_accounts/create/', views.BankAccountCreateView.as_view(), name='bank_account_create'), path(
path('bank_accounts/<uuid:pk>/update/', views.BankAccountUpdateView.as_view(), name='bank_account_update'), "representatives/create/",
path('bank_accounts/<uuid:pk>/delete/', views.bank_account_delete, name='bank_account_delete'), views.RepresentativeCreateView.as_view(),
name="representative_create",
),
path(
"representatives/<int:pk>/update/",
views.RepresentativeUpdateView.as_view(),
name="representative_update",
),
path(
"representatives/<int:pk>/delete/",
views.RepresentativeDeleteView.as_view(),
name="representative_delete",
),
# Ledger URLS
# Bank Account
path(
"bank_accounts/", views.BankAccountListView.as_view(), name="bank_account_list"
),
path(
"bank_accounts/<uuid:pk>/",
views.BankAccountDetailView.as_view(),
name="bank_account_detail",
),
path(
"bank_accounts/create/",
views.BankAccountCreateView.as_view(),
name="bank_account_create",
),
path(
"bank_accounts/<uuid:pk>/update/",
views.BankAccountUpdateView.as_view(),
name="bank_account_update",
),
path(
"bank_accounts/<uuid:pk>/delete/",
views.bank_account_delete,
name="bank_account_delete",
),
# Account # Account
path('coa_accounts/', views.AccountListView.as_view(), name='account_list'), path("coa_accounts/", views.AccountListView.as_view(), name="account_list"),
path('coa_accounts/<uuid:pk>/', views.AccountDetailView.as_view(), name='account_detail'), path(
path('coa_accounts/create/', views.AccountCreateView.as_view(), name='account_create'), "coa_accounts/<uuid:pk>/",
path('coa_accounts/<uuid:pk>/update/', views.AccountUpdateView.as_view(), name='account_update'), views.AccountDetailView.as_view(),
path('coa_accounts/<uuid:pk>/delete/', views.account_delete, name='account_delete'), name="account_detail",
),
path(
"coa_accounts/create/", views.AccountCreateView.as_view(), name="account_create"
),
path(
"coa_accounts/<uuid:pk>/update/",
views.AccountUpdateView.as_view(),
name="account_update",
),
path("coa_accounts/<uuid:pk>/delete/", views.account_delete, name="account_delete"),
# Estimate # Estimate
path('sales/estimates/', views.EstimateListView.as_view(), name='estimate_list'), path("sales/estimates/", views.EstimateListView.as_view(), name="estimate_list"),
path('sales/estimates/<uuid:pk>/', views.EstimateDetailView.as_view(), name='estimate_detail'), path(
path('sales/estimates/create/', views.create_estimate, name='estimate_create'), "sales/estimates/<uuid:pk>/",
path('sales/estimates/<uuid:pk>/estimate_mark_as/', views.estimate_mark_as, name='estimate_mark_as'), views.EstimateDetailView.as_view(),
path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), name="estimate_detail",
path('sales/estimates/<uuid:pk>/payment_request/', views.PaymentRequest.as_view(), name='payment_request'), ),
path('sales/estimates/<uuid:pk>/send_email', views.send_email_view, name='send_email'), path("sales/estimates/create/", views.estimate_create, name="estimate_create"),
path(
"sales/estimates/<uuid:pk>/estimate_mark_as/",
views.estimate_mark_as,
name="estimate_mark_as",
),
path(
"sales/estimates/<uuid:pk>/preview/",
views.EstimatePreviewView.as_view(),
name="estimate_preview",
),
path(
"sales/estimates/<uuid:pk>/payment_request/",
views.PaymentRequest.as_view(),
name="payment_request",
),
path(
"sales/estimates/<uuid:pk>/send_email", views.send_email_view, name="send_email"
),
# Invoice # Invoice
path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'), path("sales/invoices/", views.InvoiceListView.as_view(), name="invoice_list"),
path('sales/invoices/<uuid:pk>/create/', views.invoice_create, name='invoice_create'), path(
path('sales/invoices/<uuid:pk>/', views.InvoiceDetailView.as_view(), name='invoice_detail'), "sales/invoices/<uuid:pk>/create/", views.invoice_create, name="invoice_create"
path('sales/invoices/<uuid:pk>/preview/', views.InvoicePreviewView.as_view(), name='invoice_preview'), ),
path('sales/invoices/<uuid:pk>/invoice_mark_as/', views.invoice_mark_as, name='invoice_mark_as'), path(
path('sales/invoices/<uuid:pk>/draft_invoice_update/', views.DraftInvoiceModelUpdateFormView.as_view(), name='draft_invoice_update'), "sales/invoices/<uuid:pk>/",
path('sales/invoices/<uuid:pk>/approved_invoice_update/', views.ApprovedInvoiceModelUpdateFormView.as_view(), name='approved_invoice_update'), views.InvoiceDetailView.as_view(),
path('sales/invoices/<uuid:pk>/paid_invoice_update/', views.PaidInvoiceModelUpdateFormView.as_view(), name='paid_invoice_update'), name="invoice_detail",
),
# path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), path(
# path('send_email/<uuid:pk>', views.send_email, name='send_email'), "sales/invoices/<uuid:pk>/preview/",
views.InvoicePreviewView.as_view(),
#Payment name="invoice_preview",
path('sales/payments/', views.PaymentListView, name='payment_list'), ),
path('sales/payments/<uuid:pk>/create/', views.PaymentCreateView, name='payment_create'), path(
path('sales/payments/create/', views.PaymentCreateView, name='payment_create'), "sales/invoices/<uuid:pk>/invoice_mark_as/",
path('sales/payments/<uuid:pk>/payment_details/', views.PaymentDetailView, name='payment_details'), views.invoice_mark_as,
path('sales/payments/<uuid:pk>/payment_mark_as_paid/', views.payment_mark_as_paid, name='payment_mark_as_paid'), name="invoice_mark_as",
# path('sales/payments/<uuid:pk>/update/', views.JournalEntryUpdateView.as_view(), name='payment_update'), ),
# path('sales/payments/<uuid:pk>/delete/', views.JournalEntryDeleteView.as_view(), name='payment_delete'), path(
# path('sales/payments/<uuid:pk>/preview/', views.JournalEntryPreviewView.as_view(), name='payment_preview'), "sales/invoices/<uuid:pk>/draft_invoice_update/",
# # Journal views.DraftInvoiceModelUpdateFormView.as_view(),
# path('sales/journal/<uuid:pk>/create/', views.JournalEntryCreateView.as_view(), name='journal_create'), name="draft_invoice_update",
),
# Items path(
path('items/services/', views.ItemServiceListView.as_view(), name='item_service_list'), "sales/invoices/<uuid:pk>/approved_invoice_update/",
path('items/services/create/', views.ItemServiceCreateView.as_view(), name='item_service_create'), views.ApprovedInvoiceModelUpdateFormView.as_view(),
path('items/services/<int:pk>/update/', views.ItemServiceUpdateView.as_view(), name='item_service_update'), name="approved_invoice_update",
# Expanese ),
path('items/expeneses/', views.ItemExpenseListView.as_view(), name='item_expense_list'), path(
path('items/expeneses/create/', views.ItemExpenseCreateView.as_view(), name='item_expense_create'), "sales/invoices/<uuid:pk>/paid_invoice_update/",
path('items/expeneses/<uuid:pk>/update/', views.ItemExpenseUpdateView.as_view(), name='item_expense_update'), views.PaidInvoiceModelUpdateFormView.as_view(),
# Bills name="paid_invoice_update",
path('items/bills/', views.BillListView.as_view(), name='bill_list'), ),
path('items/bills/create/', views.BillCreateView.as_view(), name='bill_create'), # path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'),
# path('items/bills/<uuid:pk>/update/', views.ItemExpenseUpdateView.as_view(), name='item_expense_update'), # path('send_email/<uuid:pk>', views.send_email, name='send_email'),
# Payment
path("sales/payments/", views.PaymentListView, name="payment_list"),
path(
"sales/payments/<uuid:pk>/create/",
views.PaymentCreateView,
name="payment_create",
),
path("sales/payments/create/", views.PaymentCreateView, name="payment_create"),
path(
"sales/payments/<uuid:pk>/payment_details/",
views.PaymentDetailView,
name="payment_details",
),
path(
"sales/payments/<uuid:pk>/payment_mark_as_paid/",
views.payment_mark_as_paid,
name="payment_mark_as_paid",
),
# path('sales/payments/<uuid:pk>/update/', views.JournalEntryUpdateView.as_view(), name='payment_update'),
# path('sales/payments/<uuid:pk>/delete/', views.JournalEntryDeleteView.as_view(), name='payment_delete'),
# path('sales/payments/<uuid:pk>/preview/', views.JournalEntryPreviewView.as_view(), name='payment_preview'),
# # Journal
# path('sales/journal/<uuid:pk>/create/', views.JournalEntryCreateView.as_view(), name='journal_create'),
# Items
path(
"items/services/", views.ItemServiceListView.as_view(), name="item_service_list"
),
path(
"items/services/create/",
views.ItemServiceCreateView.as_view(),
name="item_service_create",
),
path(
"items/services/<int:pk>/update/",
views.ItemServiceUpdateView.as_view(),
name="item_service_update",
),
# Expanese
path(
"items/expeneses/",
views.ItemExpenseListView.as_view(),
name="item_expense_list",
),
path(
"items/expeneses/create/",
views.ItemExpenseCreateView.as_view(),
name="item_expense_create",
),
path(
"items/expeneses/<uuid:pk>/update/",
views.ItemExpenseUpdateView.as_view(),
name="item_expense_update",
),
# Bills
path("items/bills/", views.BillListView.as_view(), name="bill_list"),
path("items/bills/create/", views.bill_create, name="bill_create"),
path(
"items/bills/<uuid:pk>/bill_detail/",
views.BillDetailView.as_view(),
name="bill_detail",
),
path("items/bills/<uuid:pk>/delete/", views.BillDeleteView, name="bill_delete"),
] ]
handler404 = 'inventory.views.custom_page_not_found_view' handler404 = "inventory.views.custom_page_not_found_view"
handler500 = 'inventory.views.custom_error_view' handler500 = "inventory.views.custom_error_view"
handler403 = 'inventory.views.custom_permission_denied_view' handler403 = "inventory.views.custom_permission_denied_view"
handler400 = 'inventory.views.custom_bad_request_view' handler400 = "inventory.views.custom_bad_request_view"

View File

@ -0,0 +1,57 @@
from django_ledger.models.invoice import InvoiceModel
class InvoiceModelBase(InvoiceModel):
"""
Custom Invoice Model with Net 15 payment terms.
"""
TERMS_ON_RECEIPT = 'on_receipt'
TERMS_NET_15 = 'net_15'
TERMS_NET_30 = 'net_30'
TERMS_NET_60 = 'net_60'
TERMS_NET_90 = 'net_90'
TERMS_NET_90_PLUS = 'net_90+'
TERM_CHOICES = [
(TERMS_ON_RECEIPT, 'Due On Receipt'),
(TERMS_NET_15, 'Net 15 Days'),
(TERMS_NET_30, 'Net 30 Days'),
(TERMS_NET_60, 'Net 60 Days'),
(TERMS_NET_90, 'Net 90 Days'),
]
TERM_CHOICES_VALID = tuple(i[0] for i in TERM_CHOICES)
TERM_DAYS_MAPPING = {
TERMS_ON_RECEIPT: 0,
TERMS_NET_15: 15,
TERMS_NET_30: 30,
TERMS_NET_60: 60,
TERMS_NET_90: 90,
TERMS_NET_90_PLUS: 120
}
def net_due_group(self):
"""
Determines the group where the financial instrument falls based on the number of days until the due date.
Returns
-------
str
The terms group as a string.
"""
due_in = self.due_in_days()
if due_in == 0:
return self.TERMS_ON_RECEIPT
elif due_in <= 15:
return self.TERMS_NET_15
elif due_in <= 30:
return self.TERMS_NET_30
elif due_in <= 60:
return self.TERMS_NET_60
elif due_in <= 90:
return self.TERMS_NET_90
return self.TERMS_NET_90_PLUS
class Meta:
proxy = True

View File

@ -14,13 +14,13 @@ from django_ledger.models import (
CustomerModel, CustomerModel,
LedgerModel, LedgerModel,
ItemModel, ItemModel,
BillModel BillModel,
) )
from django_ledger.forms.bank_account import ( from django_ledger.forms.bank_account import (
BankAccountCreateForm, BankAccountCreateForm,
BankAccountUpdateForm, BankAccountUpdateForm,
) )
from django_ledger.forms.bill import BillModelCreateForm
from django_ledger.forms.invoice import ( from django_ledger.forms.invoice import (
DraftInvoiceModelUpdateForm, DraftInvoiceModelUpdateForm,
ApprovedInvoiceModelUpdateForm, ApprovedInvoiceModelUpdateForm,
@ -64,7 +64,6 @@ from django.urls import reverse, reverse_lazy
from django.contrib import messages from django.contrib import messages
from django.db.models import Sum, F, Count from django.db.models import Sum, F, Count
from django.db import transaction from django.db import transaction
from .services import ( from .services import (
decodevin, decodevin,
get_make, get_make,
@ -1489,23 +1488,6 @@ def UserDeleteview(request, pk):
return redirect("user_list") return redirect("user_list")
# errors
def custom_page_not_found_view(request, exception):
return render(request, "errors/404.html", {})
def custom_error_view(request, exception=None):
return render(request, "errors/500.html", {})
def custom_permission_denied_view(request, exception=None):
return render(request, "errors/403.html", {})
def custom_bad_request_view(request, exception=None):
return render(request, "errors/400.html", {})
class OrganizationListView(LoginRequiredMixin, ListView): class OrganizationListView(LoginRequiredMixin, ListView):
model = models.Organization model = models.Organization
template_name = "organizations/organization_list.html" template_name = "organizations/organization_list.html"
@ -1652,40 +1634,40 @@ def download_quotation_pdf(request, quotation_id):
return HttpResponse("Quotation not found", status=404) return HttpResponse("Quotation not found", status=404)
@login_required # @login_required
def invoice_detail(request, pk): # def invoice_detail(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk) # quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = request.user.dealer # dealer = request.user.dealer
entity = dealer.entity # entity = dealer.entity
customer = ( # customer = (
entity.get_customers() # entity.get_customers()
.filter(customer_name=quotation.customer.get_full_name) # .filter(customer_name=quotation.customer.get_full_name)
.first() # .first()
) # )
invoice_model = entity.get_invoices() # invoice_model = entity.get_invoices()
invoice = invoice_model.filter( # invoice = invoice_model.filter(
customer=customer, date_draft=quotation.date_draft # customer=customer, date_draft=quotation.date_draft
).first() # ).first()
return redirect("quotation_detail", pk=pk) # return redirect("quotation_detail", pk=pk)
@login_required # @login_required
def payment_invoice(request, pk): # def payment_invoice(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk) # quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = request.user.dealer # dealer = request.user.dealer
entity = dealer.entity # entity = dealer.entity
customer = ( # customer = (
entity.get_customers() # entity.get_customers()
.filter(customer_name=quotation.customer.get_full_name) # .filter(customer_name=quotation.customer.get_full_name)
.first() # .first()
) # )
invoice_model = entity.get_invoices() # invoice_model = entity.get_invoices()
invoice = invoice_model.filter( # invoice = invoice_model.filter(
customer=customer, date_draft=quotation.date_draft # customer=customer, date_draft=quotation.date_draft
).first() # ).first()
return redirect("quotation_detail", pk=pk) # return redirect("quotation_detail", pk=pk)
# class PaymentCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView): # class PaymentCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
@ -1706,75 +1688,75 @@ def payment_invoice(request, pk):
# return context # return context
def payment_create(request, pk): # def payment_create(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk) # quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = get_user_type(request) # dealer = get_user_type(request)
if request.method == "POST": # if request.method == "POST":
form = forms.PaymentForm(request.POST) # form = forms.PaymentForm(request.POST)
if form.is_valid(): # if form.is_valid():
form.instance.quotation = quotation # form.instance.quotation = quotation
insatnce = form.save() # insatnce = form.save()
dealer = dealer # dealer = dealer
entity = dealer.entity # entity = dealer.entity
customer = ( # customer = (
entity.get_customers() # entity.get_customers()
.filter(customer_name=quotation.customer.get_full_name) # .filter(customer_name=quotation.customer.get_full_name)
.first() # .first()
) # )
coa_qs, coa_map = entity.get_all_coa_accounts() # coa_qs, coa_map = entity.get_all_coa_accounts()
cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash") # cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash")
recivable_account = ( # recivable_account = (
coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable") # coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable")
) # )
journal_entry = JournalEntryModel.objects.filter( # journal_entry = JournalEntryModel.objects.filter(
pk=quotation.payment_id # pk=quotation.payment_id
).first() # ).first()
TransactionModel.objects.create( # TransactionModel.objects.create(
journal_entry=journal_entry, # journal_entry=journal_entry,
account=cash_account.first(), # Debit Cash # account=cash_account.first(), # Debit Cash
amount=insatnce.amount, # Payment amount # amount=insatnce.amount, # Payment amount
tx_type="debit", # tx_type="debit",
description="Payment Received", # description="Payment Received",
) # )
TransactionModel.objects.create( # TransactionModel.objects.create(
journal_entry=journal_entry, # journal_entry=journal_entry,
account=recivable_account.first(), # Credit Accounts Receivable # account=recivable_account.first(), # Credit Accounts Receivable
amount=insatnce.amount, # Payment amount # amount=insatnce.amount, # Payment amount
tx_type="credit", # tx_type="credit",
description="Payment Received", # description="Payment Received",
) # )
journal_entry.posted = True # journal_entry.posted = True
quotation.posted = True # quotation.posted = True
quotation.save() # quotation.save()
journal_entry.save() # journal_entry.save()
invoice_model = ( # invoice_model = (
entity.get_invoices() # entity.get_invoices()
.filter(date_approved=quotation.date_approved) # .filter(date_approved=quotation.date_approved)
.first() # .first()
) # )
invoice_model.mark_as_paid( # invoice_model.mark_as_paid(
entity_slug=entity.slug, user_model=request.user.dealer # entity_slug=entity.slug, user_model=request.user.dealer
) # )
date = timezone.now() # date = timezone.now()
invoice_model.date_paid = date # invoice_model.date_paid = date
quotation.date_paid = date # quotation.date_paid = date
invoice_model.save() # invoice_model.save()
quotation.status = "Paid" # quotation.status = "Paid"
quotation.save() # quotation.save()
messages.success(request, "Payment created successfully.") # messages.success(request, "Payment created successfully.")
return redirect("quotation_detail", pk=pk) # return redirect("quotation_detail", pk=pk)
else: # else:
form = forms.PaymentForm() # form = forms.PaymentForm()
return render( # return render(
request, # request,
"sales/payments/payment_create.html", # "sales/payments/payment_create.html",
{"quotation": quotation, "form": form}, # {"quotation": quotation, "form": form},
) # )
# Ledger # Ledger
@ -1998,7 +1980,7 @@ class EstimateListView(LoginRequiredMixin, ListView):
# @csrf_exempt # @csrf_exempt
@login_required @login_required
def create_estimate(request): def estimate_create(request):
dealer = get_user_type(request) dealer = get_user_type(request)
entity = dealer.entity entity = dealer.entity
@ -2124,7 +2106,7 @@ def create_estimate(request):
for x in car_list for x in car_list
], ],
} }
print(context)
return render(request, "sales/estimates/estimate_form.html", context) return render(request, "sales/estimates/estimate_form.html", context)
@ -2391,6 +2373,8 @@ def invoice_create(request, pk):
invoice.save() invoice.save()
messages.success(request, "Invoice created successfully!") messages.success(request, "Invoice created successfully!")
return redirect("invoice_detail", pk=invoice.pk) return redirect("invoice_detail", pk=invoice.pk)
else:
print(form.errors)
form = forms.InvoiceModelCreateForm( form = forms.InvoiceModelCreateForm(
entity_slug=entity.slug, user_model=entity.admin entity_slug=entity.slug, user_model=entity.admin
) )
@ -2897,33 +2881,199 @@ class ItemExpenseListView(ListView):
class BillListView(ListView): class BillListView(ListView):
model = ItemModel model = BillModel
template_name = "ledger/bills/bill_list.html" template_name = "ledger/bills/bill_list.html"
context_object_name = "bills" context_object_name = "bills"
def get_queryset(self): def get_queryset(self):
dealer = get_user_type(self.request) dealer = get_user_type(self.request)
items = dealer.entity.get_bills() qs = dealer.entity.get_bills()
return items return qs
class BillCreateView(LoginRequiredMixin,SuccessMessageMixin,CreateView): class BillDetailView(LoginRequiredMixin, DetailView):
model = BillModel model = BillModel
form_class = BillModelCreateForm template_name = "ledger/bills/bill_detail.html"
template_name = "ledger/bills/bill_form.html" context_object_name = "bill"
success_url = reverse_lazy("bill_list")
success_message = _("Bill created successfully.")
def get_form_kwargs(self): def get_context_data(self, **kwargs):
dealer = get_user_type(self.request) bill = kwargs.get("object")
kwargs = super().get_form_kwargs()
kwargs["entity_model"] = dealer.entity
return kwargs
# def form_valid(self, form): if bill.get_itemtxs_data():
# dealer = get_user_type(self.request) txs = bill.get_itemtxs_data()[0]
# form.instance.entity = dealer.entity car_and_item_info = [
# return super().form_valid(form) {
"car": models.Car.objects.get(vin=x.item_model.name),
"total": models.Car.objects.get(
vin=x.item_model.name
).finances.cost_price
* Decimal(x.quantity),
"itemmodel": x,
}
for x in txs
]
grand_total = sum(
Decimal(
models.Car.objects.get(vin=x.item_model.name).finances.cost_price
)
* Decimal(x.quantity)
for x in txs
)
kwargs["car_and_item_info"] = car_and_item_info
kwargs["grand_total"] = grand_total
return super().get_context_data(**kwargs)
# class BillCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
# model = BillModel
# form_class = BillModelCreateForm
# template_name = "ledger/bills/bill_form.html"
# success_url = reverse_lazy("bill_list")
# success_message = _("Bill created successfully.")
# def get_form_kwargs(self):
# dealer = get_user_type(self.request)
# kwargs = super().get_form_kwargs()
# kwargs["entity_model"] = dealer.entity
# return kwargs
# def form_valid(self, form):
# dealer = get_user_type(self.request)
# form.instance.entity = dealer.entity
# ledger = dealer.entity.create_ledger(
# name=f"Bill for Vendor {form.instance.vendor.vendor_name}", posted=True
# )
# form.instance.ledger = ledger
# return super().form_valid(form)
@login_required
def bill_create(request):
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": "Estimate created successfully!",
"url": f"{url}",
}
)
form = forms.BillModelCreateForm(entity_model=entity)
form.initial.update(
{
"cash_account": entity.get_default_coa_accounts().get(name="Cash"),
"prepaid_account": entity.get_default_coa_accounts().get(
name="Prepaid Expenses"
),
"unearned_account": entity.get_default_coa_accounts().get(
name="Accounts Payable"
),
}
)
car_list = models.Car.objects.filter(dealer=dealer)
context = {
"form": form,
"items": [
{
"car": x,
"product": entity.get_items_all()
.filter(item_role=ItemModel.ITEM_ROLE_PRODUCT, name=x.vin)
.first(),
}
for x in car_list
],
}
return render(request, "ledger/bills/bill_form.html", context)
def BillDeleteView(request, pk):
bill = get_object_or_404(BillModel, pk=pk)
bill.delete()
return redirect("bill_list")
class SubscriptionPlans(ListView): class SubscriptionPlans(ListView):

View File

@ -1,122 +1,270 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% load i18n %} {% load i18n %}
{% block title %} {% block title %}{{ _("View Bill") }}{% endblock title %}
{{ page_title }}
{% endblock %}
{% block content %} {% block content %}
<!-- Delete Modal --> <div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true"> <div class="modal-dialog modal-sm">
<div class="modal-dialog modal-sm"> <div class="modal-content">
<div class="modal-content rounded"> <div class="modal-header bg-primary">
<div class="modal-body d-flex justify-content-center"> <h5 class="modal-title text-light" id="confirmModalLabel">{% trans 'Confirm' %}</h5>
<h1 class="text-danger me-2"><i class="bi bi-exclamation-diamond-fill"></i></h1> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
<span class="text-danger">{% trans 'Are you sure you want to delete this account?' %}</span> </div>
</div> <div class="modal-body">
<div class="btn-group"> {% trans 'Are you sure' %}
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">{% trans 'No' %}</button> <div class="modal-footer">
<div class="btn btn-sm btn-danger"> <button type="button"
<form action="{% url 'account_delete' account.pk %}" method="post"> class="btn btn-sm btn-danger"
{% csrf_token %} data-bs-dismiss="modal">
<button type="submit" class="btn btn-sm btn-danger">{% trans 'Yes' %}</button> {% trans 'No' %}
</form> </button>
{% comment %} <form id="confirmForm" method="POST" action="{% url 'bill_mark_as' bill.pk %}?mark=accept" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-success btn-sm">{% trans "Yes" %}</button>
</form> {% endcomment %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- ============================================-->
<div class="row my-5"> <div class="modal fade" id="mark_as_paid_Modal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="card rounded"> <div class="modal-dialog modal-sm">
<div class="card-header"> <div class="modal-content">
<p class="mb-0">{{ header_title|upper }}</p> <div class="modal-header bg-primary">
<h5 class="modal-title text-light" id="confirmModalLabel">{% trans 'Confirm' %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="card-body"> <div class="modal-body">
<div class="row"> {% trans 'Are you sure' %}
<div class="col-md-6"> <div class="modal-footer">
<p> <button type="button"
<strong>{{ _('Account Name') }}:</strong> {{ account.name }} class="btn btn-sm btn-danger"
</p> data-bs-dismiss="modal">
<p> {% trans 'No' %}
<strong>{{ _('Account Code') }}:</strong> {{ account.code }} </button>
</p> {% comment %} <form id="confirmForm" method="POST" action="{% url 'payment_mark_as_paid' bill.pk %}" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-success btn-sm">{% trans "Yes" %}</button>
</form> {% endcomment %}
</div> </div>
<div class="col-md-6"> </div>
<p> </div>
<strong>{{ _('Balance Type') }}:</strong> {{ account.balance_type }} </div>
</p> </div>
<p> <!-- ============================================-->
<strong>{{ _('Active') }}:</strong> {{ account.active }} <!-- <section> begin ============================-->
</p> <section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
</div> <div class="row-small mt-3">
</div> <div class="d-flex justify-content-between align-items-end mb-4">
<div class="row"> <h2 class="mb-0">{% trans 'Bill' %}</h2>
<div class="col"> <div class="d-flex align-items-center gap-2">
<table class="table is-fullwidth is-narrow is-striped is-bordered django-ledger-table-bottom-margin-75"> {% if bill.bill_status == 'in_review' %}
<tr> <button id="accept_bill" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#confirmModal"><span class="d-none d-sm-inline-block">{% trans 'Accept' %}</span></button>
<th class="has-text-centered">{{ _('JE Number') }}</th> {% endif %}
<th class="has-text-centered">{{ _('Date') }}</th> {% if bill.bill_status == 'approved' %}
<th class="has-text-centered">{{ _('Debit') }}</th> <a href="{% url 'payment_create' bill.pk %}" class="btn btn-phoenix-primary"><span class="d-none d-sm-inline-block">{% trans 'Record Payment' %}</span></a>
<th class="has-text-centered">{{ _('Credit') }}</th> {% endif %}
<th class="has-text-centered">{{ _('Description') }}</th> {% if not bill.is_paid %}
<th class="has-text-centered">{{ _('Unit') }}</th> <button id="mark_bill_as_paid" class="btn btn-phoenix-secondary" data-bs-toggle="modal" data-bs-target="#mark_as_paid_Modal"><span class="d-none d-sm-inline-block">{% trans 'Mark as Paid' %}</span></button>
<th class="has-text-centered">{{ _('Actions') }}</th> {% endif %}
</tr> </div>
</div>
{% for tx in account.transactionmodel_set.all %}
<tr class="has-text-centered"> <!-- ============================================-->
<td>{{ tx.journal_entry.je_number }}</td> <div class="card mb-5">
<td>{{ tx.journal_entry.timestamp }}</td> <div class="card-body">
<td> <div class="row g-4 g-xl-1 g-xxl-3 justify-content-between">
{% if tx.tx_type == 'debit' %} <div class="col-sm-auto">
${{ tx.amount }} <div class="d-sm-block d-inline-flex d-md-flex flex-xl-column flex-xxl-row align-items-center align-items-xl-start align-items-xxl-center">
{% endif %} <div class="d-flex bg-success-subtle rounded flex-center me-3 mb-sm-3 mb-md-0 mb-xl-3 mb-xxl-0" style="width:32px; height:32px"><span class="text-success-dark" data-feather="dollar-sign" style="width:24px; height:24px"></span></div>
</td> <div>
<td> <p class="fw-bold mb-1">{% trans 'Paid Amount' %}</p>
{% if tx.tx_type == 'credit' %} <h4 class="fw-bolder text-nowrap {% if bill.is_paid %}text-success{% endif %}">${{bill.amount_paid}}</h4>
${{ tx.amount }} <h6 class="fw-bolder text-nowrap">Owned <span class="fw-semibold text-nowrap text-success">${{bill.get_amount_open}}</span></h6>
{% endif %} <div class="progress" style="height:17px">
</td> <div class="progress-bar fw-semibold bg-{% if bill.get_progress_percent < 100 %}secondary{% else %}success{% endif %} rounded-2" role="progressbar" style="width: {{bill.get_progress_percent}}%" aria-valuenow="{{bill.get_progress_percent}}" aria-valuemin="0" aria-valuemax="100">{{bill.get_progress_percent}}%</div>
<td>{{ tx.description }}</td> </div>
<td>{{ tx.journal_entry.entity_unit.name }}</td> </div>
<td> </div>
<div class="btn-reveal-trigger position-static"> </div>
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button> {% if 'net' in bill.terms %}
<div class="dropdown-menu dropdown-menu-end py-2"> <div class="col-sm-auto">
<a class="dropdown-item" href="{% url 'payment_details' tx.journal_entry.pk %}">{% trans 'view'|capfirst %}</a> <div class="d-sm-block d-inline-flex d-md-flex flex-xl-column flex-xxl-row align-items-center align-items-xl-start align-items-xxl-center border-start-sm ps-sm-5 border-translucent">
<div class="d-flex bg-primary-subtle rounded flex-center me-3 mb-sm-3 mb-md-0 mb-xl-3 mb-xxl-0" style="width:32px; height:32px"><span class="text-primary-dark" data-feather="layout" style="width:24px; height:24px"></span></div>
<div>
<p class="fw-bold mb-1"></p>
<div class="fs-9 text-body-secondary fw-semibold mb-0 d-sm-block d-inline-flex d-md-flex flex-xl-column ">
<table>
<tr>
<td>{% trans 'Terms' %}:</td>
<td><span class="badge rounded bg-success">{{bill.bill_number}}</span></td>
</tr>
<tr>
<td>{% trans 'Date Due' %}:</td>
<td><span class="badge rounded bg-success">{{bill.date_due}}</span></td>
</tr>
<tr>
<td>{% trans 'Due in Days' %}:</td>
<td>
<span class="badge rounded bg-success">{{bill.due_in_days}}</span>
</td>
</tr>
<tr>
<td>{% trans 'Is Past Due' %}:</td>
<td>
{% if bill.is_past_due %}
<span class="badge rounded bg-danger">{% trans 'Yes' %}</span>
{% else %}
<span class="badge rounded bg-success">{% trans 'No' %}</span>
{% endif %}
</td>
</tr>
</table>
</div>
</div> </div>
</div> </div>
</td> </div>
</tr> {% endif %}
{% endfor %} <div class="col-sm-auto">
<tr class="has-text-weight-bold"> <div class="d-sm-block d-inline-flex d-md-flex flex-xl-column flex-xxl-row align-items-center align-items-xl-start align-items-xxl-center border-start-sm ps-sm-5 border-translucent">
<td></td> <div class="d-flex bg-primary-subtle rounded flex-center me-3 mb-sm-3 mb-md-0 mb-xl-3 mb-xxl-0" style="width:32px; height:32px"><span class="text-primary-dark" data-feather="layout" style="width:24px; height:24px"></span></div>
<td class="has-text-right">Total</td> <div>
<td class="has-text-centered">${{ total_debits }}</td> <p class="fw-bold mb-1">{% trans 'Due Amount' %}</p>
<td class="has-text-centered">${{ total_credits }}</td> {% if bill.is_paid %}
<td></td> <s><h4 class="fw-bolder text-nowrap">${{bill.amount_due}} </h4></s>
<td></td> {% else %}
<td></td> <h4 class="fw-bolder text-nowrap">${{bill.amount_due}} </h4>
</tr> {% endif %}
</table> </div>
</div>
</div>
</div>
</div>
</div>
<!-- <section> begin ============================-->
<div class="bg-body dark__bg-gray-1100 p-4 mb-4 rounded-2 text-body-tertiary">
<div class="row g-4">
<div class="col-12 col-lg-3">
<div class="row g-4 g-lg-2">
<div class="col-12 col-sm-6 col-lg-12">
<div class="row align-items-center g-0">
<div class="col-auto col-lg-6 col-xl-5">
<h6 class="mb-0 me-3">{% trans "Bill Number" %} :</h6>
</div>
<div class="col-auto col-lg-6 col-xl-7">
<p class="fs-9 text-body-secondary fw-semibold mb-0">#{{bill.bill_number}}</p>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-12">
<div class="row align-items-center g-0">
<div class="col-auto col-lg-6 col-xl-5">
<h6 class="me-3">{% trans "Bill Date" %} :</h6>
</div>
<div class="col-auto col-lg-6 col-xl-7">
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{bill.created}}</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-5">
<div class="row align-items-center g-0">
<div class="col-auto col-lg-6 col-xl-5">
<h6 class="mb-2 me-3">{% trans "Customer Name" %} :</h6>
<p class="fs-9 text-body-secondary fw-semibold mb-0">{{bill.vendor.vendor_name}}</p>
</div>
</div>
</div>
<div class="col-12 col-sm-6 col-lg-4">
<div class="row g-4">
<div class="col-12 col-lg-6">
<h6 class="mb-2"> {% trans "bill Status" %} :</h6>
<div class="fs-9 text-body-secondary fw-semibold mb-0">
{% if bill.bill_status == 'draft' %}
<span class="badge text-bg-warning">{% trans "Draft" %}</span>
{% elif bill.bill_status == 'in_review' %}
<span class="badge text-bg-info">{% trans "In Review" %}</span>
{% elif bill.bill_status == 'approved' %}
<span class="badge text-bg-info">{% trans "Approved" %}</span>
{% elif bill.bill_status == 'declined' %}
<span class="badge text-bg-danger">{% trans "Declined" %}</span>
{% elif bill.bill_status == 'paid' %}
<span class="badge text-bg-success">{% trans "Paid" %}</span>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="px-0">
<div class="table-responsive scrollbar">
<table id="bill-table" class="table fs-9 text-body mb-0">
<thead class="bg-body-secondary">
<tr>
<th scope="col" style="width: 24px;">#</th>
<th scope="col" style="min-width: 260px;">{% trans "Item" %}</th>
<th scope="col" style="min-width: 60px;">{% trans "Quantity" %}</th>
<th scope="col" style="min-width: 60px;">{% trans "Unit Price" %}</th>
<th scope="col" style="min-width: 60px;">{% trans "Total" %}</th>
</tr>
</thead>
<tbody>
{% for item in car_and_item_info %}
<tr>
<td class="">{{forloop.counter}}</td>
<td class="">{{item.car.id_car_model}}</td>
<td class="align-middle">{{item.itemmodel.quantity}}</td>
<td class="align-middle ps-5">{{item.car.finances.cost_price}}</td>
<td class="align-middle text-body-tertiary fw-semibold">{{item.total}}</td>
</tr>
{% endfor %}
<tr class="bg-body-secondary total-sum">
<td class="align-middle ps-4 fw-bolder text-body-highlight" colspan="4">{% trans "Grand Total" %}</td>
<td class="align-middle text-start fw-bolder">
<span id="grand-total">{{grand_total}}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div> </div>
</div>
</div>
<div class="card-footer d-flex">
<a class="btn btn-sm btn-primary me-1" href="{% url 'account_update' account.pk %}">
<!-- <i class="bi bi-pencil-square"></i> -->
{{ _('Edit') }}
</a>
<a class="btn btn-sm btn-danger me-1" data-bs-toggle="modal" data-bs-target="#deleteModal">
<!-- <i class="bi bi-trash-fill"></i> -->
{{ _('Delete') }}
</a>
<a class="btn btn-sm btn-secondary" href="{% url 'account_list' %}">
<!-- <i class="bi bi-arrow-left-square-fill"></i> -->
{% trans 'Back to List' %}
</a>
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block extra_js %}
<script>
function calculateTotals() {
const table = document.getElementById('estimate-table');
const rows = table.getElementsByTagName('tbody')[0].rows;
let grandTotal = 0;
for (let row of rows) {
// Ensure the row has the expected number of cells
if (row.cells.length >= 5) {
const quantity = parseFloat(row.cells[2].textContent); // Quantity column
const unitPrice = parseFloat(row.cells[3].textContent); // Unit Price column
if (!isNaN(quantity) && !isNaN(unitPrice)) {
const total = quantity * unitPrice;
row.cells[4].textContent = total.toFixed(2); // Populate Total column
grandTotal += total; // Add to grand total
}
}
}
// Display the grand total
document.getElementById('grand-total').textContent = grandTotal.toFixed(2);
}
// Run the function on page load
window.onload = calculateTotals;
</script>
{% endblock %}

View File

@ -1,38 +1,159 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n %}
{% load crispy_forms_filters %} {% load crispy_forms_filters %}
{% block title %}{% trans "account" %}{% endblock title %} {% load i18n static %}
{% block title %}{{ _("Create Bill") }}{% endblock title %}
{% block content %} {% block content %}
<div class="row my-5"> <div class="row mt-4">
<!-- Display Form Errors --> <h3 class="text-center">{% trans "Create Bill" %}</h3>
<div class="card shadow rounded"> <form id="mainForm" method="post" class="needs-validation">
<div class="card-header bg-primary text-white"> {% csrf_token %}
<p class="mb-0"> <div class="row g-3">
{% if account.created %} {{ form|crispy }}
<!--<i class="bi bi-pencil-square"></i>--> <div class="row mt-5">
{{ _("Edit Account") }} <div id="formrow">
{% else %} <h3 class="text-start">Unit Items</h3>
<!--<i class="bi bi-person-plus"></i> --> <div class="form-row row g-3 mb-3 mt-5">
{{ _("Add Account") }} <div class="mb-2 col-sm-2">
{% endif %} <select class="form-control item" name="item[]" required>
</p> {% for item in items %}
</div> <option value="{{ item.product.pk }}">{{ item.car.id_car_model }}</option>
<div class="card-body"> {% endfor %}
<form method="post" class="form" novalidate> </select>
{% csrf_token %} </div>
{{ form|crispy }} <div class="mb-2 col-sm-2">
{% for error in form.errors %} <input class="form-control quantity" type="number" placeholder="Quantity" name="quantity[]" required>
<div class="text-danger">{{ error }}</div> </div>
{% endfor %} <div class="mb-2 col-sm-1">
<div class="d-flex justify-content-end"> <button class="btn btn-danger removeBtn">Remove</button>
<button class="btn btn-sm btn-success me-1" type="submit"> </div>
{{ _("Save") }} </div>
</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger">{% trans "Cancel"|capfirst %}</a>
</div> </div>
</form> <div class="col-12">
<button id="addMoreBtn" class="btn btn-primary">Add More</button>
</div>
</div>
</div> </div>
</div>
<!-- Buttons -->
<div class="mt-5 text-center">
<button type="submit" class="btn btn-success me-2" {% if not items %}disabled{% endif %}>{% trans "Save" %}</button>
<a href="{% url 'bill_list' %}" class="btn btn-secondary">{% trans "Cancel" %}</a>
</div>
</form>
</div> </div>
{% endblock %} {% endblock content %}
{% block extra_js %}
<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);
});
console.log(formData);
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 extra_js %}

View File

@ -30,30 +30,25 @@
</div> </div>
</div> </div>
<!-- Customer Table --> <!-- Customer Table -->
{% if page_obj.object_list %}
<div id="accountsTable"> <div id="accountsTable">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-sm fs-9 mb-0"> <table class="table table-sm fs-9 mb-0">
<thead> <thead>
<tr class="bg-body-highlight"> <tr class="bg-body-highlight">
<th class="border-top border-translucent ps-3"> <th class="border-top border-translucent ps-3">
{% trans 'Account Name' %} {% trans 'Bill Number' %}
</th> </th>
<th class="border-top border-translucent"> <th class="border-top border-translucent">
{% trans 'Code' %} {% trans 'Bill Status' %}
</th> </th>
<th class="border-top border-translucent text-end pe-3"> <th class="border-top border-translucent text-end pe-3">
{% trans 'Balance Type' %} {% trans 'Vendor' %}
</th>
<th class="border-top border-translucent text-end pe-3">
{% trans 'Active' %}
</th> </th>
<th class="border-top border-translucent text-end align-middle pe-0 ps-4" scope="col"></th> <th class="border-top border-translucent text-end align-middle pe-0 ps-4" scope="col"></th>
</tr> </tr>
</thead> </thead>
<tbody class="list"> <tbody class="list">
{% for bill in bills %} {% for bill in bills %}
<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true"> <div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
@ -78,31 +73,16 @@
</div> </div>
</div> </div>
<tr> <tr>
<td class="align-middle ps-3">{{ bill.name }}</td> <td class="align-middle ps-3">{{ bill.bill_number }}</td>
<td class="align-middle">{{ bill.code }}</td> <td class="align-middle">{{ bill.bill_status }}</td>
<td class="align-middle text-end py-3 pe-3"> <td class="align-middle text-end py-3 pe-3">
{% if bill.balance_type == 'debit' %} {{bill.vendor.vendor_name}}
<div class="badge badge-phoenix fs-10 badge-phoenix-success">
<span class="fw-bold">{{ _('Debit') }}</span><span class="ms-1 fas fa-arrow-circle-down"></span>
</div>
{% else %}
<div class="badge badge-phoenix fs-10 badge-phoenix-danger">
<span class="fw-bold">{{ _('Credit') }}</span><span class="ms-1 fas fa-arrow-circle-up"></span>
</div>
{% endif %}
</td>
<td class="align-middle text-end py-3 pe-3">
{% if bill.active %}
<span class="fw-bold text-success fas fa-check-circle"></span>
{% else %}
<span class="fw-bold text-danger far fa-times-circle"></span>
{% endif %}
</td> </td>
<td> <td>
<div class="btn-reveal-trigger position-static"> <div class="btn-reveal-trigger position-static">
<button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button> <button class="btn btn-sm dropdown-toggle dropdown-caret-none transition-none btn-reveal fs-10" type="button" data-bs-toggle="dropdown" data-boundary="window" aria-haspopup="true" aria-expanded="false" data-bs-reference="parent"><span class="fas fa-ellipsis-h fs-10"></span></button>
<div class="dropdown-menu dropdown-menu-end py-2"> <div class="dropdown-menu dropdown-menu-end py-2">
<a href="{% url 'bill_detail' bill.uuid %}" 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 class="dropdown-divider"></div><button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans 'Delete' %}</button> <div class="dropdown-divider"></div><button class="dropdown-item text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">{% trans 'Delete' %}</button>
</div> </div>
</div> </div>
@ -157,8 +137,7 @@
</nav> </nav>
</div> </div>
</div> </div>
</div> </div>
{% endif %}
</main> </main>
</div> </div>
</div> </div>