From c277e73dec4cb12ccde9a828299879da337990c2 Mon Sep 17 00:00:00 2001 From: gitea Date: Thu, 16 Jan 2025 17:31:44 +0000 Subject: [PATCH 1/2] update --- inventory/forms.py | 13 +- inventory/migrations/0004_invoicemodelbase.py | 25 + inventory/urls.py | 641 +++++++++++++----- inventory/utilities/ledgers.py | 57 ++ inventory/views.py | 416 ++++++++---- templates/ledger/bills/bill_detail.html | 364 +++++++--- templates/ledger/bills/bill_form.html | 183 ++++- templates/ledger/bills/bill_list.html | 43 +- 8 files changed, 1264 insertions(+), 478 deletions(-) create mode 100644 inventory/migrations/0004_invoicemodelbase.py create mode 100644 inventory/utilities/ledgers.py diff --git a/inventory/forms.py b/inventory/forms.py index 1d9083b4..4d513bb7 100644 --- a/inventory/forms.py +++ b/inventory/forms.py @@ -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'})) \ No newline at end of file + 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'})) + \ No newline at end of file diff --git a/inventory/migrations/0004_invoicemodelbase.py b/inventory/migrations/0004_invoicemodelbase.py new file mode 100644 index 00000000..72906762 --- /dev/null +++ b/inventory/migrations/0004_invoicemodelbase.py @@ -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',), + ), + ] diff --git a/inventory/urls.py b/inventory/urls.py index 08b832fe..5e2e23f5 100644 --- a/inventory/urls.py +++ b/inventory/urls.py @@ -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//', views.DealerDetailView.as_view(), name='dealer_detail'), - path('dealers//update/', views.DealerUpdateView.as_view(), name='dealer_update'), - path('dealers/activity/', views.UserActivityLogListView.as_view(), name='dealer_activity'), + path("dealers//", views.DealerDetailView.as_view(), name="dealer_detail"), + path( + "dealers//update/", + views.DealerUpdateView.as_view(), + name="dealer_update", + ), + path( + "dealers/activity/", + views.UserActivityLogListView.as_view(), + name="dealer_activity", + ), # path('dealers//delete/', views.DealerDeleteView.as_view(), name='dealer_delete'), - # CRM URLs - path('customers/', views.CustomerListView.as_view(), name='customer_list'), - path('customers//', views.CustomerDetailView.as_view(), name='customer_detail'), - path('customers/create/', views.CustomerCreateView.as_view(), name='customer_create'), - path('customers//update/', views.CustomerUpdateView.as_view(), name='customer_update'), - path('customers//delete/', views.delete_customer, name='customer_delete'), - path('customers//opportunities/create/', views.OpportunityCreateView.as_view(), name='create_opportunity'), - path('customers//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//view/', views.LeadDetailView.as_view(), name='lead_detail'), - path('crm/leads/create/', views.LeadCreateView.as_view(), name='lead_create'), - path('crm/leads//update/', views.LeadUpdateView.as_view(), name='lead_update'), - path('crm/leads//delete/', views.LeadDeleteView.as_view(), name='lead_delete'), - path('crm/leads//add-note/', views.add_note_to_lead, name='add_note'), - path('crm/leads//add-activity/', views.add_activity_to_lead, name='add_activity'), - path('crm/opportunities/create/', views.OpportunityCreateView.as_view(), name='opportunity_create'), - path('crm/opportunities//', views.OpportunityDetailView.as_view(), name='opportunity_detail'), - path('crm/opportunities//edit/', views.OpportunityUpdateView.as_view(), name='update_opportunity'), - path('crm/opportunities/', views.OpportunityListView.as_view(), name='opportunity_list'), - path('crm/opportunities//delete/', views.delete_opportunity, name='delete_opportunity'), + path("customers/", views.CustomerListView.as_view(), name="customer_list"), + path( + "customers//", + views.CustomerDetailView.as_view(), + name="customer_detail", + ), + path( + "customers/create/", views.CustomerCreateView.as_view(), name="customer_create" + ), + path( + "customers//update/", + views.CustomerUpdateView.as_view(), + name="customer_update", + ), + path("customers//delete/", views.delete_customer, name="customer_delete"), + path( + "customers//opportunities/create/", + views.OpportunityCreateView.as_view(), + name="create_opportunity", + ), + path( + "customers//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//view/", views.LeadDetailView.as_view(), name="lead_detail" + ), + path("crm/leads/create/", views.LeadCreateView.as_view(), name="lead_create"), + path( + "crm/leads//update/", views.LeadUpdateView.as_view(), name="lead_update" + ), + path( + "crm/leads//delete/", views.LeadDeleteView.as_view(), name="lead_delete" + ), + path("crm/leads//add-note/", views.add_note_to_lead, name="add_note"), + path( + "crm/leads//add-activity/", + views.add_activity_to_lead, + name="add_activity", + ), + path( + "crm/opportunities/create/", + views.OpportunityCreateView.as_view(), + name="opportunity_create", + ), + path( + "crm/opportunities//", + views.OpportunityDetailView.as_view(), + name="opportunity_detail", + ), + path( + "crm/opportunities//edit/", + views.OpportunityUpdateView.as_view(), + name="update_opportunity", + ), + path( + "crm/opportunities/", + views.OpportunityListView.as_view(), + name="opportunity_list", + ), + path( + "crm/opportunities//delete/", + views.delete_opportunity, + name="delete_opportunity", + ), # path('crm/opportunities//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//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//', views.VendorDetailView.as_view(), name='vendor_detail'), - path('vendors/create/', views.VendorCreateView.as_view(), name='vendor_create'), - path('vendors//update/', views.VendorUpdateView.as_view(), name='vendor_update'), - path('vendors//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//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//", views.VendorDetailView.as_view(), name="vendor_detail"), + path("vendors/create/", views.VendorCreateView.as_view(), name="vendor_create"), + path( + "vendors//update/", + views.VendorUpdateView.as_view(), + name="vendor_update", + ), + path( + "vendors//delete/", + views.VendorDetailView.as_view(), + name="vendor_delete", + ), # Car URLs - path('cars/inventory/', views.CarInventory.as_view(), name='car_inventory_all'), - path('cars/inventory////', views.CarInventory.as_view(), name='car_inventory'), - path('cars/inventory/stats', views.inventory_stats_view, name='inventory_stats'), - path('cars//', views.CarDetailView.as_view(), name='car_detail'), - path('cars//update/', views.CarUpdateView.as_view(), name='car_update'), - path('cars//delete/', views.CarDeleteView.as_view(), name='car_delete'), - path('cars//finance/create/', views.CarFinanceCreateView.as_view(), name='car_finance_create'), - path('cars/finance//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//add-color/', views.CarColorCreate.as_view(), name='add_color'), - path('cars//location/add/', views.CarLocationCreateView.as_view(), name='add_car_location'), - path('cars//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////", + views.CarInventory.as_view(), + name="car_inventory", + ), + path("cars/inventory/stats", views.inventory_stats_view, name="inventory_stats"), + path("cars//", views.CarDetailView.as_view(), name="car_detail"), + path("cars//update/", views.CarUpdateView.as_view(), name="car_update"), + path("cars//delete/", views.CarDeleteView.as_view(), name="car_delete"), + path( + "cars//finance/create/", + views.CarFinanceCreateView.as_view(), + name="car_finance_create", + ), + path( + "cars/finance//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//add-color/", views.CarColorCreate.as_view(), name="add_color" + ), + path( + "cars//location/add/", + views.CarLocationCreateView.as_view(), + name="add_car_location", + ), + path( + "cars//location/update/", + views.CarLocationUpdateView.as_view(), + name="transfer", + ), + path("cars/inventory/search/", views.SearchCodeView.as_view(), name="car_search"), # path('cars//colors//update/',views.CarColorUpdateView.as_view(),name='color_update'), - - path('cars/reserve//', views.reserve_car_view, name='reserve_car'), - path('reservations//', views.manage_reservation, name='reservations'), - path('cars//add-custom-card/', views.CustomCardCreateView.as_view(), name='add_custom_card'), - + path("cars/reserve//", views.reserve_car_view, name="reserve_car"), + path( + "reservations//", + views.manage_reservation, + name="reservations", + ), + path( + "cars//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//', views.QuotationDetailView.as_view(), name='quotation_detail'), - path('sales/quotations/', views.QuotationListView.as_view(), name='quotation_list'), - path('sales/quotations//confirm/', views.confirm_quotation, name='confirm_quotation'), - path('sales/orders/detail//', views.SalesOrderDetailView.as_view(), name='order_detail'), - path('quotation//pdf/', views.download_quotation_pdf, name='quotation_pdf'), - path('generate_invoice//', views.generate_invoice, name='generate_invoice'), - path('sales/quotations//mark_quotation/', views.mark_quotation, name='mark_quotation'), - path('sales/quotations//post_quotation/', views.post_quotation, name='post_quotation'), - path('sales/quotations//invoice_detail/', views.invoice_detail, name='invoice_detail'), - path('subscriptions', views.SubscriptionPlans.as_view(), name='subscriptions'), - #Payment URLs -# path('sales/quotations//payment/', views.PaymentCreateView.as_view(), name='payment_create'), - path('sales/quotations//payment/', views.payment_create, name='payment_create'), - + path( + "sales/quotations/create/", + views.QuotationCreateView.as_view(), + name="quotation_create", + ), + path( + "sales/quotations//", + views.QuotationDetailView.as_view(), + name="quotation_detail", + ), + path("sales/quotations/", views.QuotationListView.as_view(), name="quotation_list"), + path( + "sales/quotations//confirm/", + views.confirm_quotation, + name="confirm_quotation", + ), + path( + "sales/orders/detail//", + views.SalesOrderDetailView.as_view(), + name="order_detail", + ), + path( + "quotation//pdf/", + views.download_quotation_pdf, + name="quotation_pdf", + ), + path("generate_invoice//", views.generate_invoice, name="generate_invoice"), + path( + "sales/quotations//mark_quotation/", + views.mark_quotation, + name="mark_quotation", + ), + path( + "sales/quotations//post_quotation/", + views.post_quotation, + name="post_quotation", + ), + path( + "sales/quotations//invoice_detail/", + views.InvoiceDetailView.as_view(), + name="invoice_detail", + ), + path("subscriptions", views.SubscriptionPlans.as_view(), name="subscriptions"), + # Payment URLs + # path('sales/quotations//payment/', views.PaymentCreateView.as_view(), name='payment_create'), + path( + "sales/quotations//payment/", + views.PaymentCreateView, + name="payment_create", + ), # Users URLs - path('user/create/', views.UserCreateView.as_view(), name='user_create'), - path('user//update/', views.UserUpdateView.as_view(), name='user_update'), - path('user//', views.UserDetailView.as_view(), name='user_detail'), - path('user/', views.UserListView.as_view(), name='user_list'), - path('user//confirm/', views.UserDeleteview, name='user_delete'), + path("user/create/", views.UserCreateView.as_view(), name="user_create"), + path("user//update/", views.UserUpdateView.as_view(), name="user_update"), + path("user//", views.UserDetailView.as_view(), name="user_detail"), + path("user/", views.UserListView.as_view(), name="user_list"), + path("user//confirm/", views.UserDeleteview, name="user_delete"), # Organization URLs - path('organizations/', views.OrganizationListView.as_view(), name='organization_list'), - path('organizations//', views.OrganizationDetailView.as_view(), name='organization_detail'), - path('organizations/create/', views.OrganizationCreateView.as_view(), name='organization_create'), - path('organizations//update/', views.OrganizationUpdateView.as_view(), name='organization_update'), - path('organizations//delete/', views.OrganizationDeleteView.as_view(), name='organization_delete'), - + path( + "organizations/", views.OrganizationListView.as_view(), name="organization_list" + ), + path( + "organizations//", + views.OrganizationDetailView.as_view(), + name="organization_detail", + ), + path( + "organizations/create/", + views.OrganizationCreateView.as_view(), + name="organization_create", + ), + path( + "organizations//update/", + views.OrganizationUpdateView.as_view(), + name="organization_update", + ), + path( + "organizations//delete/", + views.OrganizationDeleteView.as_view(), + name="organization_delete", + ), # Representative URLs - path('representatives/', views.RepresentativeListView.as_view(), name='representative_list'), - path('representatives//', views.RepresentativeDetailView.as_view(), name='representative_detail'), - path('representatives/create/', views.RepresentativeCreateView.as_view(), name='representative_create'), - path('representatives//update/', views.RepresentativeUpdateView.as_view(), name='representative_update'), - path('representatives//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//', views.BankAccountDetailView.as_view(), name='bank_account_detail'), - path('bank_accounts/create/', views.BankAccountCreateView.as_view(), name='bank_account_create'), - path('bank_accounts//update/', views.BankAccountUpdateView.as_view(), name='bank_account_update'), - path('bank_accounts//delete/', views.bank_account_delete, name='bank_account_delete'), + path( + "representatives/", + views.RepresentativeListView.as_view(), + name="representative_list", + ), + path( + "representatives//", + views.RepresentativeDetailView.as_view(), + name="representative_detail", + ), + path( + "representatives/create/", + views.RepresentativeCreateView.as_view(), + name="representative_create", + ), + path( + "representatives//update/", + views.RepresentativeUpdateView.as_view(), + name="representative_update", + ), + path( + "representatives//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//", + views.BankAccountDetailView.as_view(), + name="bank_account_detail", + ), + path( + "bank_accounts/create/", + views.BankAccountCreateView.as_view(), + name="bank_account_create", + ), + path( + "bank_accounts//update/", + views.BankAccountUpdateView.as_view(), + name="bank_account_update", + ), + path( + "bank_accounts//delete/", + views.bank_account_delete, + name="bank_account_delete", + ), # Account - path('coa_accounts/', views.AccountListView.as_view(), name='account_list'), - path('coa_accounts//', views.AccountDetailView.as_view(), name='account_detail'), - path('coa_accounts/create/', views.AccountCreateView.as_view(), name='account_create'), - path('coa_accounts//update/', views.AccountUpdateView.as_view(), name='account_update'), - path('coa_accounts//delete/', views.account_delete, name='account_delete'), + path("coa_accounts/", views.AccountListView.as_view(), name="account_list"), + path( + "coa_accounts//", + views.AccountDetailView.as_view(), + name="account_detail", + ), + path( + "coa_accounts/create/", views.AccountCreateView.as_view(), name="account_create" + ), + path( + "coa_accounts//update/", + views.AccountUpdateView.as_view(), + name="account_update", + ), + path("coa_accounts//delete/", views.account_delete, name="account_delete"), # Estimate - path('sales/estimates/', views.EstimateListView.as_view(), name='estimate_list'), - path('sales/estimates//', views.EstimateDetailView.as_view(), name='estimate_detail'), - path('sales/estimates/create/', views.create_estimate, name='estimate_create'), - path('sales/estimates//estimate_mark_as/', views.estimate_mark_as, name='estimate_mark_as'), - path('sales/estimates//preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), - path('sales/estimates//payment_request/', views.PaymentRequest.as_view(), name='payment_request'), - path('sales/estimates//send_email', views.send_email_view, name='send_email'), + path("sales/estimates/", views.EstimateListView.as_view(), name="estimate_list"), + path( + "sales/estimates//", + views.EstimateDetailView.as_view(), + name="estimate_detail", + ), + path("sales/estimates/create/", views.estimate_create, name="estimate_create"), + path( + "sales/estimates//estimate_mark_as/", + views.estimate_mark_as, + name="estimate_mark_as", + ), + path( + "sales/estimates//preview/", + views.EstimatePreviewView.as_view(), + name="estimate_preview", + ), + path( + "sales/estimates//payment_request/", + views.PaymentRequest.as_view(), + name="payment_request", + ), + path( + "sales/estimates//send_email", views.send_email_view, name="send_email" + ), # Invoice - path('sales/invoices/', views.InvoiceListView.as_view(), name='invoice_list'), - path('sales/invoices//create/', views.invoice_create, name='invoice_create'), - path('sales/invoices//', views.InvoiceDetailView.as_view(), name='invoice_detail'), - path('sales/invoices//preview/', views.InvoicePreviewView.as_view(), name='invoice_preview'), - path('sales/invoices//invoice_mark_as/', views.invoice_mark_as, name='invoice_mark_as'), - path('sales/invoices//draft_invoice_update/', views.DraftInvoiceModelUpdateFormView.as_view(), name='draft_invoice_update'), - path('sales/invoices//approved_invoice_update/', views.ApprovedInvoiceModelUpdateFormView.as_view(), name='approved_invoice_update'), - path('sales/invoices//paid_invoice_update/', views.PaidInvoiceModelUpdateFormView.as_view(), name='paid_invoice_update'), - - # path('sales/estimates//preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), - # path('send_email/', views.send_email, name='send_email'), - - #Payment - path('sales/payments/', views.PaymentListView, name='payment_list'), - path('sales/payments//create/', views.PaymentCreateView, name='payment_create'), - path('sales/payments/create/', views.PaymentCreateView, name='payment_create'), - path('sales/payments//payment_details/', views.PaymentDetailView, name='payment_details'), - path('sales/payments//payment_mark_as_paid/', views.payment_mark_as_paid, name='payment_mark_as_paid'), - # path('sales/payments//update/', views.JournalEntryUpdateView.as_view(), name='payment_update'), - # path('sales/payments//delete/', views.JournalEntryDeleteView.as_view(), name='payment_delete'), - # path('sales/payments//preview/', views.JournalEntryPreviewView.as_view(), name='payment_preview'), - # # Journal - # path('sales/journal//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//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//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//update/', views.ItemExpenseUpdateView.as_view(), name='item_expense_update'), + path("sales/invoices/", views.InvoiceListView.as_view(), name="invoice_list"), + path( + "sales/invoices//create/", views.invoice_create, name="invoice_create" + ), + path( + "sales/invoices//", + views.InvoiceDetailView.as_view(), + name="invoice_detail", + ), + path( + "sales/invoices//preview/", + views.InvoicePreviewView.as_view(), + name="invoice_preview", + ), + path( + "sales/invoices//invoice_mark_as/", + views.invoice_mark_as, + name="invoice_mark_as", + ), + path( + "sales/invoices//draft_invoice_update/", + views.DraftInvoiceModelUpdateFormView.as_view(), + name="draft_invoice_update", + ), + path( + "sales/invoices//approved_invoice_update/", + views.ApprovedInvoiceModelUpdateFormView.as_view(), + name="approved_invoice_update", + ), + path( + "sales/invoices//paid_invoice_update/", + views.PaidInvoiceModelUpdateFormView.as_view(), + name="paid_invoice_update", + ), + # path('sales/estimates//preview/', views.EstimatePreviewView.as_view(), name='estimate_preview'), + # path('send_email/', views.send_email, name='send_email'), + # Payment + path("sales/payments/", views.PaymentListView, name="payment_list"), + path( + "sales/payments//create/", + views.PaymentCreateView, + name="payment_create", + ), + path("sales/payments/create/", views.PaymentCreateView, name="payment_create"), + path( + "sales/payments//payment_details/", + views.PaymentDetailView, + name="payment_details", + ), + path( + "sales/payments//payment_mark_as_paid/", + views.payment_mark_as_paid, + name="payment_mark_as_paid", + ), + # path('sales/payments//update/', views.JournalEntryUpdateView.as_view(), name='payment_update'), + # path('sales/payments//delete/', views.JournalEntryDeleteView.as_view(), name='payment_delete'), + # path('sales/payments//preview/', views.JournalEntryPreviewView.as_view(), name='payment_preview'), + # # Journal + # path('sales/journal//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//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//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//bill_detail/", + views.BillDetailView.as_view(), + name="bill_detail", + ), + path("items/bills//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" diff --git a/inventory/utilities/ledgers.py b/inventory/utilities/ledgers.py new file mode 100644 index 00000000..2f8772a9 --- /dev/null +++ b/inventory/utilities/ledgers.py @@ -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 \ No newline at end of file diff --git a/inventory/views.py b/inventory/views.py index 23f17d27..704c968f 100644 --- a/inventory/views.py +++ b/inventory/views.py @@ -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): diff --git a/templates/ledger/bills/bill_detail.html b/templates/ledger/bills/bill_detail.html index 07798133..1b78e795 100644 --- a/templates/ledger/bills/bill_detail.html +++ b/templates/ledger/bills/bill_detail.html @@ -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 %} - -