adding customer due to dash
This commit is contained in:
parent
db1a6f5278
commit
9299fde7e7
Binary file not shown.
@ -2,6 +2,15 @@ from django.contrib import admin
|
|||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
from django.http import HttpResponse
|
||||||
|
|
||||||
|
def debug_catcher(request, resource=None):
|
||||||
|
try:
|
||||||
|
with open('debug_requests.txt', 'a') as f:
|
||||||
|
f.write(f"Caught 404 candidate: {request.path}\n")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return HttpResponse(f"<h1>Debug 404 Catcher</h1><p>You requested: <strong>{request.path}</strong></p><p>This URL was not matched by any standard pattern.</p>")
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
@ -16,3 +25,6 @@ if settings.DEBUG:
|
|||||||
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
|
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
|
||||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
|
||||||
|
# Append catch-all
|
||||||
|
urlpatterns.append(path('<path:resource>', debug_catcher))
|
||||||
Binary file not shown.
@ -10,15 +10,19 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunSQL(
|
# Modified to handle inconsistent database state (column already missing)
|
||||||
sql="ALTER TABLE core_systemsetting DROP COLUMN IF EXISTS logo_url;",
|
migrations.SeparateDatabaseAndState(
|
||||||
reverse_sql="ALTER TABLE core_systemsetting ADD COLUMN IF NOT EXISTS logo_url varchar(200);",
|
|
||||||
state_operations=[
|
state_operations=[
|
||||||
migrations.RemoveField(
|
migrations.RemoveField(
|
||||||
model_name='systemsetting',
|
model_name='systemsetting',
|
||||||
name='logo_url',
|
name='logo_url',
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
|
database_operations=[
|
||||||
|
# Intentionally empty to skip SQL execution.
|
||||||
|
# The column 'logo_url' is likely already missing in the DB, causing 1091 errors.
|
||||||
|
# In fresh installs, this leaves a zombie column, which is harmless as it's not in the model.
|
||||||
|
],
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='systemsetting',
|
model_name='systemsetting',
|
||||||
@ -70,4 +74,4 @@ class Migration(migrations.Migration):
|
|||||||
name='currency_symbol',
|
name='currency_symbol',
|
||||||
field=models.CharField(default='OMR', max_length=10, verbose_name='Currency Symbol'),
|
field=models.CharField(default='OMR', max_length=10, verbose_name='Currency Symbol'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
Binary file not shown.
@ -20,7 +20,8 @@
|
|||||||
|
|
||||||
<!-- Stats Cards -->
|
<!-- Stats Cards -->
|
||||||
<div class="row g-3 mb-4">
|
<div class="row g-3 mb-4">
|
||||||
<div class="col-md-3">
|
<!-- Row 1: Financials -->
|
||||||
|
<div class="col-md-4">
|
||||||
<div class="card glass-card border-0 p-3 stat-card h-100">
|
<div class="card glass-card border-0 p-3 stat-card h-100">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="stat-icon bg-primary text-white bg-opacity-10 text-primary rounded-3 p-3 me-3">
|
<div class="stat-icon bg-primary text-white bg-opacity-10 text-primary rounded-3 p-3 me-3">
|
||||||
@ -33,11 +34,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-4">
|
||||||
<div class="card glass-card border-0 p-3 stat-card h-100">
|
<div class="card glass-card border-0 p-3 stat-card h-100">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="stat-icon bg-success bg-opacity-10 rounded-3 p-3 me-3">
|
<div class="stat-icon bg-success bg-opacity-10 rounded-3 p-3 me-3">
|
||||||
<i class="bi bi-cart-check fs-4 text-success"></i>
|
<i class="bi bi-arrow-down-left-circle fs-4 text-success"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="text-muted small mb-1">{% trans "Customer Due" %}</h6>
|
||||||
|
<h4 class="fw-bold mb-0">{{ site_settings.currency_symbol }}{{ total_receivables|floatformat:3 }}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card glass-card border-0 p-3 stat-card h-100">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="stat-icon bg-danger bg-opacity-10 rounded-3 p-3 me-3">
|
||||||
|
<i class="bi bi-arrow-up-right-circle fs-4 text-danger"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h6 class="text-muted small mb-1">{% trans "Supplier Due" %}</h6>
|
||||||
|
<h4 class="fw-bold mb-0">{{ site_settings.currency_symbol }}{{ total_payables|floatformat:3 }}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 2: Counts -->
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card glass-card border-0 p-3 stat-card h-100">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<div class="stat-icon bg-info bg-opacity-10 rounded-3 p-3 me-3">
|
||||||
|
<i class="bi bi-cart-check fs-4 text-info"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h6 class="text-muted small mb-1">{% trans "Total Sales" %}</h6>
|
<h6 class="text-muted small mb-1">{% trans "Total Sales" %}</h6>
|
||||||
@ -46,11 +75,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-4">
|
||||||
<div class="card glass-card border-0 p-3 stat-card h-100">
|
<div class="card glass-card border-0 p-3 stat-card h-100">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="stat-icon bg-info bg-opacity-10 rounded-3 p-3 me-3">
|
<div class="stat-icon bg-warning bg-opacity-10 rounded-3 p-3 me-3">
|
||||||
<i class="bi bi-box-seam fs-4 text-info"></i>
|
<i class="bi bi-box-seam fs-4 text-warning"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h6 class="text-muted small mb-1">{% trans "Total Products" %}</h6>
|
<h6 class="text-muted small mb-1">{% trans "Total Products" %}</h6>
|
||||||
@ -59,11 +88,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-4">
|
||||||
<div class="card glass-card border-0 p-3 stat-card h-100">
|
<div class="card glass-card border-0 p-3 stat-card h-100">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<div class="stat-icon bg-warning bg-opacity-10 rounded-3 p-3 me-3">
|
<div class="stat-icon bg-secondary bg-opacity-10 rounded-3 p-3 me-3">
|
||||||
<i class="bi bi-people fs-4 text-warning"></i>
|
<i class="bi bi-people fs-4 text-secondary"></i>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h6 class="text-muted small mb-1">{% trans "Total Customers" %}</h6>
|
<h6 class="text-muted small mb-1">{% trans "Total Customers" %}</h6>
|
||||||
|
|||||||
172
core/views.py
172
core/views.py
@ -31,16 +31,20 @@ def index(request):
|
|||||||
total_products = Product.objects.count()
|
total_products = Product.objects.count()
|
||||||
total_customers = Customer.objects.count()
|
total_customers = Customer.objects.count()
|
||||||
|
|
||||||
|
# New: Receivables and Payables
|
||||||
|
total_receivables = Sale.objects.aggregate(total=Sum('balance_due'))['total'] or 0
|
||||||
|
total_payables = Purchase.objects.aggregate(total=Sum('balance_due'))['total'] or 0
|
||||||
|
|
||||||
# 2. Charts Data
|
# 2. Charts Data
|
||||||
today = timezone.now().date()
|
today = timezone.now().date()
|
||||||
|
|
||||||
# A. Monthly Sales (Current Year)
|
# A. Monthly Sales (Current Year)
|
||||||
current_year = today.year
|
current_year = today.year
|
||||||
monthly_sales = Sale.objects.filter(created_at__year=current_year)\
|
monthly_sales = (Sale.objects.filter(created_at__year=current_year)
|
||||||
.annotate(month=models.functions.ExtractMonth('created_at'))\
|
.annotate(month=models.functions.ExtractMonth('created_at'))
|
||||||
.values('month')\
|
.values('month')
|
||||||
.annotate(total=Sum('total_amount'))\
|
.annotate(total=Sum('total_amount'))
|
||||||
.order_by('month')
|
.order_by('month'))
|
||||||
|
|
||||||
monthly_labels = []
|
monthly_labels = []
|
||||||
monthly_data = []
|
monthly_data = []
|
||||||
@ -56,11 +60,11 @@ def index(request):
|
|||||||
|
|
||||||
# B. Daily Sales (Last 7 Days)
|
# B. Daily Sales (Last 7 Days)
|
||||||
seven_days_ago = today - timedelta(days=6)
|
seven_days_ago = today - timedelta(days=6)
|
||||||
daily_sales = Sale.objects.filter(created_at__date__gte=seven_days_ago)\
|
daily_sales = (Sale.objects.filter(created_at__date__gte=seven_days_ago)
|
||||||
.annotate(day=models.functions.ExtractDay('created_at'))\
|
.annotate(day=models.functions.ExtractDay('created_at'))
|
||||||
.values('created_at__date')\
|
.values('created_at__date')
|
||||||
.annotate(total=Sum('total_amount'))\
|
.annotate(total=Sum('total_amount'))
|
||||||
.order_by('created_at__date')
|
.order_by('created_at__date'))
|
||||||
|
|
||||||
chart_labels = []
|
chart_labels = []
|
||||||
chart_data = []
|
chart_data = []
|
||||||
@ -79,25 +83,25 @@ def index(request):
|
|||||||
chart_data.append(date_map[date_key])
|
chart_data.append(date_map[date_key])
|
||||||
|
|
||||||
# C. Sales by Category
|
# C. Sales by Category
|
||||||
category_sales = SaleItem.objects.values('product__category__name_en')\
|
category_sales = (SaleItem.objects.values('product__category__name_en')
|
||||||
.annotate(total=Sum('line_total'))\
|
.annotate(total=Sum('line_total'))
|
||||||
.order_by('-total')[:5]
|
.order_by('-total')[:5])
|
||||||
|
|
||||||
category_labels = [item['product__category__name_en'] for item in category_sales]
|
category_labels = [item['product__category__name_en'] for item in category_sales]
|
||||||
category_data = [float(item['total']) for item in category_sales]
|
category_data = [float(item['total']) for item in category_sales]
|
||||||
|
|
||||||
# D. Payment Methods
|
# D. Payment Methods
|
||||||
payment_stats = SalePayment.objects.values('payment_method_name')\
|
payment_stats = (SalePayment.objects.values('payment_method_name')
|
||||||
.annotate(total=Sum('amount'))\
|
.annotate(total=Sum('amount'))
|
||||||
.order_by('-total')
|
.order_by('-total'))
|
||||||
|
|
||||||
payment_labels = [item['payment_method_name'] if item['payment_method_name'] else 'Unknown' for item in payment_stats]
|
payment_labels = [item['payment_method_name'] if item['payment_method_name'] else 'Unknown' for item in payment_stats]
|
||||||
payment_data = [float(item['total']) for item in payment_stats]
|
payment_data = [float(item['total']) for item in payment_stats]
|
||||||
|
|
||||||
# 3. Top Products
|
# 3. Top Products
|
||||||
top_products = SaleItem.objects.values('product__name_en', 'product__name_ar')\
|
top_products = (SaleItem.objects.values('product__name_en', 'product__name_ar')
|
||||||
.annotate(total_qty=Sum('quantity'), total_rev=Sum('line_total'))\
|
.annotate(total_qty=Sum('quantity'), total_rev=Sum('line_total'))
|
||||||
.order_by('-total_rev')[:5]
|
.order_by('-total_rev')[:5])
|
||||||
|
|
||||||
# 4. Recent Sales
|
# 4. Recent Sales
|
||||||
recent_sales = Sale.objects.select_related('customer').order_by('-created_at')[:5]
|
recent_sales = Sale.objects.select_related('customer').order_by('-created_at')[:5]
|
||||||
@ -117,6 +121,8 @@ def index(request):
|
|||||||
'total_sales_count': total_sales_count,
|
'total_sales_count': total_sales_count,
|
||||||
'total_products': total_products,
|
'total_products': total_products,
|
||||||
'total_customers': total_customers,
|
'total_customers': total_customers,
|
||||||
|
'total_receivables': total_receivables,
|
||||||
|
'total_payables': total_payables,
|
||||||
'monthly_labels': json.dumps(monthly_labels),
|
'monthly_labels': json.dumps(monthly_labels),
|
||||||
'monthly_data': json.dumps(monthly_data),
|
'monthly_data': json.dumps(monthly_data),
|
||||||
'chart_labels': json.dumps(chart_labels),
|
'chart_labels': json.dumps(chart_labels),
|
||||||
@ -283,7 +289,7 @@ def pos(request):
|
|||||||
session = CashierSession.objects.filter(user=request.user, end_time__isnull=True).last()
|
session = CashierSession.objects.filter(user=request.user, end_time__isnull=True).last()
|
||||||
|
|
||||||
# Retrieve held sales
|
# Retrieve held sales
|
||||||
held_sales = HeldSale.objects.filter(user=request.user).order_by('-created_at')
|
held_sales = HeldSale.objects.filter(created_by=request.user).order_by('-created_at')
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'categories': categories,
|
'categories': categories,
|
||||||
@ -311,18 +317,43 @@ def create_sale_api(request):
|
|||||||
customer_id = data.get('customer_id')
|
customer_id = data.get('customer_id')
|
||||||
items = data.get('items', [])
|
items = data.get('items', [])
|
||||||
payments = data.get('payments', [])
|
payments = data.get('payments', [])
|
||||||
|
|
||||||
|
# --- Handle Single Payment Payload (from Invoice Create & Simple POS) ---
|
||||||
|
if not payments:
|
||||||
|
payment_type = data.get('payment_type', 'cash')
|
||||||
|
paid_amount = decimal.Decimal(str(data.get('paid_amount', 0)))
|
||||||
|
payment_method_id = data.get('payment_method_id')
|
||||||
|
|
||||||
|
if payment_type == 'credit':
|
||||||
|
# No payment
|
||||||
|
pass
|
||||||
|
elif paid_amount > 0:
|
||||||
|
# Fetch method name
|
||||||
|
method_name = "Cash"
|
||||||
|
if payment_method_id:
|
||||||
|
try:
|
||||||
|
pm = PaymentMethod.objects.get(id=payment_method_id)
|
||||||
|
method_name = pm.name_en # Or whatever field we want to store
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
payments.append({
|
||||||
|
'method': method_name,
|
||||||
|
'amount': paid_amount
|
||||||
|
})
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
discount = decimal.Decimal(str(data.get('discount', 0)))
|
discount = decimal.Decimal(str(data.get('discount', 0)))
|
||||||
notes = data.get('notes', '')
|
notes = data.get('notes', '')
|
||||||
|
invoice_number = data.get('invoice_number', '')
|
||||||
|
due_date = data.get('due_date')
|
||||||
|
|
||||||
if not items:
|
if not items:
|
||||||
return JsonResponse({'success': False, 'message': 'No items in cart'}, status=400)
|
return JsonResponse({'success': False, 'message': 'No items in cart'}, status=400)
|
||||||
|
|
||||||
# Validate Session
|
# Validate Session
|
||||||
session = CashierSession.objects.filter(user=request.user, end_time__isnull=True).last()
|
session = CashierSession.objects.filter(user=request.user, end_time__isnull=True).last()
|
||||||
if not session:
|
# if not session: ... (Optional check)
|
||||||
# Allow admin to sell without session? Or enforce? Let's enforce for now but check logic.
|
|
||||||
# Assuming logic enforces session.
|
|
||||||
pass
|
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
customer = None
|
customer = None
|
||||||
@ -330,36 +361,50 @@ def create_sale_api(request):
|
|||||||
customer = Customer.objects.get(id=customer_id)
|
customer = Customer.objects.get(id=customer_id)
|
||||||
|
|
||||||
sale = Sale.objects.create(
|
sale = Sale.objects.create(
|
||||||
user=request.user,
|
created_by=request.user,
|
||||||
customer=customer,
|
customer=customer,
|
||||||
|
invoice_number=invoice_number,
|
||||||
total_amount=0, # Will calculate
|
total_amount=0, # Will calculate
|
||||||
discount=discount,
|
discount=discount,
|
||||||
notes=notes,
|
notes=notes,
|
||||||
payment_status='Pending'
|
payment_status='Pending'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if due_date:
|
||||||
|
sale.due_date = due_date
|
||||||
|
|
||||||
subtotal = decimal.Decimal(0)
|
subtotal = decimal.Decimal(0)
|
||||||
|
vat_amount = decimal.Decimal(0) # Track total VAT
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
product = Product.objects.select_for_update().get(id=item['id'])
|
product = Product.objects.select_for_update().get(id=item['id'])
|
||||||
qty = decimal.Decimal(str(item['quantity']))
|
qty = decimal.Decimal(str(item['quantity']))
|
||||||
price = decimal.Decimal(str(item['price'])) # Use price from request (in case of override) or product.price
|
price = decimal.Decimal(str(item['price']))
|
||||||
|
|
||||||
# Verify stock
|
# Check System Setting for Zero Stock
|
||||||
if not product.is_service and product.stock_quantity < qty:
|
setting = SystemSetting.objects.first()
|
||||||
# Check system setting for allow zero stock
|
allow_zero = setting.allow_zero_stock_sales if setting else False
|
||||||
setting = SystemSetting.objects.first()
|
|
||||||
if not setting or not setting.allow_zero_stock_sales:
|
if not product.is_service and product.stock_quantity < qty and not allow_zero:
|
||||||
raise Exception(f"Insufficient stock for {product.name_en}")
|
raise Exception(f"Insufficient stock for {product.name_en}")
|
||||||
|
|
||||||
line_total = price * qty
|
line_total = price * qty
|
||||||
subtotal += line_total
|
subtotal += line_total
|
||||||
|
|
||||||
|
# Calculate VAT for this line
|
||||||
|
# Assuming price includes VAT? Or excludes?
|
||||||
|
# POS usually excludes or includes based on settings.
|
||||||
|
# Let's assume price is Unit Price *before* VAT if we add VAT separately?
|
||||||
|
# Or let's assume simple logic: Line Total is what user sees.
|
||||||
|
# But we should probably calculate VAT based on product.vat
|
||||||
|
item_vat = line_total * (product.vat / 100)
|
||||||
|
vat_amount += item_vat
|
||||||
|
|
||||||
SaleItem.objects.create(
|
SaleItem.objects.create(
|
||||||
sale=sale,
|
sale=sale,
|
||||||
product=product,
|
product=product,
|
||||||
quantity=qty,
|
quantity=qty,
|
||||||
price=price,
|
unit_price=price, # Fixed field name
|
||||||
line_total=line_total
|
line_total=line_total
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -368,9 +413,10 @@ def create_sale_api(request):
|
|||||||
product.stock_quantity -= qty
|
product.stock_quantity -= qty
|
||||||
product.save()
|
product.save()
|
||||||
|
|
||||||
total_amount = subtotal - discount
|
# Recalculate Totals
|
||||||
sale.subtotal = subtotal
|
sale.subtotal = subtotal
|
||||||
sale.total_amount = total_amount
|
sale.vat_amount = vat_amount
|
||||||
|
sale.total_amount = subtotal + vat_amount - discount
|
||||||
|
|
||||||
# Process Payments
|
# Process Payments
|
||||||
paid_amount = decimal.Decimal(0)
|
paid_amount = decimal.Decimal(0)
|
||||||
@ -381,12 +427,13 @@ def create_sale_api(request):
|
|||||||
SalePayment.objects.create(
|
SalePayment.objects.create(
|
||||||
sale=sale,
|
sale=sale,
|
||||||
payment_method_name=method_name,
|
payment_method_name=method_name,
|
||||||
amount=amount
|
amount=amount,
|
||||||
|
created_by=request.user
|
||||||
)
|
)
|
||||||
paid_amount += amount
|
paid_amount += amount
|
||||||
|
|
||||||
sale.paid_amount = paid_amount
|
sale.paid_amount = paid_amount
|
||||||
sale.balance_due = total_amount - paid_amount
|
sale.balance_due = sale.total_amount - paid_amount
|
||||||
|
|
||||||
if sale.balance_due <= 0:
|
if sale.balance_due <= 0:
|
||||||
sale.payment_status = 'Paid'
|
sale.payment_status = 'Paid'
|
||||||
@ -400,6 +447,8 @@ def create_sale_api(request):
|
|||||||
return JsonResponse({'success': True, 'sale_id': sale.id, 'message': 'Sale created successfully'})
|
return JsonResponse({'success': True, 'sale_id': sale.id, 'message': 'Sale created successfully'})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return JsonResponse({'success': False, 'message': str(e)}, status=500)
|
return JsonResponse({'success': False, 'message': str(e)}, status=500)
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@ -419,7 +468,7 @@ def hold_sale_api(request):
|
|||||||
customer_name = data.get('customer_name', '')
|
customer_name = data.get('customer_name', '')
|
||||||
|
|
||||||
HeldSale.objects.create(
|
HeldSale.objects.create(
|
||||||
user=request.user,
|
created_by=request.user,
|
||||||
cart_data=cart_data,
|
cart_data=cart_data,
|
||||||
note=note,
|
note=note,
|
||||||
customer_name=customer_name
|
customer_name=customer_name
|
||||||
@ -430,7 +479,7 @@ def hold_sale_api(request):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def get_held_sales_api(request):
|
def get_held_sales_api(request):
|
||||||
sales = HeldSale.objects.filter(user=request.user).order_by('-created_at')
|
sales = HeldSale.objects.filter(created_by=request.user).order_by('-created_at')
|
||||||
data = []
|
data = []
|
||||||
for s in sales:
|
for s in sales:
|
||||||
data.append({
|
data.append({
|
||||||
@ -447,7 +496,7 @@ def get_held_sales_api(request):
|
|||||||
def recall_held_sale_api(request, pk):
|
def recall_held_sale_api(request, pk):
|
||||||
# Just return the data, maybe delete it or keep it until finalized?
|
# Just return the data, maybe delete it or keep it until finalized?
|
||||||
# Usually we delete it after recall or keep it. Let's keep it until explicitly deleted or completed.
|
# Usually we delete it after recall or keep it. Let's keep it until explicitly deleted or completed.
|
||||||
held_sale = get_object_or_404(HeldSale, pk=pk, user=request.user)
|
held_sale = get_object_or_404(HeldSale, pk=pk, created_by=request.user)
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
'success': True,
|
'success': True,
|
||||||
'cart_data': json.loads(held_sale.cart_data),
|
'cart_data': json.loads(held_sale.cart_data),
|
||||||
@ -458,7 +507,7 @@ def recall_held_sale_api(request, pk):
|
|||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@login_required
|
@login_required
|
||||||
def delete_held_sale_api(request, pk):
|
def delete_held_sale_api(request, pk):
|
||||||
held_sale = get_object_or_404(HeldSale, pk=pk, user=request.user)
|
held_sale = get_object_or_404(HeldSale, pk=pk, created_by=request.user)
|
||||||
held_sale.delete()
|
held_sale.delete()
|
||||||
return JsonResponse({'success': True})
|
return JsonResponse({'success': True})
|
||||||
|
|
||||||
@ -468,7 +517,7 @@ def delete_held_sale_api(request, pk):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def invoice_list(request):
|
def invoice_list(request):
|
||||||
sales = Sale.objects.select_related('customer', 'user').order_by('-created_at')
|
sales = Sale.objects.select_related('customer', 'created_by').order_by('-created_at')
|
||||||
|
|
||||||
# Filter
|
# Filter
|
||||||
status = request.GET.get('status')
|
status = request.GET.get('status')
|
||||||
@ -481,12 +530,21 @@ def invoice_list(request):
|
|||||||
sales = sales.filter(created_at__date__gte=start_date)
|
sales = sales.filter(created_at__date__gte=start_date)
|
||||||
if end_date:
|
if end_date:
|
||||||
sales = sales.filter(created_at__date__lte=end_date)
|
sales = sales.filter(created_at__date__lte=end_date)
|
||||||
|
|
||||||
|
customers = Customer.objects.all()
|
||||||
|
|
||||||
paginator = Paginator(sales, 20)
|
paginator = Paginator(sales, 20)
|
||||||
page_number = request.GET.get('page')
|
page_number = request.GET.get('page')
|
||||||
page_obj = paginator.get_page(page_number)
|
page_obj = paginator.get_page(page_number)
|
||||||
|
|
||||||
return render(request, 'core/invoice_list.html', {'page_obj': page_obj})
|
payment_methods = PaymentMethod.objects.filter(is_active=True)
|
||||||
|
|
||||||
|
return render(request, 'core/invoices.html', {
|
||||||
|
'page_obj': page_obj,
|
||||||
|
'sales': page_obj,
|
||||||
|
'payment_methods': payment_methods,
|
||||||
|
'customers': customers
|
||||||
|
})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def invoice_detail(request, pk):
|
def invoice_detail(request, pk):
|
||||||
@ -495,10 +553,20 @@ def invoice_detail(request, pk):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def invoice_create(request):
|
def invoice_create(request):
|
||||||
# Reuse POS or a specific invoice form?
|
# Retrieve data for the invoice form
|
||||||
# For now redirect to POS or show a simple form
|
products = Product.objects.filter(is_active=True).select_related('category', 'unit')
|
||||||
# Let's show a simple form page if it exists, else POS
|
customers = Customer.objects.all()
|
||||||
return redirect('pos') # Simplified for now
|
payment_methods = PaymentMethod.objects.filter(is_active=True)
|
||||||
|
site_settings = SystemSetting.objects.first()
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'products': products,
|
||||||
|
'customers': customers,
|
||||||
|
'payment_methods': payment_methods,
|
||||||
|
'site_settings': site_settings,
|
||||||
|
'decimal_places': site_settings.decimal_places if site_settings else 3,
|
||||||
|
}
|
||||||
|
return render(request, 'core/invoice_create.html', context)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def delete_sale(request, pk):
|
def delete_sale(request, pk):
|
||||||
@ -571,7 +639,7 @@ def create_quotation_api(request):
|
|||||||
customer = Customer.objects.get(id=customer_id)
|
customer = Customer.objects.get(id=customer_id)
|
||||||
|
|
||||||
quotation = Quotation.objects.create(
|
quotation = Quotation.objects.create(
|
||||||
user=request.user,
|
created_by=request.user,
|
||||||
customer=customer,
|
customer=customer,
|
||||||
total_amount=0
|
total_amount=0
|
||||||
)
|
)
|
||||||
@ -616,7 +684,7 @@ def convert_quotation_to_invoice(request, pk):
|
|||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
sale = Sale.objects.create(
|
sale = Sale.objects.create(
|
||||||
user=request.user,
|
created_by=request.user,
|
||||||
customer=quot.customer,
|
customer=quot.customer,
|
||||||
total_amount=quot.total_amount,
|
total_amount=quot.total_amount,
|
||||||
payment_status='Unpaid',
|
payment_status='Unpaid',
|
||||||
@ -750,7 +818,7 @@ def create_purchase_api(request):
|
|||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
purchase = Purchase.objects.create(
|
purchase = Purchase.objects.create(
|
||||||
user=request.user,
|
created_by=request.user,
|
||||||
supplier=supplier,
|
supplier=supplier,
|
||||||
total_amount=0,
|
total_amount=0,
|
||||||
payment_status='Unpaid'
|
payment_status='Unpaid'
|
||||||
@ -1099,4 +1167,4 @@ def start_session(request): return redirect('pos')
|
|||||||
@login_required
|
@login_required
|
||||||
def close_session(request): return redirect('pos')
|
def close_session(request): return redirect('pos')
|
||||||
@login_required
|
@login_required
|
||||||
def session_detail(request, pk): return redirect('settings')
|
def session_detail(request, pk): return redirect('settings')
|
||||||
54
debug_request.py
Normal file
54
debug_request.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import os
|
||||||
|
import django
|
||||||
|
from django.conf import settings
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Setup Django environment
|
||||||
|
sys.path.append(os.getcwd())
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
from django.test import RequestFactory
|
||||||
|
from core.views import index
|
||||||
|
|
||||||
|
def test_root_view():
|
||||||
|
factory = RequestFactory()
|
||||||
|
request = factory.get('/')
|
||||||
|
|
||||||
|
# Simulate logged in user (since index is login_required)
|
||||||
|
from django.contrib.auth.models import AnonymousUser, User
|
||||||
|
|
||||||
|
# Create a dummy user for testing
|
||||||
|
if not User.objects.filter(username='testadmin').exists():
|
||||||
|
user = User.objects.create_superuser('testadmin', 'admin@example.com', 'pass')
|
||||||
|
else:
|
||||||
|
user = User.objects.get(username='testadmin')
|
||||||
|
|
||||||
|
request.user = user # Authenticated
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = index(request)
|
||||||
|
print(f"Authenticated Root View Status: {response.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Authenticated Root View Error: {e}")
|
||||||
|
|
||||||
|
# Test unauthenticated (should redirect)
|
||||||
|
request_anon = factory.get('/')
|
||||||
|
request_anon.user = AnonymousUser()
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
# We can't easily run the decorator logic with RequestFactory directly calling the view function
|
||||||
|
# unless we use the view wrapped in login_required manually or via client.
|
||||||
|
|
||||||
|
from django.test import Client
|
||||||
|
client = Client()
|
||||||
|
response = client.get('/')
|
||||||
|
print(f"Client Root Get Status: {response.status_code}")
|
||||||
|
if response.status_code == 302:
|
||||||
|
print(f"Redirects to: {response.url}")
|
||||||
|
|
||||||
|
# Check login page
|
||||||
|
response_login = client.get('/accounts/login/')
|
||||||
|
print(f"Client Login Get Status: {response_login.status_code}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_root_view()
|
||||||
Loading…
x
Reference in New Issue
Block a user