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 django.forms.models import inlineformset_factory
from django_ledger.forms.invoice import InvoiceModelCreateForm as InvoiceModelCreateFormBase
from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormBase
from .models import (
Dealer,
# Branch,
@ -634,4 +635,14 @@ class InvoiceModelCreateForm(InvoiceModelCreateFormBase):
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'}))
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 . import views
from allauth.account import views as allauth_views
from django.conf.urls import (
handler400, handler403, handler404, handler500
)
from django.conf.urls import handler400, handler403, handler404, handler500
urlpatterns = [
# main URLs
path('', views.HomeView.as_view(), name='landing_page'),
path('welcome/', views.WelcomeView.as_view(), name='welcome'),
path("", views.HomeView.as_view(), name="landing_page"),
path("welcome/", views.WelcomeView.as_view(), name="welcome"),
# Accounts URLs
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('signup/', allauth_views.SignupView.as_view(template_name='account/signup.html'), name='account_signup'),
path('signup/', views.dealer_signup, name='account_signup'),
path('password/change/',
allauth_views.PasswordChangeView.as_view(template_name='account/password_change.html'),
name='account_change_password'),
path('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'),
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('signup/', allauth_views.SignupView.as_view(template_name='account/signup.html'), name='account_signup'),
path("signup/", views.dealer_signup, name="account_signup"),
path(
"password/change/",
allauth_views.PasswordChangeView.as_view(
template_name="account/password_change.html"
),
name="account_change_password",
),
path(
"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
path('dealers/<int:pk>/', views.DealerDetailView.as_view(), name='dealer_detail'),
path('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>/", views.DealerDetailView.as_view(), name="dealer_detail"),
path(
"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'),
# CRM URLs
path('customers/', views.CustomerListView.as_view(), name='customer_list'),
path('customers/<int:pk>/', views.CustomerDetailView.as_view(), name='customer_detail'),
path('customers/create/', views.CustomerCreateView.as_view(), name='customer_create'),
path('customers/<int:pk>/update/', views.CustomerUpdateView.as_view(), name='customer_update'),
path('customers/<int:pk>/delete/', views.delete_customer, name='customer_delete'),
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('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("customers/", views.CustomerListView.as_view(), name="customer_list"),
path(
"customers/<int:pk>/",
views.CustomerDetailView.as_view(),
name="customer_detail",
),
path(
"customers/create/", views.CustomerCreateView.as_view(), name="customer_create"
),
path(
"customers/<int:pk>/update/",
views.CustomerUpdateView.as_view(),
name="customer_update",
),
path("customers/<int:pk>/delete/", views.delete_customer, name="customer_delete"),
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("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/notifications/', views.NotificationListView.as_view(), name='notifications_history'),
path('crm/fetch_notifications/', views.fetch_notifications, name='fetch_notifications'),
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'),
path(
"crm/notifications/",
views.NotificationListView.as_view(),
name="notifications_history",
),
path(
"crm/fetch_notifications/",
views.fetch_notifications,
name="fetch_notifications",
),
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
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('cars/inventory/stats', views.inventory_stats_view, name='inventory_stats'),
path('cars/<int:pk>/', views.CarDetailView.as_view(), name='car_detail'),
path('cars/<int:pk>/update/', views.CarUpdateView.as_view(), name='car_update'),
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/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/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("cars/inventory/stats", views.inventory_stats_view, name="inventory_stats"),
path("cars/<int:pk>/", views.CarDetailView.as_view(), name="car_detail"),
path("cars/<int:pk>/update/", views.CarUpdateView.as_view(), name="car_update"),
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/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/reserve/<int:car_id>/', views.reserve_car_view, name='reserve_car'),
path('reservations/<int:reservation_id>/', views.manage_reservation, name='reservations'),
path('cars/<int:car_pk>/add-custom-card/', views.CustomCardCreateView.as_view(), name='add_custom_card'),
path("cars/reserve/<int:car_id>/", views.reserve_car_view, name="reserve_car"),
path(
"reservations/<int:reservation_id>/",
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
path('sales/quotations/create/', views.QuotationCreateView.as_view(), name='quotation_create'),
path('sales/quotations/<int:pk>/', views.QuotationDetailView.as_view(), name='quotation_detail'),
path('sales/quotations/', views.QuotationListView.as_view(), name='quotation_list'),
path('sales/quotations/<int:pk>/confirm/', 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.invoice_detail, 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.payment_create, name='payment_create'),
path(
"sales/quotations/create/",
views.QuotationCreateView.as_view(),
name="quotation_create",
),
path(
"sales/quotations/<int:pk>/",
views.QuotationDetailView.as_view(),
name="quotation_detail",
),
path("sales/quotations/", views.QuotationListView.as_view(), name="quotation_list"),
path(
"sales/quotations/<int:pk>/confirm/",
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
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>/', views.UserDetailView.as_view(), name='user_detail'),
path('user/', views.UserListView.as_view(), name='user_list'),
path('user/<int:pk>/confirm/', views.UserDeleteview, name='user_delete'),
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>/", views.UserDetailView.as_view(), name="user_detail"),
path("user/", views.UserListView.as_view(), name="user_list"),
path("user/<int:pk>/confirm/", views.UserDeleteview, name="user_delete"),
# Organization URLs
path('organizations/', views.OrganizationListView.as_view(), name='organization_list'),
path('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'),
path(
"organizations/", views.OrganizationListView.as_view(), name="organization_list"
),
path(
"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
path('representatives/', views.RepresentativeListView.as_view(), name='representative_list'),
path('representatives/<int:pk>/', views.RepresentativeDetailView.as_view(), name='representative_detail'),
path('representatives/create/', 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'),
path(
"representatives/",
views.RepresentativeListView.as_view(),
name="representative_list",
),
path(
"representatives/<int:pk>/",
views.RepresentativeDetailView.as_view(),
name="representative_detail",
),
path(
"representatives/create/",
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
path('coa_accounts/', views.AccountListView.as_view(), name='account_list'),
path('coa_accounts/<uuid:pk>/', views.AccountDetailView.as_view(), 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'),
path("coa_accounts/", views.AccountListView.as_view(), name="account_list"),
path(
"coa_accounts/<uuid:pk>/",
views.AccountDetailView.as_view(),
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
path('sales/estimates/', views.EstimateListView.as_view(), name='estimate_list'),
path('sales/estimates/<uuid:pk>/', views.EstimateDetailView.as_view(), name='estimate_detail'),
path('sales/estimates/create/', views.create_estimate, 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'),
path("sales/estimates/", views.EstimateListView.as_view(), name="estimate_list"),
path(
"sales/estimates/<uuid:pk>/",
views.EstimateDetailView.as_view(),
name="estimate_detail",
),
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
path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'),
path('sales/invoices/<uuid:pk>/create/', views.invoice_create, name='invoice_create'),
path('sales/invoices/<uuid:pk>/', views.InvoiceDetailView.as_view(), name='invoice_detail'),
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('sales/invoices/<uuid:pk>/draft_invoice_update/', views.DraftInvoiceModelUpdateFormView.as_view(), name='draft_invoice_update'),
path('sales/invoices/<uuid:pk>/approved_invoice_update/', views.ApprovedInvoiceModelUpdateFormView.as_view(), name='approved_invoice_update'),
path('sales/invoices/<uuid:pk>/paid_invoice_update/', views.PaidInvoiceModelUpdateFormView.as_view(), name='paid_invoice_update'),
# path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'),
# 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.BillCreateView.as_view(), name='bill_create'),
# path('items/bills/<uuid:pk>/update/', views.ItemExpenseUpdateView.as_view(), name='item_expense_update'),
path("sales/invoices/", views.InvoiceListView.as_view(), name="invoice_list"),
path(
"sales/invoices/<uuid:pk>/create/", views.invoice_create, name="invoice_create"
),
path(
"sales/invoices/<uuid:pk>/",
views.InvoiceDetailView.as_view(),
name="invoice_detail",
),
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(
"sales/invoices/<uuid:pk>/draft_invoice_update/",
views.DraftInvoiceModelUpdateFormView.as_view(),
name="draft_invoice_update",
),
path(
"sales/invoices/<uuid:pk>/approved_invoice_update/",
views.ApprovedInvoiceModelUpdateFormView.as_view(),
name="approved_invoice_update",
),
path(
"sales/invoices/<uuid:pk>/paid_invoice_update/",
views.PaidInvoiceModelUpdateFormView.as_view(),
name="paid_invoice_update",
),
# path('sales/estimates/<uuid:pk>/preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'),
# 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'
handler500 = 'inventory.views.custom_error_view'
handler403 = 'inventory.views.custom_permission_denied_view'
handler400 = 'inventory.views.custom_bad_request_view'
handler404 = "inventory.views.custom_page_not_found_view"
handler500 = "inventory.views.custom_error_view"
handler403 = "inventory.views.custom_permission_denied_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,
LedgerModel,
ItemModel,
BillModel
BillModel,
)
from django_ledger.forms.bank_account import (
BankAccountCreateForm,
BankAccountUpdateForm,
)
from django_ledger.forms.bill import BillModelCreateForm
from django_ledger.forms.invoice import (
DraftInvoiceModelUpdateForm,
ApprovedInvoiceModelUpdateForm,
@ -64,7 +64,6 @@ from django.urls import reverse, reverse_lazy
from django.contrib import messages
from django.db.models import Sum, F, Count
from django.db import transaction
from .services import (
decodevin,
get_make,
@ -1489,23 +1488,6 @@ def UserDeleteview(request, pk):
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):
model = models.Organization
template_name = "organizations/organization_list.html"
@ -1652,40 +1634,40 @@ def download_quotation_pdf(request, quotation_id):
return HttpResponse("Quotation not found", status=404)
@login_required
def invoice_detail(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = request.user.dealer
entity = dealer.entity
customer = (
entity.get_customers()
.filter(customer_name=quotation.customer.get_full_name)
.first()
)
invoice_model = entity.get_invoices()
# @login_required
# def invoice_detail(request, pk):
# quotation = get_object_or_404(models.SaleQuotation, pk=pk)
# dealer = request.user.dealer
# entity = dealer.entity
# customer = (
# entity.get_customers()
# .filter(customer_name=quotation.customer.get_full_name)
# .first()
# )
# invoice_model = entity.get_invoices()
invoice = invoice_model.filter(
customer=customer, date_draft=quotation.date_draft
).first()
return redirect("quotation_detail", pk=pk)
# invoice = invoice_model.filter(
# customer=customer, date_draft=quotation.date_draft
# ).first()
# return redirect("quotation_detail", pk=pk)
@login_required
def payment_invoice(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = request.user.dealer
entity = dealer.entity
customer = (
entity.get_customers()
.filter(customer_name=quotation.customer.get_full_name)
.first()
)
invoice_model = entity.get_invoices()
invoice = invoice_model.filter(
customer=customer, date_draft=quotation.date_draft
).first()
# @login_required
# def payment_invoice(request, pk):
# quotation = get_object_or_404(models.SaleQuotation, pk=pk)
# dealer = request.user.dealer
# entity = dealer.entity
# customer = (
# entity.get_customers()
# .filter(customer_name=quotation.customer.get_full_name)
# .first()
# )
# invoice_model = entity.get_invoices()
# invoice = invoice_model.filter(
# customer=customer, date_draft=quotation.date_draft
# ).first()
return redirect("quotation_detail", pk=pk)
# return redirect("quotation_detail", pk=pk)
# class PaymentCreateView(LoginRequiredMixin, SuccessMessageMixin, CreateView):
@ -1706,75 +1688,75 @@ def payment_invoice(request, pk):
# return context
def payment_create(request, pk):
quotation = get_object_or_404(models.SaleQuotation, pk=pk)
dealer = get_user_type(request)
if request.method == "POST":
form = forms.PaymentForm(request.POST)
if form.is_valid():
form.instance.quotation = quotation
insatnce = form.save()
# def payment_create(request, pk):
# quotation = get_object_or_404(models.SaleQuotation, pk=pk)
# dealer = get_user_type(request)
# if request.method == "POST":
# form = forms.PaymentForm(request.POST)
# if form.is_valid():
# form.instance.quotation = quotation
# insatnce = form.save()
dealer = dealer
entity = dealer.entity
customer = (
entity.get_customers()
.filter(customer_name=quotation.customer.get_full_name)
.first()
)
coa_qs, coa_map = entity.get_all_coa_accounts()
cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash")
recivable_account = (
coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable")
)
journal_entry = JournalEntryModel.objects.filter(
pk=quotation.payment_id
).first()
TransactionModel.objects.create(
journal_entry=journal_entry,
account=cash_account.first(), # Debit Cash
amount=insatnce.amount, # Payment amount
tx_type="debit",
description="Payment Received",
)
# dealer = dealer
# entity = dealer.entity
# customer = (
# entity.get_customers()
# .filter(customer_name=quotation.customer.get_full_name)
# .first()
# )
# coa_qs, coa_map = entity.get_all_coa_accounts()
# cash_account = coa_qs.first().get_coa_accounts().filter(name="Cash")
# recivable_account = (
# coa_qs.first().get_coa_accounts().filter(name="Accounts Receivable")
# )
# journal_entry = JournalEntryModel.objects.filter(
# pk=quotation.payment_id
# ).first()
# TransactionModel.objects.create(
# journal_entry=journal_entry,
# account=cash_account.first(), # Debit Cash
# amount=insatnce.amount, # Payment amount
# tx_type="debit",
# description="Payment Received",
# )
TransactionModel.objects.create(
journal_entry=journal_entry,
account=recivable_account.first(), # Credit Accounts Receivable
amount=insatnce.amount, # Payment amount
tx_type="credit",
description="Payment Received",
)
journal_entry.posted = True
quotation.posted = True
quotation.save()
journal_entry.save()
# TransactionModel.objects.create(
# journal_entry=journal_entry,
# account=recivable_account.first(), # Credit Accounts Receivable
# amount=insatnce.amount, # Payment amount
# tx_type="credit",
# description="Payment Received",
# )
# journal_entry.posted = True
# quotation.posted = True
# quotation.save()
# journal_entry.save()
invoice_model = (
entity.get_invoices()
.filter(date_approved=quotation.date_approved)
.first()
)
# invoice_model = (
# entity.get_invoices()
# .filter(date_approved=quotation.date_approved)
# .first()
# )
invoice_model.mark_as_paid(
entity_slug=entity.slug, user_model=request.user.dealer
)
date = timezone.now()
invoice_model.date_paid = date
quotation.date_paid = date
invoice_model.save()
quotation.status = "Paid"
quotation.save()
# invoice_model.mark_as_paid(
# entity_slug=entity.slug, user_model=request.user.dealer
# )
# date = timezone.now()
# invoice_model.date_paid = date
# quotation.date_paid = date
# invoice_model.save()
# quotation.status = "Paid"
# quotation.save()
messages.success(request, "Payment created successfully.")
return redirect("quotation_detail", pk=pk)
else:
form = forms.PaymentForm()
return render(
request,
"sales/payments/payment_create.html",
{"quotation": quotation, "form": form},
)
# messages.success(request, "Payment created successfully.")
# return redirect("quotation_detail", pk=pk)
# else:
# form = forms.PaymentForm()
# return render(
# request,
# "sales/payments/payment_create.html",
# {"quotation": quotation, "form": form},
# )
# Ledger
@ -1998,7 +1980,7 @@ class EstimateListView(LoginRequiredMixin, ListView):
# @csrf_exempt
@login_required
def create_estimate(request):
def estimate_create(request):
dealer = get_user_type(request)
entity = dealer.entity
@ -2124,7 +2106,7 @@ def create_estimate(request):
for x in car_list
],
}
print(context)
return render(request, "sales/estimates/estimate_form.html", context)
@ -2391,6 +2373,8 @@ def invoice_create(request, pk):
invoice.save()
messages.success(request, "Invoice created successfully!")
return redirect("invoice_detail", pk=invoice.pk)
else:
print(form.errors)
form = forms.InvoiceModelCreateForm(
entity_slug=entity.slug, user_model=entity.admin
)
@ -2897,33 +2881,199 @@ class ItemExpenseListView(ListView):
class BillListView(ListView):
model = ItemModel
model = BillModel
template_name = "ledger/bills/bill_list.html"
context_object_name = "bills"
def get_queryset(self):
dealer = get_user_type(self.request)
items = dealer.entity.get_bills()
return items
qs = dealer.entity.get_bills()
return qs
class BillCreateView(LoginRequiredMixin,SuccessMessageMixin,CreateView):
class BillDetailView(LoginRequiredMixin, DetailView):
model = BillModel
form_class = BillModelCreateForm
template_name = "ledger/bills/bill_form.html"
success_url = reverse_lazy("bill_list")
success_message = _("Bill created successfully.")
template_name = "ledger/bills/bill_detail.html"
context_object_name = "bill"
def get_form_kwargs(self):
dealer = get_user_type(self.request)
kwargs = super().get_form_kwargs()
kwargs["entity_model"] = dealer.entity
return kwargs
def get_context_data(self, **kwargs):
bill = kwargs.get("object")
# def form_valid(self, form):
# dealer = get_user_type(self.request)
# form.instance.entity = dealer.entity
# return super().form_valid(form)
if bill.get_itemtxs_data():
txs = bill.get_itemtxs_data()[0]
car_and_item_info = [
{
"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):

View File

@ -1,122 +1,270 @@
{% extends 'base.html' %}
{% extends "base.html" %}
{% load i18n %}
{% block title %}
{{ page_title }}
{% endblock %}
{% block title %}{{ _("View Bill") }}{% endblock title %}
{% block content %}
<!-- Delete Modal -->
<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-content rounded">
<div class="modal-body d-flex justify-content-center">
<h1 class="text-danger me-2"><i class="bi bi-exclamation-diamond-fill"></i></h1>
<span class="text-danger">{% trans 'Are you sure you want to delete this account?' %}</span>
</div>
<div class="btn-group">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">{% trans 'No' %}</button>
<div class="btn btn-sm btn-danger">
<form action="{% url 'account_delete' account.pk %}" method="post">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-danger">{% trans 'Yes' %}</button>
</form>
<div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<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 class="modal-body">
{% trans 'Are you sure' %}
<div class="modal-footer">
<button type="button"
class="btn btn-sm btn-danger"
data-bs-dismiss="modal">
{% trans 'No' %}
</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 class="row my-5">
<div class="card rounded">
<div class="card-header">
<p class="mb-0">{{ header_title|upper }}</p>
<!-- ============================================-->
<div class="modal fade" id="mark_as_paid_Modal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<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 class="card-body">
<div class="row">
<div class="col-md-6">
<p>
<strong>{{ _('Account Name') }}:</strong> {{ account.name }}
</p>
<p>
<strong>{{ _('Account Code') }}:</strong> {{ account.code }}
</p>
<div class="modal-body">
{% trans 'Are you sure' %}
<div class="modal-footer">
<button type="button"
class="btn btn-sm btn-danger"
data-bs-dismiss="modal">
{% trans 'No' %}
</button>
{% 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 class="col-md-6">
<p>
<strong>{{ _('Balance Type') }}:</strong> {{ account.balance_type }}
</p>
<p>
<strong>{{ _('Active') }}:</strong> {{ account.active }}
</p>
</div>
</div>
<div class="row">
<div class="col">
<table class="table is-fullwidth is-narrow is-striped is-bordered django-ledger-table-bottom-margin-75">
<tr>
<th class="has-text-centered">{{ _('JE Number') }}</th>
<th class="has-text-centered">{{ _('Date') }}</th>
<th class="has-text-centered">{{ _('Debit') }}</th>
<th class="has-text-centered">{{ _('Credit') }}</th>
<th class="has-text-centered">{{ _('Description') }}</th>
<th class="has-text-centered">{{ _('Unit') }}</th>
<th class="has-text-centered">{{ _('Actions') }}</th>
</tr>
{% for tx in account.transactionmodel_set.all %}
<tr class="has-text-centered">
<td>{{ tx.journal_entry.je_number }}</td>
<td>{{ tx.journal_entry.timestamp }}</td>
<td>
{% if tx.tx_type == 'debit' %}
${{ tx.amount }}
{% endif %}
</td>
<td>
{% if tx.tx_type == 'credit' %}
${{ tx.amount }}
{% endif %}
</td>
<td>{{ tx.description }}</td>
<td>{{ tx.journal_entry.entity_unit.name }}</td>
<td>
<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>
<div class="dropdown-menu dropdown-menu-end py-2">
<a class="dropdown-item" href="{% url 'payment_details' tx.journal_entry.pk %}">{% trans 'view'|capfirst %}</a>
</div>
</div>
</div>
</div>
<!-- ============================================-->
<!-- <section> begin ============================-->
<section class="pt-5 pb-9 bg-body-emphasis dark__bg-gray-1200 border-top">
<div class="row-small mt-3">
<div class="d-flex justify-content-between align-items-end mb-4">
<h2 class="mb-0">{% trans 'Bill' %}</h2>
<div class="d-flex align-items-center gap-2">
{% if bill.bill_status == 'in_review' %}
<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>
{% endif %}
{% if bill.bill_status == 'approved' %}
<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>
{% endif %}
{% if not bill.is_paid %}
<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>
{% endif %}
</div>
</div>
<!-- ============================================-->
<div class="card mb-5">
<div class="card-body">
<div class="row g-4 g-xl-1 g-xxl-3 justify-content-between">
<div class="col-sm-auto">
<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">
<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>
<div>
<p class="fw-bold mb-1">{% trans 'Paid Amount' %}</p>
<h4 class="fw-bolder text-nowrap {% if bill.is_paid %}text-success{% endif %}">${{bill.amount_paid}}</h4>
<h6 class="fw-bolder text-nowrap">Owned <span class="fw-semibold text-nowrap text-success">${{bill.get_amount_open}}</span></h6>
<div class="progress" style="height:17px">
<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>
</div>
</div>
</div>
</div>
{% if 'net' in bill.terms %}
<div class="col-sm-auto">
<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>
</td>
</tr>
{% endfor %}
<tr class="has-text-weight-bold">
<td></td>
<td class="has-text-right">Total</td>
<td class="has-text-centered">${{ total_debits }}</td>
<td class="has-text-centered">${{ total_credits }}</td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</div>
{% endif %}
<div class="col-sm-auto">
<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">{% trans 'Due Amount' %}</p>
{% if bill.is_paid %}
<s><h4 class="fw-bolder text-nowrap">${{bill.amount_due}} </h4></s>
{% else %}
<h4 class="fw-bolder text-nowrap">${{bill.amount_due}} </h4>
{% endif %}
</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 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 %}
{% 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" %}
{% load i18n %}
{% load crispy_forms_filters %}
{% block title %}{% trans "account" %}{% endblock title %}
{% load i18n static %}
{% block title %}{{ _("Create Bill") }}{% endblock title %}
{% block content %}
<div class="row my-5">
<!-- Display Form Errors -->
<div class="card shadow rounded">
<div class="card-header bg-primary text-white">
<p class="mb-0">
{% if account.created %}
<!--<i class="bi bi-pencil-square"></i>-->
{{ _("Edit Account") }}
{% else %}
<!--<i class="bi bi-person-plus"></i> -->
{{ _("Add Account") }}
{% endif %}
</p>
</div>
<div class="card-body">
<form method="post" class="form" novalidate>
{% csrf_token %}
{{ form|crispy }}
{% for error in form.errors %}
<div class="text-danger">{{ error }}</div>
{% endfor %}
<div class="d-flex justify-content-end">
<button class="btn btn-sm btn-success me-1" type="submit">
{{ _("Save") }}
</button>
<a href="{{request.META.HTTP_REFERER}}" class="btn btn-sm btn-danger">{% trans "Cancel"|capfirst %}</a>
<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-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>
</div>
</div>
</form>
<div class="col-12">
<button id="addMoreBtn" class="btn btn-primary">Add More</button>
</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>
{% 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>
<!-- Customer Table -->
{% if page_obj.object_list %}
<!-- Customer Table -->
<div id="accountsTable">
<div class="table-responsive">
<table class="table table-sm fs-9 mb-0">
<thead>
<tr class="bg-body-highlight">
<th class="border-top border-translucent ps-3">
{% trans 'Account Name' %}
{% trans 'Bill Number' %}
</th>
<th class="border-top border-translucent">
{% trans 'Code' %}
</th>
{% trans 'Bill Status' %}
</th>
<th class="border-top border-translucent text-end pe-3">
{% trans 'Balance Type' %}
</th>
<th class="border-top border-translucent text-end pe-3">
{% trans 'Active' %}
{% trans 'Vendor' %}
</th>
<th class="border-top border-translucent text-end align-middle pe-0 ps-4" scope="col"></th>
</tr>
</thead>
<tbody class="list">
<tbody class="list">
{% 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-dialog modal-sm">
@ -78,31 +73,16 @@
</div>
</div>
<tr>
<td class="align-middle ps-3">{{ bill.name }}</td>
<td class="align-middle">{{ bill.code }}</td>
<td class="align-middle ps-3">{{ bill.bill_number }}</td>
<td class="align-middle">{{ bill.bill_status }}</td>
<td class="align-middle text-end py-3 pe-3">
{% if bill.balance_type == 'debit' %}
<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 %}
{{bill.vendor.vendor_name}}
</td>
<td>
<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>
<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>
</div>
@ -157,8 +137,7 @@
</nav>
</div>
</div>
</div>
{% endif %}
</div>
</main>
</div>
</div>