update po
This commit is contained in:
parent
6e0808402a
commit
90fea4d256
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.1 on 2025-06-12 16:25
|
||||
# Generated by Django 5.2.1 on 2025-06-12 14:22
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
@ -20,6 +20,9 @@ from django_ledger.forms.bill import BillModelCreateForm as BillModelCreateFormB
|
||||
from django_ledger.forms.journal_entry import (
|
||||
JournalEntryModelCreateForm as JournalEntryModelCreateFormBase,
|
||||
)
|
||||
import csv
|
||||
from io import TextIOWrapper
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import (
|
||||
Dealer,
|
||||
@ -1928,3 +1931,66 @@ class ItemInventoryForm(forms.Form):
|
||||
label=_("Trim"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
#####################################################################
|
||||
|
||||
class CSVUploadForm(forms.Form):
|
||||
dealer = forms.ModelChoiceField(
|
||||
queryset=Dealer.objects.all(),
|
||||
label=_('Dealer'),
|
||||
widget=forms.HiddenInput()
|
||||
)
|
||||
vendor = forms.ModelChoiceField(
|
||||
queryset=Vendor.objects.all(),
|
||||
label=_('Vendor'),
|
||||
widget=forms.Select(attrs={'class': 'form-select'}),
|
||||
required=True
|
||||
)
|
||||
year = forms.IntegerField(
|
||||
label=_('Year'),
|
||||
widget=forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
required=True
|
||||
)
|
||||
exterior = forms.ModelChoiceField(
|
||||
queryset=ExteriorColors.objects.all(),
|
||||
label=_('Exterior Color'),
|
||||
widget=forms.RadioSelect(attrs={'class': 'form-select'}),
|
||||
required=True
|
||||
)
|
||||
interior = forms.ModelChoiceField(
|
||||
queryset=InteriorColors.objects.all(),
|
||||
label=_('Interior Color'),
|
||||
widget=forms.RadioSelect(attrs={'class': 'form-select'}),
|
||||
required=True
|
||||
)
|
||||
receiving_date = forms.DateField(
|
||||
label=_('Receiving Date'),
|
||||
widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||
required=True
|
||||
)
|
||||
|
||||
def clean_csv_file(self):
|
||||
csv_file = self.cleaned_data['csv_file']
|
||||
if not csv_file.name.endswith('.csv'):
|
||||
raise forms.ValidationError(_('File is not a CSV file'))
|
||||
|
||||
# Read and validate CSV structure
|
||||
try:
|
||||
csv_data = TextIOWrapper(csv_file.file, encoding='utf-8')
|
||||
reader = csv.DictReader(csv_data)
|
||||
|
||||
required_fields = ['vin', 'make', 'model', 'year']
|
||||
if not all(field in reader.fieldnames for field in required_fields):
|
||||
missing = set(required_fields) - set(reader.fieldnames)
|
||||
raise forms.ValidationError(
|
||||
_('CSV is missing required columns: %(missing)s'),
|
||||
params={'missing': ', '.join(missing)}
|
||||
)
|
||||
except Exception as e:
|
||||
raise forms.ValidationError(_('Error reading CSV file: %(error)s') % {'error': str(e)})
|
||||
|
||||
# Reset file pointer for later processing
|
||||
csv_file.file.seek(0)
|
||||
return csv_file
|
||||
@ -712,7 +712,9 @@ class Car(Base):
|
||||
.filter(name=f"Cogs:{self.id_car_make.name}")
|
||||
.first()
|
||||
)
|
||||
|
||||
def add_colors(self,exterior,interior):
|
||||
self.colors = CarColors.objects.create(car=self,exterior=exterior,interior=interior)
|
||||
self.save()
|
||||
|
||||
class CarTransfer(models.Model):
|
||||
car = models.ForeignKey(
|
||||
|
||||
@ -254,6 +254,8 @@ urlpatterns = [
|
||||
name="vendor_delete",
|
||||
),
|
||||
# Car URLs
|
||||
path('cars/upload_cars/', views.upload_cars, name='upload_cars'),
|
||||
path('cars/<uuid:po_pk>/upload_cars/', views.upload_cars, name='upload_cars'),
|
||||
path("cars/add/", views.CarCreateView.as_view(), name="car_add"),
|
||||
path("cars/inventory/", views.CarInventory.as_view(), name="car_inventory_all"),
|
||||
path(
|
||||
@ -696,13 +698,13 @@ path(
|
||||
path("items/bills/", views.BillListView.as_view(), name="bill_list"),
|
||||
# path("items/bills/create/", views.BillModelCreateViewView.as_view(), name="bill_create"),
|
||||
path('items/bills/<slug:entity_slug>/create/',
|
||||
views.BillModelCreateViewView.as_view(),
|
||||
views.BillModelCreateView.as_view(),
|
||||
name='bill-create'),
|
||||
path('items/bills/<slug:entity_slug>/create/purchase-order/<uuid:po_pk>/',
|
||||
views.BillModelCreateViewView.as_view(for_purchase_order=True),
|
||||
views.BillModelCreateView.as_view(for_purchase_order=True),
|
||||
name='bill-create-po'),
|
||||
path('items/bills/<slug:entity_slug>/create/estimate/<uuid:ce_pk>/',
|
||||
views.BillModelCreateViewView.as_view(for_estimate=True),
|
||||
views.BillModelCreateView.as_view(for_estimate=True),
|
||||
name='bill-create-estimate'),
|
||||
path('items/bills/<slug:entity_slug>/detail/<uuid:bill_pk>/',
|
||||
views.BillModelDetailViewView.as_view(),
|
||||
@ -769,7 +771,6 @@ path(
|
||||
name="bill_mark_as_paid",
|
||||
),
|
||||
|
||||
|
||||
# orders
|
||||
path("orders/", views.OrderListView.as_view(), name="order_list_view"),
|
||||
|
||||
@ -858,7 +859,7 @@ path(
|
||||
path('management/<str:content_type>/<slug:slug>/permenant_delete_account/', views.permenant_delete_account, name='permenant_delete_account'),
|
||||
path('management/audit_log_dashboard/', views.AuditLogDashboardView, name='audit_log_dashboard'),
|
||||
|
||||
|
||||
|
||||
#########
|
||||
# Purchase Order
|
||||
path('purchase_orders/', views.PurchaseOrderListView.as_view(), name='purchase_order_list'),
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
# Standard
|
||||
import os
|
||||
import io
|
||||
import csv
|
||||
import cv2
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from time import sleep
|
||||
import tempfile
|
||||
import numpy as np
|
||||
from time import sleep
|
||||
# from rich import print
|
||||
from random import randint
|
||||
from decimal import Decimal
|
||||
from io import TextIOWrapper
|
||||
from django.apps import apps
|
||||
from datetime import datetime
|
||||
from calendar import month_name
|
||||
from pyzbar.pyzbar import decode
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
@ -17,6 +22,7 @@ from urllib.parse import urlparse, urlunparse
|
||||
from inventory.models import Status as LeadStatus
|
||||
from django.db import IntegrityError
|
||||
from background_task.models import Task
|
||||
from django.views.generic import FormView
|
||||
from django.db.models.deletion import RestrictedError
|
||||
from django.http.response import StreamingHttpResponse
|
||||
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
||||
@ -26,7 +32,7 @@ from django.db.models import Q
|
||||
from django.conf import settings
|
||||
from django.db.models import Func
|
||||
from django.contrib import messages
|
||||
from django.http import Http404, HttpResponseNotFound, HttpResponseRedirect, JsonResponse, HttpResponseForbidden
|
||||
from django.http import Http404, HttpResponseBadRequest, HttpResponseNotFound, HttpResponseRedirect, JsonResponse, HttpResponseForbidden
|
||||
from django.forms import HiddenInput, ValidationError
|
||||
from django.shortcuts import HttpResponse
|
||||
|
||||
@ -85,15 +91,17 @@ from django_ledger.forms.bank_account import (
|
||||
BankAccountUpdateForm,
|
||||
)
|
||||
from django_ledger.views.bill import (
|
||||
BillModelCreateView,
|
||||
# BillModelCreateView,
|
||||
BillModelDetailView,
|
||||
BillModelUpdateView,
|
||||
BaseBillActionView as BaseBillActionViewBase,
|
||||
BillModelModelBaseView
|
||||
)
|
||||
from django_ledger.forms.bill import (
|
||||
ApprovedBillModelUpdateForm,
|
||||
InReviewBillModelUpdateForm,
|
||||
get_bill_itemtxs_formset_class,
|
||||
BillModelCreateForm
|
||||
|
||||
)
|
||||
from django_ledger.forms.invoice import (
|
||||
@ -980,23 +988,23 @@ class CarColorsUpdateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessM
|
||||
template_name = "inventory/add_colors.html"
|
||||
success_message = _("Car Colors details updated successfully")
|
||||
permission_required = ["inventory.change_car"]
|
||||
|
||||
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
"""
|
||||
Retrieves the CarColors instance associated with the Car slug from the URL.
|
||||
This ensures we are updating the colors for the correct car.
|
||||
"""
|
||||
|
||||
|
||||
# Get the car_slug from the URL keywords arguments
|
||||
slug = self.kwargs.get('slug')
|
||||
|
||||
|
||||
# If no car_slug is provided, it's an invalid request
|
||||
if not slug:
|
||||
# You might want to raise Http404 or a more specific error here
|
||||
raise ValueError("Car slug is required to identify the colors to update.")
|
||||
|
||||
|
||||
|
||||
return get_object_or_404(models.CarColors, car__slug=slug)
|
||||
|
||||
def get_success_url(self):
|
||||
@ -1008,7 +1016,7 @@ class CarColorsUpdateView( LoginRequiredMixin, PermissionRequiredMixin, SuccessM
|
||||
return reverse("car_detail", kwargs={"slug": self.object.car.slug})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
"""
|
||||
Adds the related Car object to the template context.
|
||||
"""
|
||||
context = super().get_context_data(**kwargs)
|
||||
@ -6118,6 +6126,7 @@ class BillDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
permission_required = ["django_ledger.view_billmodel"]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
dealer = get_user_type(self.request)
|
||||
bill = kwargs.get("object")
|
||||
if bill.get_itemtxs_data():
|
||||
txs = bill.get_itemtxs_data()[0]
|
||||
@ -6133,6 +6142,7 @@ class BillDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
|
||||
|
||||
kwargs["transactions"] = transactions
|
||||
kwargs["grand_total"] = grand_total
|
||||
kwargs["entity"] = dealer.entity
|
||||
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
@ -6307,13 +6317,299 @@ def bill_mark_as_paid(request, pk):
|
||||
return redirect("bill_detail", pk=bill.pk)
|
||||
|
||||
|
||||
# class BillModelCreateViewView(Create):
|
||||
# template_name = 'bill/bill_create.html'
|
||||
|
||||
class BillModelCreateViewView(BillModelCreateView):
|
||||
# def get_context_data(self, **kwargs):
|
||||
# context = super().get_context_data(**kwargs)
|
||||
# context["entity"] = get_user_type(self.request).entity
|
||||
# return context
|
||||
|
||||
|
||||
# def post(self, request, *args, **kwargs):
|
||||
# """
|
||||
# Completely override the post method to ensure our form handling is called
|
||||
# """
|
||||
# print("Custom post method called") # Debugging
|
||||
# form = self.get_form()
|
||||
# if form.is_valid():
|
||||
# return self.custom_form_valid(form)
|
||||
# else:
|
||||
# return self.form_invalid(form)
|
||||
|
||||
# def custom_form_valid(self, form):
|
||||
# """
|
||||
# Our own form_valid implementation that will definitely be called
|
||||
# """
|
||||
# print("Custom form_valid called") # Debugging
|
||||
|
||||
# # Handle PO case
|
||||
# if self.for_purchase_order:
|
||||
# print("Handling PO case") # Debugging
|
||||
# return self.handle_po_case(form)
|
||||
|
||||
# # Handle Estimate case
|
||||
# if self.for_estimate:
|
||||
# print("Handling Estimate case") # Debugging
|
||||
# return self.handle_estimate_case(form)
|
||||
|
||||
# # Default case
|
||||
# print("Handling default case") # Debugging
|
||||
# return self.handle_default_case(form)
|
||||
|
||||
# def handle_po_case(self, form):
|
||||
# """Special handling for purchase orders"""
|
||||
# po_pk = self.kwargs['po_pk']
|
||||
# item_uuids = self.request.GET.get('item_uuids', '').split(',')
|
||||
|
||||
# if not item_uuids or not all(item_uuids):
|
||||
# return HttpResponseBadRequest()
|
||||
|
||||
# # Create bill
|
||||
# bill_model = form.save(commit=False)
|
||||
# ledger_model, bill_model = bill_model.configure(
|
||||
# entity_slug=self.AUTHORIZED_ENTITY_MODEL,
|
||||
# commit_ledger=True
|
||||
# )
|
||||
|
||||
# # Get PO
|
||||
# po_qs = PurchaseOrderModel.objects.for_entity(
|
||||
# entity_slug=self.kwargs['entity_slug'],
|
||||
# user_model=self.request.user
|
||||
# )
|
||||
# po_model = get_object_or_404(po_qs, uuid__exact=po_pk)
|
||||
|
||||
# # Validate
|
||||
# try:
|
||||
# bill_model.can_bind_po(po_model, raise_exception=True)
|
||||
# except ValidationError as e:
|
||||
# messages.error(self.request, e.message)
|
||||
# return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
# # Update models
|
||||
# po_model_items_qs = po_model.itemtransactionmodel_set.filter(uuid__in=item_uuids)
|
||||
# if po_model.is_contract_bound():
|
||||
# bill_model.ce_model_id = po_model.ce_model_id
|
||||
|
||||
# bill_model.update_amount_due()
|
||||
# bill_model.get_state(commit=True)
|
||||
# bill_model.clean()
|
||||
# bill_model.save()
|
||||
# po_model_items_qs.update(bill_model=bill_model)
|
||||
|
||||
# # Redirect to our custom URL
|
||||
# return HttpResponseRedirect(
|
||||
# reverse('purchase_order_update',
|
||||
# kwargs={
|
||||
# 'entity_slug': self.kwargs['entity_slug'],
|
||||
# 'po_pk': po_pk
|
||||
# })
|
||||
# )
|
||||
|
||||
# def handle_estimate_case(self, form):
|
||||
# """Special handling for estimates"""
|
||||
# bill_model = form.save(commit=False)
|
||||
# ledger_model, bill_model = bill_model.configure(
|
||||
# entity_slug=self.AUTHORIZED_ENTITY_MODEL,
|
||||
# commit_ledger=True
|
||||
# )
|
||||
|
||||
# ce_pk = self.kwargs['ce_pk']
|
||||
# estimate_model_qs = EstimateModel.objects.for_entity(
|
||||
# entity_slug=self.kwargs['entity_slug'],
|
||||
# user_model=self.request.user
|
||||
# )
|
||||
# estimate_model = get_object_or_404(estimate_model_qs, uuid__exact=ce_pk)
|
||||
# bill_model.bind_estimate(estimate_model=estimate_model, commit=True)
|
||||
|
||||
# # Redirect to our custom URL
|
||||
# return HttpResponseRedirect(
|
||||
# reverse('estimate_detail',
|
||||
# kwargs={
|
||||
# 'pk': ce_pk
|
||||
# })
|
||||
# )
|
||||
|
||||
# def handle_default_case(self, form):
|
||||
# """Default form handling"""
|
||||
# bill_model = form.save(commit=False)
|
||||
# ledger_model, bill_model = bill_model.configure(
|
||||
# entity_slug=self.AUTHORIZED_ENTITY_MODEL,
|
||||
# commit_ledger=True
|
||||
# )
|
||||
# bill_model.save()
|
||||
|
||||
# # Redirect to our custom URL
|
||||
# return HttpResponseRedirect(
|
||||
# reverse('bill-detail',
|
||||
# kwargs={
|
||||
# 'entity_slug': self.kwargs['entity_slug'],
|
||||
# 'bill_pk': bill_model.uuid
|
||||
# })
|
||||
# )
|
||||
class BillModelCreateView(CreateView):
|
||||
template_name = 'bill/bill_create.html'
|
||||
PAGE_TITLE = _('Create Bill')
|
||||
extra_context = {
|
||||
'page_title': PAGE_TITLE,
|
||||
'header_title': PAGE_TITLE,
|
||||
'header_subtitle_icon': 'uil:bill'
|
||||
}
|
||||
for_purchase_order = False
|
||||
for_estimate = False
|
||||
|
||||
def get(self, request, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
if self.for_estimate and 'ce_pk' in self.kwargs:
|
||||
estimate_qs = EstimateModel.objects.for_entity(
|
||||
entity_slug=self.kwargs['entity_slug'],
|
||||
user_model=self.request.user
|
||||
)
|
||||
estimate_model: EstimateModel = get_object_or_404(estimate_qs, uuid__exact=self.kwargs['ce_pk'])
|
||||
if not estimate_model.can_bind():
|
||||
return HttpResponseNotFound('404 Not Found')
|
||||
return super(BillModelCreateView, self).get(request, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["entity"] = get_user_type(self.request).entity
|
||||
context = super(BillModelCreateView, self).get_context_data(**kwargs)
|
||||
|
||||
if self.for_purchase_order:
|
||||
po_pk = self.kwargs['po_pk']
|
||||
po_item_uuids_qry_param = self.request.GET.get('item_uuids')
|
||||
if po_item_uuids_qry_param:
|
||||
try:
|
||||
po_item_uuids = po_item_uuids_qry_param.split(',')
|
||||
except:
|
||||
return HttpResponseBadRequest()
|
||||
else:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
po_qs = PurchaseOrderModel.objects.for_entity(
|
||||
entity_slug=self.kwargs['entity_slug'],
|
||||
user_model=self.request.user
|
||||
).prefetch_related('itemtransactionmodel_set')
|
||||
po_model: PurchaseOrderModel = get_object_or_404(po_qs, uuid__exact=po_pk)
|
||||
po_itemtxs_qs = po_model.itemtransactionmodel_set.filter(
|
||||
bill_model__isnull=True,
|
||||
uuid__in=po_item_uuids
|
||||
)
|
||||
context['po_model'] = po_model
|
||||
context['po_itemtxs_qs'] = po_itemtxs_qs
|
||||
form_action = reverse('bill-create-po',
|
||||
kwargs={
|
||||
'entity_slug': self.kwargs['entity_slug'],
|
||||
'po_pk': po_model.uuid
|
||||
}) + f'?item_uuids={po_item_uuids_qry_param}'
|
||||
elif self.for_estimate:
|
||||
estimate_qs = EstimateModel.objects.for_entity(
|
||||
entity_slug=self.kwargs['entity_slug'],
|
||||
user_model=self.request.user
|
||||
)
|
||||
estimate_uuid = self.kwargs['ce_pk']
|
||||
estimate_model: EstimateModel = get_object_or_404(estimate_qs, uuid__exact=estimate_uuid)
|
||||
form_action = reverse('bill-create-estimate',
|
||||
kwargs={
|
||||
'entity_slug': self.kwargs['entity_slug'],
|
||||
'ce_pk': estimate_model.uuid
|
||||
})
|
||||
else:
|
||||
form_action = reverse('bill-create',
|
||||
kwargs={
|
||||
'entity_slug': self.kwargs['entity_slug'],
|
||||
})
|
||||
context['form_action_url'] = form_action
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
return {
|
||||
'date_draft': get_localdate()
|
||||
}
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
dealer = get_user_type(self.request)
|
||||
return BillModelCreateForm(
|
||||
entity_model=dealer.entity,
|
||||
**self.get_form_kwargs()
|
||||
)
|
||||
|
||||
def form_valid(self, form):
|
||||
dealer = get_user_type(self.request)
|
||||
bill_model: BillModel = form.save(commit=False)
|
||||
ledger_model, bill_model = bill_model.configure(
|
||||
entity_slug=dealer.entity.slug,
|
||||
user_model=self.request.user,
|
||||
commit_ledger=True
|
||||
)
|
||||
|
||||
if self.for_estimate:
|
||||
ce_pk = self.kwargs['ce_pk']
|
||||
estimate_model_qs = EstimateModel.objects.for_entity(
|
||||
entity_slug=self.kwargs['entity_slug'],
|
||||
user_model=self.request.user)
|
||||
|
||||
estimate_model = get_object_or_404(estimate_model_qs, uuid__exact=ce_pk)
|
||||
bill_model.bind_estimate(estimate_model=estimate_model, commit=False)
|
||||
|
||||
elif self.for_purchase_order:
|
||||
po_pk = self.kwargs['po_pk']
|
||||
item_uuids = self.request.GET.get('item_uuids')
|
||||
if not item_uuids:
|
||||
return HttpResponseBadRequest()
|
||||
item_uuids = item_uuids.split(',')
|
||||
po_qs = PurchaseOrderModel.objects.for_entity(
|
||||
entity_slug=self.kwargs['entity_slug'],
|
||||
user_model=self.request.user
|
||||
)
|
||||
po_model: PurchaseOrderModel = get_object_or_404(po_qs, uuid__exact=po_pk)
|
||||
|
||||
try:
|
||||
bill_model.can_bind_po(po_model, raise_exception=True)
|
||||
except ValidationError as e:
|
||||
messages.add_message(self.request,
|
||||
message=e.message,
|
||||
level=messages.ERROR,
|
||||
extra_tags='is-danger')
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
po_model_items_qs = po_model.itemtransactionmodel_set.filter(uuid__in=item_uuids)
|
||||
|
||||
if po_model.is_contract_bound():
|
||||
bill_model.ce_model_id = po_model.ce_model_id
|
||||
|
||||
bill_model.update_amount_due()
|
||||
bill_model.get_state(commit=True)
|
||||
bill_model.clean()
|
||||
bill_model.save()
|
||||
po_model_items_qs.update(bill_model=bill_model)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
return super(BillModelCreateView, self).form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
entity_slug = self.kwargs['entity_slug']
|
||||
if self.for_purchase_order:
|
||||
po_pk = self.kwargs['po_pk']
|
||||
return reverse('purchase_order_update',
|
||||
kwargs={
|
||||
'entity_slug': entity_slug,
|
||||
'po_pk': po_pk
|
||||
})
|
||||
elif self.for_estimate:
|
||||
return reverse('customer-estimate-detail',
|
||||
kwargs={
|
||||
'entity_slug': entity_slug,
|
||||
'ce_pk': self.kwargs['ce_pk']
|
||||
})
|
||||
bill_model: BillModel = self.object
|
||||
return reverse('bill-detail',
|
||||
kwargs={
|
||||
'entity_slug': entity_slug,
|
||||
'bill_pk': bill_model.uuid
|
||||
})
|
||||
|
||||
|
||||
class BillModelDetailViewView(BillModelDetailView):
|
||||
template_name = 'bill/bill_detail.html'
|
||||
class BillModelUpdateViewView(BillModelUpdateView):
|
||||
@ -8385,7 +8681,7 @@ def AuditLogDashboardView(request):
|
||||
page_obj = paginator.page(1)
|
||||
except EmptyPage:
|
||||
page_obj = paginator.page(paginator.num_pages)
|
||||
|
||||
|
||||
|
||||
elif q == 'loginEvents':
|
||||
template_name = 'admin_management/auth_logs.html'
|
||||
@ -8408,7 +8704,7 @@ def AuditLogDashboardView(request):
|
||||
|
||||
# 1. Paginate the raw QuerySet FIRST
|
||||
paginator = Paginator(model_events_queryset, logs_per_page)
|
||||
|
||||
|
||||
try:
|
||||
# Get the page object, which contains only the raw QuerySet objects for the current page
|
||||
page_obj_raw = paginator.page(current_pagination_page)
|
||||
@ -8462,18 +8758,18 @@ def AuditLogDashboardView(request):
|
||||
'new': 'Invalid JSON in changed_fields'
|
||||
})
|
||||
processed_model_events_for_page.append(event_data)
|
||||
|
||||
|
||||
# 3. Replace the object_list of the original page_obj with the processed data
|
||||
# This keeps all pagination properties (has_next, number, etc.) intact.
|
||||
page_obj_raw.object_list = processed_model_events_for_page
|
||||
page_obj = page_obj_raw # This will be passed to the context
|
||||
|
||||
|
||||
# Pass the final page object to the context
|
||||
context['page_obj'] = page_obj
|
||||
|
||||
return render(request, template_name, context)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def activate_account(request, content_type, slug):
|
||||
@ -8533,6 +8829,7 @@ def PurchaseOrderCreateView(request):
|
||||
return render(request, "purchase_orders/po_form.html", {"form": form})
|
||||
|
||||
def InventoryItemCreateView(request):
|
||||
for_po = request.GET.get('for_po')
|
||||
dealer = get_user_type(request)
|
||||
entity = dealer.entity
|
||||
coa = entity.get_default_coa()
|
||||
@ -8552,13 +8849,16 @@ def InventoryItemCreateView(request):
|
||||
model = request.POST.get("model")
|
||||
serie = request.POST.get("serie")
|
||||
trim = request.POST.get("trim")
|
||||
year = request.POST.get("year")
|
||||
exterior = models.ExteriorColors.objects.get(pk=request.POST.get("exterior"))
|
||||
interior = models.InteriorColors.objects.get(pk=request.POST.get("interior"))
|
||||
|
||||
make_name = models.CarMake.objects.get(pk=make)
|
||||
model_name = models.CarModel.objects.get(pk=model)
|
||||
serie_name = models.CarSerie.objects.get(pk=serie)
|
||||
trim_name = models.CarTrim.objects.get(pk=trim)
|
||||
|
||||
inventory_name = f"{make_name.name} - {model_name.name} - {serie_name.name} - {trim_name.name}"
|
||||
inventory_name = f"{make_name.name} - {model_name.name} - {serie_name.name} - {trim_name.name} - {year} - {exterior.name} - {interior.name}"
|
||||
uom = entity.get_uom_all().get(name='Unit')
|
||||
entity.create_item_inventory(
|
||||
name=inventory_name,
|
||||
@ -8569,6 +8869,10 @@ def InventoryItemCreateView(request):
|
||||
)
|
||||
messages.success(request, _("Inventory item created successfully"))
|
||||
return redirect('purchase_order_list')
|
||||
if for_po:
|
||||
form = forms.CSVUploadForm()
|
||||
context = {"make_data":models.CarMake.objects.all(),"inventory_accounts":inventory_accounts,"cogs_accounts":cogs_accounts,"form":form}
|
||||
return render(request,'purchase_orders/car_inventory_item_form.html',context)
|
||||
return render(request,'purchase_orders/inventory_item_form.html',{"make_data":models.CarMake.objects.all(),"inventory_accounts":inventory_accounts,"cogs_accounts":cogs_accounts})
|
||||
|
||||
|
||||
@ -8878,4 +9182,64 @@ class BillModelActionUnlockLedgerView(BaseBillActionView):
|
||||
|
||||
|
||||
class BillModelActionForceMigrateView(BaseBillActionView):
|
||||
action_name = 'migrate_state'
|
||||
action_name = 'migrate_state'
|
||||
|
||||
###############################################################
|
||||
###############################################################
|
||||
|
||||
def upload_cars(request,po_pk=None):
|
||||
dealer = get_user_type(request)
|
||||
|
||||
if request.method == 'POST':
|
||||
csv_file = request.FILES.get('csv_file')
|
||||
year = request.POST.get("year")
|
||||
receiving_date = datetime.strptime(request.POST.get("receiving_date"), "%Y-%m-%d")
|
||||
receiving_date = timezone.make_aware(receiving_date)
|
||||
|
||||
try:
|
||||
make = models.CarMake.objects.get(pk=request.POST.get("make"))
|
||||
model = models.CarModel.objects.get(pk=request.POST.get("model"))
|
||||
serie = models.CarSerie.objects.get(pk=request.POST.get("serie"))
|
||||
trim = models.CarTrim.objects.get(pk=request.POST.get("trim"))
|
||||
vendor = models.Vendor.objects.get(pk=request.POST.get("vendor"))
|
||||
exterior = models.ExteriorColors.objects.get(pk=request.POST.get("exterior"))
|
||||
interior = models.InteriorColors.objects.get(pk=request.POST.get("interior"))
|
||||
except Exception as e:
|
||||
messages.error(request, f"Error processing CSV: {str(e)}")
|
||||
if po_pk:
|
||||
return redirect('upload_cars',po_pk=po_pk)
|
||||
else:
|
||||
return redirect('upload_cars')
|
||||
if not csv_file.name.endswith('.csv'):
|
||||
messages.error(request, "Please upload a CSV file")
|
||||
return redirect('upload_cars')
|
||||
try:
|
||||
# Read the file content
|
||||
file_content = csv_file.read().decode('utf-8')
|
||||
csv_data = io.StringIO(file_content)
|
||||
reader = csv.DictReader(csv_data)
|
||||
cars_created = 0
|
||||
for row in reader:
|
||||
car = models.Car.objects.create(
|
||||
dealer=dealer,
|
||||
vin=row['vin'],
|
||||
id_car_make=make,
|
||||
id_car_model=model,
|
||||
id_car_serie=serie,
|
||||
id_car_trim=trim,
|
||||
year=year,
|
||||
vendor=vendor,
|
||||
receiving_date=receiving_date,
|
||||
)
|
||||
car.add_colors(exterior=exterior, interior=interior)
|
||||
cars_created += 1
|
||||
|
||||
messages.success(request, f"Successfully imported {cars_created} cars")
|
||||
return redirect('car_list') # redirect to your car list view
|
||||
|
||||
except Exception as e:
|
||||
messages.error(request, f"Error processing CSV: {str(e)}")
|
||||
form = forms.CSVUploadForm()
|
||||
return render(request, 'csv_upload.html',{"make_data":models.CarMake.objects.all(),"form":form})
|
||||
###############################################################
|
||||
###############################################################
|
||||
@ -271,5 +271,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include "bill/includes/mark_as.html" %}
|
||||
{% endblock %}
|
||||
186
templates/csv_upload.html
Normal file
186
templates/csv_upload.html
Normal file
@ -0,0 +1,186 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
.color-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
width: 80px; /* Increased from 3rem for better visibility */
|
||||
height: 80px; /* Increased from 3rem for better visibility */
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.color-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.color-option {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.color-radio {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.color-radio:checked + .color-display {
|
||||
border: 2px solid #0d6efd;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
.color-radio:focus + .color-display {
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
.color-display {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
border-radius: 0.25rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.color-name {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Added for better layout of color options */
|
||||
.color-options-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
{% endblock customCSS %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2>Upload Cars CSV</h2>
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data" class="mt-4">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
{{form.vendor.label}}
|
||||
{{form.vendor}}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{form.year.label}}
|
||||
{{form.year}}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{form.receiving_date.label}}
|
||||
{{form.receiving_date}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="row g-4">
|
||||
<p class="fs-5 mb-2">{% trans 'Exterior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.exterior.queryset %}
|
||||
<div class="color-card">
|
||||
<label class="color-option">
|
||||
<input class="color-radio" type="radio" name="exterior" value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||
<span class="color-name">{{ color.get_local_name }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<p class="fs-5 mb-2">{% trans 'Interior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.interior.queryset %}
|
||||
<div class="color-card">
|
||||
<label class="color-option">
|
||||
<input class="color-radio" type="radio" name="interior" value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||
<span class="color-name">{{ color.get_local_name }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="csv_file" class="form-label">CSV File</label>
|
||||
<input type="file" class="form-control" id="csv_file" name="csv_file" accept=".csv" required>
|
||||
<div class="form-text">
|
||||
CSV should include columns: vin, make, model, year (required)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Upload</button>
|
||||
<a href="{% url 'car_list' %}" class="btn btn-secondary">Cancel</a>
|
||||
</form>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>CSV Format Example</h4>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>vin</th>
|
||||
<th>make</th>
|
||||
<th>model</th>
|
||||
<th>year</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1HGCM82633A123456</td>
|
||||
<td>Honda</td>
|
||||
<td>Accord</td>
|
||||
<td>2023</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<a href="{% static 'sample/cars_sample.csv' %}" class="btn btn-outline-primary">
|
||||
Download Sample CSV
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -28,6 +28,20 @@
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'inventory_item_create' %}">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="nav-link-icon"><span class="fas fa-plus-circle"></span></span><span class="nav-link-text">{% trans "add invenotry item"|capfirst %}</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'purchase_order_list' %}">
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="nav-link-icon"><span class="fas fa-plus-circle"></span></span><span class="nav-link-text">{% trans "purchase Orders"|capfirst %}</span>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
|
||||
@ -416,7 +430,7 @@
|
||||
<div class="overflow-auto scrollbar" style="height: 10rem;">
|
||||
<ul class="nav d-flex flex-column mb-2 pb-1">
|
||||
{% if request.is_dealer %}
|
||||
<li class="nav-item">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link px-3 d-block" href="{% url 'dealer_detail' request.user.dealer.slug %}"> <span class="me-2 text-body align-bottom" data-feather="user"></span><span>{% translate 'profile'|capfirst %}</span></a>
|
||||
</li>
|
||||
{% else %}
|
||||
|
||||
@ -67,7 +67,9 @@
|
||||
{% elif bill.is_approved %}
|
||||
<span class="badge badge-phoenix badge-phoenix-success">
|
||||
{% elif bill.is_paid %}
|
||||
<span class="badge badge-phoenix badge-phoenix-success">
|
||||
<span class="badge badge-phoenix badge-phoenix-success">
|
||||
{% elif bill.is_canceled %}
|
||||
<span class="badge badge-phoenix badge-phoenix-danger">
|
||||
{% endif %}
|
||||
{{ bill.bill_status }}
|
||||
</span>
|
||||
@ -79,10 +81,10 @@
|
||||
<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.pk %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a>
|
||||
<a href="{% url 'bill-detail' entity_slug=entity.slug bill_pk=bill.pk %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
|
||||
@ -1,13 +1,103 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n crispy_forms_tags %}
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
.color-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border: 2px solid transparent;
|
||||
width: 80px; /* Increased from 3rem for better visibility */
|
||||
height: 80px; /* Increased from 3rem for better visibility */
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.color-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.color-option {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.color-radio {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.color-radio:checked + .color-display {
|
||||
border: 2px solid #0d6efd;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
.color-radio:focus + .color-display {
|
||||
border-color: #86b7fe;
|
||||
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
.color-display {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
border-radius: 0.25rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.color-name {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
padding: 2px 5px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Added for better layout of color options */
|
||||
.color-options-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="make" target="model" data=make_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="model" target="serie" data=model_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="serie" target="trim" data=serie_data pk=po_model.pk %}
|
||||
</div>
|
||||
<div class="col">
|
||||
{% include "purchase_orders/partials/po-select.html" with name="trim" target="none" data=trim_data pk=po_model.pk %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col">
|
||||
{{form.vendor.label}}
|
||||
{{form.vendor}}
|
||||
</div>
|
||||
<div class="col">
|
||||
{{form.year.label}}
|
||||
{{form.year}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="account">Account</label>
|
||||
<select class="form-control" name="account" id="account">
|
||||
@ -16,6 +106,39 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Add New Item To Inventory</button>
|
||||
<div class="row g-4 mt-4">
|
||||
<div class="col">
|
||||
<p class="fs-5 mb-2">{% trans 'Exterior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.exterior.queryset %}
|
||||
<div class="color-card">
|
||||
<label class="color-option">
|
||||
<input class="color-radio" type="radio" name="exterior" value="{{ color.id }}" {% if color.id == form.instance.exterior.id %}checked{% endif %}>
|
||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||
<span class="color-name">{{ color.get_local_name }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<p class="fs-5 mb-2">{% trans 'Interior Colors' %}</p>
|
||||
<div class="color-options-container">
|
||||
{% for color in form.fields.interior.queryset %}
|
||||
<div class="color-card">
|
||||
<label class="color-option">
|
||||
<input class="color-radio" type="radio" name="interior" value="{{ color.id }}" {% if color.id == form.instance.interior.id %}checked{% endif %}>
|
||||
<div class="color-display" style="background-color: rgb({{ color.rgb }})">
|
||||
<span class="color-name">{{ color.get_local_name }}</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary mt-5">Add New Item To Inventory</button>
|
||||
</form>
|
||||
{% endblock content %}
|
||||
@ -58,7 +58,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<h4 class="h6 mb-1">{% trans 'Contract' %}</h4>
|
||||
<p class="mb-0">{{ po_model.ce_model.estimate_number }}</p>
|
||||
</div>
|
||||
<a href="{% url 'django_ledger:customer-estimate-detail' entity_slug=view.kwargs.entity_slug ce_pk=po_model.ce_model_id %}"
|
||||
<a href="{% url 'estimate_detail' po_model.ce_model_id %}"
|
||||
class="btn btn-sm btn-outline-info ms-auto">
|
||||
{% trans 'View Contract' %}
|
||||
</a>
|
||||
@ -192,33 +192,28 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
onclick="showPOModal('Fulfill PO', '{% url 'po-action-mark-as-fulfilled' entity_slug po_model.pk %}', 'Mark As Fulfilled')">
|
||||
<i class="fas fa-truck me-2"></i>{% trans 'Mark as Fulfilled' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{# Danger Action Buttons #}
|
||||
{% if po_model.can_delete %}
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="showPOModal('Cancel PO', '{% url 'po-delete' entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
||||
<i class="fas fa-ban me-2"></i>{% trans 'Delete' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.can_void %}
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="showPOModal('Void PO', '{% url 'po-action-mark-as-void' entity_slug po_model.pk %}', 'Mark As Void')">
|
||||
<i class="fas fa-times-circle me-2"></i>{% trans 'Void' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.can_cancel %}
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="showPOModal('Cancel PO', '{% url 'po-action-mark-as-canceled' entity_slug po_model.pk %}', 'Mark As Cancelled')">
|
||||
<i class="fas fa-ban me-2"></i>{% trans 'Cancel' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{# Danger Action Buttons #}
|
||||
{% if po_model.can_delete %}
|
||||
<a class="btn btn-outline-danger" href="{% url 'po-delete' entity_slug po_model.pk %}">
|
||||
<i class="fas fa-ban me-2"></i>{% trans 'Delete' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.can_void %}
|
||||
<button class="btn btn-outline-danger"
|
||||
onclick="djLedger.toggleModal('{{ po_model.get_mark_as_void_html_id }}')">
|
||||
<i class="fas fa-times-circle me-2"></i>{% trans 'Void' %}
|
||||
</button>
|
||||
{% modal_action_v2 bill po_model.get_mark_as_void_url po_model.get_mark_as_void_message po_model.get_mark_as_void_html_id %}
|
||||
{% endif %}
|
||||
|
||||
{% if po_model.can_cancel %}
|
||||
<button class="btn btn-outline-secondary"
|
||||
onclick="djLedger.toggleModal('{{ po_model.get_mark_as_canceled_html_id }}')">
|
||||
<i class="fas fa-window-close me-2"></i>{% trans 'Cancel' %}
|
||||
</button>
|
||||
{% modal_action_v2 bill po_model.get_mark_as_canceled_url po_model.get_mark_as_canceled_message po_model.get_mark_as_canceled_html_id %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@ -228,7 +223,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
{% else %}
|
||||
<div class="card border-0 shadow-sm text-center py-5">
|
||||
<div class="card-body">
|
||||
<a href="{% url 'django_ledger:po-create' entity_slug=entity_slug %}" class="text-decoration-none">
|
||||
<a href="{% url 'purchase_order_create' %}" class="text-decoration-none">
|
||||
<span class="text-muted mb-3 d-inline-block">{% icon "ic:baseline-add-circle-outline" 48 %}</span>
|
||||
<h3 class="h4 text-muted">{% trans 'New Purchase Order' %}</h3>
|
||||
</a>
|
||||
|
||||
@ -34,9 +34,9 @@
|
||||
</div>
|
||||
<div class="dropdown-menu" id="dropdown-menu-{{ po.uuid }}" role="menu">
|
||||
<div class="dropdown-content">
|
||||
<a href="{% url 'django_ledger:po-detail' entity_slug=entity_slug po_pk=po.uuid %}"
|
||||
<a href="{% url 'purchase_order_detail' po_pk=po.uuid %}"
|
||||
class="dropdown-item has-text-success">Details</a>
|
||||
<a href="{% url 'django_ledger:po-delete' entity_slug=entity_slug po_pk=po.uuid %}"
|
||||
<a href="{% url 'po-delete' entity_slug=entity_slug po_pk=po.uuid %}"
|
||||
class="dropdown-item has-text-weight-bold has-text-danger">{% trans ' Delete' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -15,7 +15,7 @@
|
||||
Purchase Order {{ po_model.po_number }}?</h2>
|
||||
|
||||
<p class="card-text text-muted mb-4">All transactions associated with this Purchase Order will be deleted.
|
||||
If you want to void the PO instead, <a href="{% url 'django_ledger:po-detail' entity_slug=view.kwargs.entity_slug po_pk=po_model.uuid %}" class="text-decoration-none">click here</a></p>
|
||||
If you want to void the PO instead, <a href="{% url 'purchase_order_detail' entity_slug=view.kwargs.entity_slug po_pk=po_model.uuid %}" class="text-decoration-none">click here</a></p>
|
||||
|
||||
<div class="d-flex justify-content-center gap-3 mt-4">
|
||||
<a href="{% url 'purchase_order_update' entity_slug=view.kwargs.entity_slug po_pk=po_model.uuid %}"
|
||||
|
||||
@ -18,11 +18,15 @@
|
||||
<h3 class="">
|
||||
{{ _("Purchase Orders") |capfirst }}
|
||||
</h2>
|
||||
<a href="{% url 'purchase_order_create' %}"
|
||||
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create New PO") }}</a>
|
||||
<div>
|
||||
<a href="{% url 'purchase_order_create' %}"
|
||||
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create New PO") }}</a>
|
||||
<a href="{% url 'inventory_item_create' %}?for_po=1"
|
||||
class="btn btn-md btn-phoenix-primary"><i class="fa fa-plus me-2"></i>{{ _("Create Inventory Item for PO") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% include "partials/search_box.html" %}
|
||||
|
||||
|
||||
<div class="table-responsive px-1 scrollbar mt-3">
|
||||
<table class= "table align-items-center table-flush table-hover">
|
||||
<thead>
|
||||
@ -31,13 +35,13 @@
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:40%">Description</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Status</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Created At</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Actions</th>
|
||||
<th class="sort white-space-nowrap align-middle" scope="col" style="width:15%">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="list">
|
||||
{% if purchase_orders %}
|
||||
{% for po in purchase_orders %}
|
||||
|
||||
|
||||
<tr class="hover-actions-trigger btn-reveal-trigger position-static">
|
||||
<td class="align-middle product white-space-nowrap">{{ po.po_number }}</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ po.po_title }}</td>
|
||||
@ -48,11 +52,13 @@
|
||||
</td>
|
||||
<td class="align-middle product white-space-nowrap">{{ po.created|date:"M d, Y" }}</td>
|
||||
<td class="align-middle product white-space-nowrap">
|
||||
<a href="{% url 'purchase_order_detail' po.pk %}"
|
||||
class="btn btn-sm btn-phoenix-success">
|
||||
<i class="fa-regular fa-eye me-1"></i>
|
||||
{% trans "view"|capfirst %}
|
||||
</a>
|
||||
<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 'purchase_order_detail' po.pk %}" class="dropdown-item text-success-dark">{% trans 'View' %}</a>
|
||||
<a href="{% url 'upload_cars' po.pk %}" class="dropdown-item text-success-dark">{% trans 'Upload Data' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{%endfor%}
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
</td>
|
||||
<td class="has-text-centered">{% if item.bill_model_id %}
|
||||
<a class="is-small is-info button"
|
||||
href="{% url 'django_ledger:bill-detail' entity_slug=entity_slug bill_pk=item.bill_model_id %}">View
|
||||
href="{% url 'bill-detail' entity_slug=entity_slug bill_pk=item.bill_model_id %}">View
|
||||
Bill</a>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user