after changes

This commit is contained in:
Flatlogic Bot 2026-02-10 03:46:50 +00:00
parent c851649b1a
commit 441b04ab78
3 changed files with 885 additions and 176 deletions

View File

@ -1,5 +1,6 @@
import json import json
import decimal import decimal
import logging
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.http import JsonResponse, HttpResponse from django.http import JsonResponse, HttpResponse
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
@ -8,36 +9,128 @@ from django.utils import timezone
from django.contrib import messages from django.contrib import messages
from django.db import transaction from django.db import transaction
from django.db.models import Sum, Q, Count, F from django.db.models import Sum, Q, Count, F
from django.db.models.functions import TruncMonth, TruncDay
from django.core.paginator import Paginator from django.core.paginator import Paginator
from django.urls import reverse from django.urls import reverse
from django.utils.text import slugify
from .models import ( from .models import (
Product, Category, Unit, Supplier, Customer, Sale, SaleItem, Product, Category, Unit, Supplier, Customer, Sale, SaleItem,
Purchase, PurchaseItem, Expense, ExpenseCategory, SalePayment, Purchase, PurchaseItem, Expense, ExpenseCategory, SalePayment,
PurchasePayment, SystemSetting, CashierSession, SaleReturn, PurchasePayment, SystemSetting, CashierSession, SaleReturn,
SaleReturnItem, PurchaseReturn, PurchaseReturnItem, HeldSale, SaleReturnItem, PurchaseReturn, PurchaseReturnItem, HeldSale,
Quotation, QuotationItem, PaymentMethod, LoyaltyTier, Quotation, QuotationItem, PaymentMethod, LoyaltyTier,
Device, CashierCounterRegistry, UserProfile Device, CashierCounterRegistry, UserProfile, PurchaseOrder, PurchaseOrderItem
) )
from .forms import SystemSettingForm, CustomerForm, SupplierForm, ProductForm, CategoryForm, UnitForm from .forms import SystemSettingForm, CustomerForm, SupplierForm, ProductForm, CategoryForm, UnitForm, ExpenseForm
from .helpers import number_to_words_en
logger = logging.getLogger(__name__)
# --- Dashboard --- # --- Dashboard ---
@login_required @login_required
def index(request): def index(request):
settings = SystemSetting.objects.first()
today = timezone.now().date() today = timezone.now().date()
# Stats seven_days_ago = today - timezone.timedelta(days=7)
today_sales = Sale.objects.filter(created_at__date=today).aggregate(t=Sum('total_amount'))['t'] or 0 this_year = today.year
month_sales = Sale.objects.filter(created_at__month=today.month).aggregate(t=Sum('total_amount'))['t'] or 0
low_stock = Product.objects.filter(stock_quantity__lte=F('min_stock_level'), is_active=True).count()
total_products = Product.objects.filter(is_active=True).count()
# Recent Sales # 1. Financial Headlines
recent_sales = Sale.objects.select_related('customer').order_by('-created_at')[:5] total_sales_amount = Sale.objects.aggregate(t=Sum('total_amount'))['t'] or 0
total_receivables = Sale.objects.filter(status__in=['partial', 'unpaid']).aggregate(t=Sum('balance_due'))['t'] or 0
# For payables, we sum the balance_due of Purchases
total_payables = Purchase.objects.filter(status__in=['partial', 'unpaid']).aggregate(t=Sum('balance_due'))['t'] or 0
# 2. Counts
total_sales_count = Sale.objects.count()
total_products = Product.objects.filter(is_active=True).count()
total_customers = Customer.objects.count()
# 3. Monthly Sales Chart (This Year)
monthly_sales = Sale.objects.filter(created_at__year=this_year)\
.annotate(month=TruncMonth('created_at'))\
.values('month')\
.annotate(total=Sum('total_amount'))\
.order_by('month')
monthly_labels = []
monthly_data = []
# Initialize all months to 0
months_map = {i: 0 for i in range(1, 13)}
for entry in monthly_sales:
months_map[entry['month'].month] = float(entry['total'])
import calendar
for i in range(1, 13):
monthly_labels.append(calendar.month_name[i])
monthly_data.append(months_map[i])
# 4. Daily Sales Chart (Last 7 Days)
daily_sales = Sale.objects.filter(created_at__date__gte=seven_days_ago)\
.annotate(day=TruncDay('created_at'))\
.values('day')\
.annotate(total=Sum('total_amount'))\
.order_by('day')
daily_map = {}
for entry in daily_sales:
daily_map[entry['day'].date()] = float(entry['total'])
chart_labels = []
chart_data = []
for i in range(7):
day = seven_days_ago + timezone.timedelta(days=i)
chart_labels.append(day.strftime('%a %d'))
chart_data.append(daily_map.get(day, 0))
# 5. Sales by Category
category_sales = SaleItem.objects.values('product__category__name_en')\
.annotate(total=Sum('line_total'))\
.order_by('-total')[:6]
category_labels = [item['product__category__name_en'] for item in category_sales]
category_data = [float(item['total']) for item in category_sales]
# 6. Payment Methods
payment_stats = SalePayment.objects.values('payment_method__name_en')\
.annotate(total=Sum('amount'))\
.order_by('-total')
payment_labels = [item['payment_method__name_en'] or 'Cash' for item in payment_stats]
payment_data = [float(item['total']) for item in payment_stats]
# 7. Top Selling Products
top_products = SaleItem.objects.values('product__name_en', 'product__name_ar')\
.annotate(total_qty=Sum('quantity'), total_rev=Sum('line_total'))\
.order_by('-total_qty')[:5]
# 8. Low Stock & Expiry
low_stock_products = Product.objects.filter(stock_quantity__lte=F('min_stock_level'), is_active=True)[:5]
low_stock_count = Product.objects.filter(stock_quantity__lte=F('min_stock_level'), is_active=True).count()
expired_count = Product.objects.filter(has_expiry=True, expiry_date__lt=today).count()
# 9. Recent Sales
recent_sales = Sale.objects.select_related('customer', 'created_by').order_by('-created_at')[:5]
context = { context = {
'today_sales': today_sales, 'site_settings': settings,
'month_sales': month_sales, 'total_sales_amount': total_sales_amount,
'low_stock_count': low_stock, 'total_receivables': total_receivables,
'total_payables': total_payables,
'total_sales_count': total_sales_count,
'total_products': total_products, 'total_products': total_products,
'total_customers': total_customers,
'monthly_labels': monthly_labels,
'monthly_data': monthly_data,
'chart_labels': chart_labels,
'chart_data': chart_data,
'category_labels': category_labels,
'category_data': category_data,
'payment_labels': payment_labels,
'payment_data': payment_data,
'top_products': top_products,
'low_stock_products': low_stock_products,
'low_stock_count': low_stock_count,
'expired_count': expired_count,
'recent_sales': recent_sales, 'recent_sales': recent_sales,
} }
return render(request, 'core/index.html', context) return render(request, 'core/index.html', context)
@ -83,15 +176,22 @@ def inventory(request):
def add_category_ajax(request): def add_category_ajax(request):
if request.method != 'POST': if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid method'}) return JsonResponse({'success': False, 'error': 'Invalid method'})
try:
try: try:
data = json.loads(request.body) data = json.loads(request.body)
name_en = data.get('name_en') name_en = data.get('name_en')
name_ar = data.get('name_ar') name_ar = data.get('name_ar')
except (json.JSONDecodeError, TypeError):
name_en = request.POST.get('name_en')
name_ar = request.POST.get('name_ar')
if not name_en or not name_ar: if not name_en or not name_ar:
return JsonResponse({'success': False, 'error': 'Names are required'}) return JsonResponse({'success': False, 'error': 'Names are required'})
# Slug generation (simple) base_slug = slugify(name_en)
base_slug = name_en.lower().replace(' ', '-') if not base_slug:
base_slug = f"cat-{timezone.now().strftime('%Y%m%d%H%M%S')}"
slug = base_slug slug = base_slug
counter = 1 counter = 1
while Category.objects.filter(slug=slug).exists(): while Category.objects.filter(slug=slug).exists():
@ -101,6 +201,7 @@ def add_category_ajax(request):
Category.objects.create(name_en=name_en, name_ar=name_ar, slug=slug) Category.objects.create(name_en=name_en, name_ar=name_ar, slug=slug)
return JsonResponse({'success': True}) return JsonResponse({'success': True})
except Exception as e: except Exception as e:
logger.error(f"Error adding category: {e}")
return JsonResponse({'success': False, 'error': str(e)}) return JsonResponse({'success': False, 'error': str(e)})
@csrf_exempt @csrf_exempt
@ -108,11 +209,16 @@ def add_category_ajax(request):
def add_unit_ajax(request): def add_unit_ajax(request):
if request.method != 'POST': if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid method'}) return JsonResponse({'success': False, 'error': 'Invalid method'})
try:
try: try:
data = json.loads(request.body) data = json.loads(request.body)
name_en = data.get('name_en') name_en = data.get('name_en')
name_ar = data.get('name_ar') name_ar = data.get('name_ar')
short_name = data.get('short_name') short_name = data.get('short_name')
except (json.JSONDecodeError, TypeError):
name_en = request.POST.get('name_en')
name_ar = request.POST.get('name_ar')
short_name = request.POST.get('short_name')
if not name_en or not name_ar or not short_name: if not name_en or not name_ar or not short_name:
return JsonResponse({'success': False, 'error': 'All fields are required'}) return JsonResponse({'success': False, 'error': 'All fields are required'})
@ -120,6 +226,7 @@ def add_unit_ajax(request):
Unit.objects.create(name_en=name_en, name_ar=name_ar, short_name=short_name) Unit.objects.create(name_en=name_en, name_ar=name_ar, short_name=short_name)
return JsonResponse({'success': True}) return JsonResponse({'success': True})
except Exception as e: except Exception as e:
logger.error(f"Error adding unit: {e}")
return JsonResponse({'success': False, 'error': str(e)}) return JsonResponse({'success': False, 'error': str(e)})
@login_required @login_required
@ -129,8 +236,17 @@ def add_category(request):
name_en = request.POST.get('name_en') name_en = request.POST.get('name_en')
name_ar = request.POST.get('name_ar') name_ar = request.POST.get('name_ar')
if name_en and name_ar: if name_en and name_ar:
Category.objects.create(name_en=name_en, name_ar=name_ar, slug=name_en.lower().replace(' ', '-')) try:
base_slug = slugify(name_en) or f"cat-{timezone.now().timestamp()}"
slug = base_slug
counter = 1
while Category.objects.filter(slug=slug).exists():
slug = f"{base_slug}-{counter}"
counter += 1
Category.objects.create(name_en=name_en, name_ar=name_ar, slug=slug)
messages.success(request, "Category added!") messages.success(request, "Category added!")
except Exception as e:
messages.error(request, f"Error: {e}")
return redirect('inventory') return redirect('inventory')
@login_required @login_required
@ -152,12 +268,15 @@ def delete_category(request, pk):
@login_required @login_required
def add_unit(request): def add_unit(request):
if request.method == 'POST': if request.method == 'POST':
try:
Unit.objects.create( Unit.objects.create(
name_en=request.POST.get('name_en'), name_en=request.POST.get('name_en'),
name_ar=request.POST.get('name_ar'), name_ar=request.POST.get('name_ar'),
short_name=request.POST.get('short_name') short_name=request.POST.get('short_name')
) )
messages.success(request, "Unit added!") messages.success(request, "Unit added!")
except Exception as e:
messages.error(request, f"Error: {e}")
return redirect('inventory') return redirect('inventory')
@login_required @login_required
@ -316,12 +435,10 @@ def create_sale_api(request):
) )
sale.subtotal = subtotal sale.subtotal = subtotal
# VAT calc (simplified)
sale.total_amount = subtotal - decimal.Decimal(str(sale.discount)) sale.total_amount = subtotal - decimal.Decimal(str(sale.discount))
sale.paid_amount = sale.total_amount # Full payment assumed for POS sale.paid_amount = sale.total_amount # POS full payment
sale.save() sale.save()
# Record Payment
SalePayment.objects.create( SalePayment.objects.create(
sale=sale, sale=sale,
amount=sale.paid_amount, amount=sale.paid_amount,
@ -342,7 +459,8 @@ def invoice_list(request):
return render(request, 'core/invoices.html', { return render(request, 'core/invoices.html', {
'sales': paginator.get_page(request.GET.get('page')), 'sales': paginator.get_page(request.GET.get('page')),
'customers': Customer.objects.all(), 'customers': Customer.objects.all(),
'site_settings': SystemSetting.objects.first() 'site_settings': SystemSetting.objects.first(),
'payment_methods': PaymentMethod.objects.filter(is_active=True)
}) })
@login_required @login_required
@ -355,7 +473,6 @@ def settings_view(request):
form = SystemSettingForm(request.POST, request.FILES, instance=settings) form = SystemSettingForm(request.POST, request.FILES, instance=settings)
if form.is_valid(): if form.is_valid():
s = form.save(commit=False) s = form.save(commit=False)
# Fix nulls
if not s.wablas_server_url: s.wablas_server_url = '' if not s.wablas_server_url: s.wablas_server_url = ''
if not s.wablas_secret_key: s.wablas_secret_key = '' if not s.wablas_secret_key: s.wablas_secret_key = ''
if not s.wablas_token: s.wablas_token = '' if not s.wablas_token: s.wablas_token = ''
@ -378,139 +495,352 @@ def suggest_sku(request):
return JsonResponse({'sku': 'SKU-' + timezone.now().strftime("%Y%m%d%H%M%S")}) return JsonResponse({'sku': 'SKU-' + timezone.now().strftime("%Y%m%d%H%M%S")})
@login_required @login_required
def customer_statement(request): return render(request, 'core/customer_statement.html') def barcode_labels(request):
@login_required products = Product.objects.filter(is_active=True).order_by('name_en')
def supplier_statement(request): return render(request, 'core/supplier_statement.html') return render(request, 'core/barcode_labels.html', {'products': products})
@login_required
def cashflow_report(request): return render(request, 'core/cashflow_report.html')
@login_required
def expense_list(request): return render(request, 'core/expenses.html')
@login_required
def purchase_list(request): return render(request, 'core/purchases.html')
@login_required
def suppliers_list(request): return render(request, 'core/suppliers.html')
@login_required
def customers_list(request): return render(request, 'core/customers.html')
# Device Stubs
@login_required
def add_device(request): return redirect('settings')
@login_required
def edit_device(request, pk): return redirect('settings')
@login_required
def delete_device(request, pk): return redirect('settings')
# POS Sync Stubs
@csrf_exempt
def pos_sync_update(request): return JsonResponse({'status': 'ok'})
@csrf_exempt
def pos_sync_state(request): return JsonResponse({'state': {}})
# Helper for other views
@login_required
def customer_display(request): return render(request, 'core/customer_display.html')
@login_required
def barcode_labels(request): return render(request, 'core/barcodes.html')
# --- Customers --- # --- Customers ---
@login_required @login_required
def customers(request): return render(request, 'core/customers.html') def customers(request):
@login_required customers = Customer.objects.all().order_by('name')
def add_customer(request): return redirect('customers') paginator = Paginator(customers, 25)
@login_required return render(request, 'core/customers.html', {
def edit_customer(request, pk): return redirect('customers') 'customers': paginator.get_page(request.GET.get('page'))
@login_required })
def delete_customer(request, pk): return redirect('customers')
@csrf_exempt @csrf_exempt
@login_required @login_required
def add_customer_ajax(request): return JsonResponse({'success': True}) def add_customer_ajax(request):
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid method'})
try:
try:
data = json.loads(request.body)
name = data.get('name')
phone = data.get('phone')
except:
name = request.POST.get('name')
phone = request.POST.get('phone')
if not name:
return JsonResponse({'success': False, 'error': 'Name is required'})
Customer.objects.create(name=name, phone=phone or '')
return JsonResponse({'success': True})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@login_required
def add_customer(request):
if request.method == 'POST':
form = CustomerForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, "Customer added")
else:
messages.error(request, "Error adding customer")
return redirect('customers')
@login_required
def edit_customer(request, pk):
c = get_object_or_404(Customer, pk=pk)
if request.method == 'POST':
c.name = request.POST.get('name')
c.phone = request.POST.get('phone')
c.email = request.POST.get('email')
c.address = request.POST.get('address')
c.save()
messages.success(request, "Customer updated")
return redirect('customers')
@login_required
def delete_customer(request, pk):
get_object_or_404(Customer, pk=pk).delete()
messages.success(request, "Customer deleted")
return redirect('customers')
# --- Suppliers --- # --- Suppliers ---
@login_required @login_required
def suppliers(request): return render(request, 'core/suppliers.html') def suppliers(request):
@login_required suppliers = Supplier.objects.all().order_by('name')
def add_supplier(request): return redirect('suppliers') paginator = Paginator(suppliers, 25)
@login_required return render(request, 'core/suppliers.html', {
def edit_supplier(request, pk): return redirect('suppliers') 'suppliers': paginator.get_page(request.GET.get('page')),
@login_required 'site_settings': SystemSetting.objects.first()
def delete_supplier(request, pk): return redirect('suppliers') })
@csrf_exempt @csrf_exempt
@login_required @login_required
def add_supplier_ajax(request): return JsonResponse({'success': True}) def add_supplier_ajax(request):
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid method'})
try:
try:
data = json.loads(request.body)
name = data.get('name')
contact_person = data.get('contact_person')
phone = data.get('phone')
except:
name = request.POST.get('name')
contact_person = request.POST.get('contact_person')
phone = request.POST.get('phone')
if not name:
return JsonResponse({'success': False, 'error': 'Name is required'})
Supplier.objects.create(name=name, contact_person=contact_person or '', phone=phone or '')
return JsonResponse({'success': True})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@login_required
def add_supplier(request):
if request.method == 'POST':
form = SupplierForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, "Supplier added")
return redirect('suppliers')
@login_required
def edit_supplier(request, pk):
s = get_object_or_404(Supplier, pk=pk)
if request.method == 'POST':
s.name = request.POST.get('name')
s.contact_person = request.POST.get('contact_person')
s.phone = request.POST.get('phone')
s.save()
messages.success(request, "Supplier updated")
return redirect('suppliers')
@login_required
def delete_supplier(request, pk):
get_object_or_404(Supplier, pk=pk).delete()
messages.success(request, "Supplier deleted")
return redirect('suppliers')
# --- Purchases --- # --- Purchases ---
@login_required @login_required
def purchases(request): return render(request, 'core/purchases.html') def purchases(request):
purchases = Purchase.objects.all().order_by('-created_at')
paginator = Paginator(purchases, 25)
return render(request, 'core/purchases.html', {
'purchases': paginator.get_page(request.GET.get('page')),
'payment_methods': PaymentMethod.objects.filter(is_active=True)
})
@login_required @login_required
def purchase_create(request): return render(request, 'core/purchase_create.html') def purchase_create(request):
@login_required return render(request, 'core/purchase_create.html', {
def purchase_detail(request, pk): return render(request, 'core/purchase_detail.html') 'suppliers': Supplier.objects.all(),
@login_required 'products': Product.objects.all(),
def edit_purchase(request, pk): return redirect('purchases') 'payment_methods': PaymentMethod.objects.filter(is_active=True),
@login_required 'site_settings': SystemSetting.objects.first()
def delete_purchase(request, pk): return redirect('purchases') })
@login_required
def add_purchase_payment(request, pk): return redirect('purchases')
@login_required
def supplier_payments(request): return render(request, 'core/supplier_payments.html')
@csrf_exempt @csrf_exempt
def create_purchase_api(request): return JsonResponse({'success': True}) @login_required
@csrf_exempt def create_purchase_api(request):
def update_purchase_api(request, pk): return JsonResponse({'success': True}) if request.method != 'POST': return JsonResponse({'success': False, 'error': 'Invalid method'})
try:
data = json.loads(request.body)
items = data.get('items', [])
if not items: return JsonResponse({'success': False, 'error': 'No items'})
with transaction.atomic():
purchase = Purchase.objects.create(
supplier_id=data.get('supplier_id'),
invoice_number=data.get('invoice_number', ''),
total_amount=0,
created_by=request.user,
notes=data.get('notes', ''),
status='paid' if data.get('payment_amount') else 'unpaid' # simplified
)
total = 0
for item in items:
qty = decimal.Decimal(str(item['quantity']))
cost = decimal.Decimal(str(item.get('price', 0)))
line = qty * cost
total += line
# Update product cost and stock
prod = Product.objects.get(pk=item['id'])
prod.cost_price = cost # Last purchase price logic
prod.stock_quantity += qty
prod.save()
PurchaseItem.objects.create(
purchase=purchase,
product=prod,
quantity=qty,
cost_price=cost,
line_total=line
)
purchase.total_amount = total
# Handle payment
pay_amount = decimal.Decimal(str(data.get('payment_amount', 0)))
purchase.paid_amount = pay_amount
purchase.balance_due = total - pay_amount
purchase.status = 'paid' if purchase.balance_due <= 0 else ('partial' if pay_amount > 0 else 'unpaid')
purchase.save()
if pay_amount > 0:
PurchasePayment.objects.create(
purchase=purchase,
amount=pay_amount,
payment_method_id=data.get('payment_method_id'),
created_by=request.user
)
return JsonResponse({'success': True, 'purchase_id': purchase.id})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@login_required
def purchase_detail(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/purchase_detail.html', {
'purchase': purchase,
'settings': settings,
'payment_methods': PaymentMethod.objects.filter(is_active=True)
})
@login_required
def delete_purchase(request, pk):
get_object_or_404(Purchase, pk=pk).delete()
messages.success(request, "Purchase deleted")
return redirect('purchases')
@login_required
def edit_purchase(request, pk):
# Stub for now
return redirect('purchases')
# --- Quotations --- # --- Quotations ---
@login_required @login_required
def quotations(request): return render(request, 'core/quotations.html') def quotations(request):
quotations = Quotation.objects.all().order_by('-created_at')
return render(request, 'core/quotations.html', {'quotations': quotations})
@login_required @login_required
def quotation_create(request): return render(request, 'core/quotation_create.html') def quotation_create(request):
@login_required return render(request, 'core/quotation_create.html', {
def quotation_detail(request, pk): return render(request, 'core/quotation_detail.html') 'customers': Customer.objects.all(),
@login_required 'products': Product.objects.all(),
def convert_quotation_to_invoice(request, pk): return redirect('invoices') 'site_settings': SystemSetting.objects.first()
@login_required })
def delete_quotation(request, pk): return redirect('quotations')
@csrf_exempt @csrf_exempt
def create_quotation_api(request): return JsonResponse({'success': True}) @login_required
def create_quotation_api(request):
if request.method != 'POST': return JsonResponse({'success': False})
try:
data = json.loads(request.body)
with transaction.atomic():
q = Quotation.objects.create(
customer_id=data.get('customer_id'),
total_amount=0,
created_by=request.user,
notes=data.get('notes', ''),
quotation_number=f"QT-{timezone.now().strftime('%Y%m%d%H%M%S')}"
)
total = 0
for item in data.get('items', []):
qty = decimal.Decimal(str(item['quantity']))
price = decimal.Decimal(str(item['price']))
line = qty * price
total += line
QuotationItem.objects.create(quotation=q, product_id=item['id'], quantity=qty, unit_price=price, line_total=line)
q.total_amount = total
q.save()
return JsonResponse({'success': True, 'quotation_id': q.id})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@login_required
def quotation_detail(request, pk):
quotation = get_object_or_404(Quotation, pk=pk)
return render(request, 'core/quotation_detail.html', {
'quotation': quotation,
'settings': SystemSetting.objects.first(),
'amount_in_words': number_to_words_en(quotation.total_amount)
})
@login_required
def convert_quotation_to_invoice(request, pk):
# Logic to convert quotation to sale would go here
# For now, just stub it
messages.info(request, "Conversion logic not yet fully implemented")
return redirect('quotations')
@login_required
def delete_quotation(request, pk):
get_object_or_404(Quotation, pk=pk).delete()
return redirect('quotations')
# --- Invoices (Sales) --- # --- Invoices (Sales) ---
@login_required @login_required
def invoice_create(request): return render(request, 'core/invoice_create.html') def invoice_create(request):
settings = SystemSetting.objects.first()
context = {
'products': Product.objects.filter(is_active=True),
'customers': Customer.objects.all(),
'payment_methods': PaymentMethod.objects.filter(is_active=True),
'site_settings': settings,
'decimal_places': settings.decimal_places if settings else 3
}
return render(request, 'core/invoice_create.html', context)
@login_required @login_required
def invoice_detail(request, pk): return render(request, 'core/invoice_detail.html') def invoice_detail(request, pk):
sale = get_object_or_404(Sale, pk=pk)
settings = SystemSetting.objects.first()
amount_in_words = number_to_words_en(sale.total_amount)
return render(request, 'core/invoice_detail.html', {
'sale': sale,
'settings': settings,
'amount_in_words': amount_in_words,
'payment_methods': PaymentMethod.objects.filter(is_active=True)
})
@login_required @login_required
def add_sale_payment(request, pk): return redirect('invoices') def delete_sale(request, pk):
@login_required get_object_or_404(Sale, pk=pk).delete()
def delete_sale(request, pk): return redirect('invoices') return redirect('invoices')
@login_required
def sale_receipt(request, pk): return render(request, 'core/sale_receipt.html')
@login_required
def edit_invoice(request, pk): return redirect('invoices')
@csrf_exempt @csrf_exempt
def update_sale_api(request, pk): return JsonResponse({'success': True}) def update_sale_api(request, pk): return JsonResponse({'success': True})
@login_required
def customer_payments(request): return render(request, 'core/customer_payments.html')
@login_required
def customer_payment_receipt(request, pk): return render(request, 'core/payment_receipt.html')
# --- Held Sales ---
@csrf_exempt
def hold_sale_api(request): return JsonResponse({'success': True})
@csrf_exempt
def get_held_sales_api(request): return JsonResponse({'sales': []})
@csrf_exempt
def recall_held_sale_api(request, pk): return JsonResponse({'success': True})
@csrf_exempt
def delete_held_sale_api(request, pk): return JsonResponse({'success': True})
# --- Expenses --- # --- Expenses ---
@login_required @login_required
def expenses_view(request): return render(request, 'core/expenses.html') def expenses_view(request):
expenses = Expense.objects.all().order_by('-date')
return render(request, 'core/expenses.html', {
'expenses': expenses,
'categories': ExpenseCategory.objects.all(),
'payment_methods': PaymentMethod.objects.all()
})
@login_required @login_required
def expense_create_view(request): return redirect('expenses') def expense_create_view(request):
if request.method == 'POST':
form = ExpenseForm(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.success(request, "Expense added")
return redirect('expenses')
return redirect('expenses')
@login_required @login_required
def expense_edit_view(request, pk): return redirect('expenses') def expense_edit_view(request, pk): return redirect('expenses')
@login_required @login_required
def expense_delete_view(request, pk): return redirect('expenses') def expense_delete_view(request, pk):
get_object_or_404(Expense, pk=pk).delete()
return redirect('expenses')
@login_required @login_required
def expense_categories_view(request): return render(request, 'core/expense_categories.html') def expense_categories_view(request): return render(request, 'core/expense_categories.html')
@login_required @login_required
@ -544,21 +874,223 @@ def test_whatsapp_connection(request): return JsonResponse({'success': True})
# --- LPO --- # --- LPO ---
@login_required @login_required
def lpo_list(request): return render(request, 'core/lpo_list.html') def lpo_list(request): return render(request, 'core/lpo_list.html', {'lpos': PurchaseOrder.objects.all()})
@login_required @login_required
def lpo_create(request): return render(request, 'core/lpo_create.html') def lpo_create(request):
return render(request, 'core/lpo_create.html', {
'suppliers': Supplier.objects.all(),
'products': Product.objects.all(),
'site_settings': SystemSetting.objects.first()
})
@csrf_exempt
@login_required @login_required
def lpo_detail(request, pk): return render(request, 'core/lpo_detail.html') def create_lpo_api(request):
if request.method != 'POST': return JsonResponse({'success': False})
try:
data = json.loads(request.body)
with transaction.atomic():
lpo = PurchaseOrder.objects.create(
supplier_id=data.get('supplier_id'),
total_amount=0,
created_by=request.user,
lpo_number=f"LPO-{timezone.now().strftime('%Y%m%d%H%M%S')}"
)
total = 0
for item in data.get('items', []):
qty = decimal.Decimal(str(item['quantity']))
cost = decimal.Decimal(str(item.get('price', 0)))
line = qty * cost
total += line
PurchaseOrderItem.objects.create(purchase_order=lpo, product_id=item['id'], quantity=qty, cost_price=cost, line_total=line)
lpo.total_amount = total
lpo.save()
return JsonResponse({'success': True, 'lpo_id': lpo.id})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@login_required
def lpo_detail(request, pk):
lpo = get_object_or_404(PurchaseOrder, pk=pk)
return render(request, 'core/lpo_detail.html', {
'lpo': lpo,
'settings': SystemSetting.objects.first()
})
@login_required @login_required
def convert_lpo_to_purchase(request, pk): return redirect('lpo_list') def convert_lpo_to_purchase(request, pk): return redirect('lpo_list')
@login_required @login_required
def lpo_delete(request, pk): return redirect('lpo_list') def lpo_delete(request, pk):
get_object_or_404(PurchaseOrder, pk=pk).delete()
return redirect('lpo_list')
# --- Sales Returns ---
@login_required
def sales_returns(request): return render(request, 'core/sales_returns.html', {'returns': SaleReturn.objects.all()})
@login_required
def sale_return_create(request): return render(request, 'core/sale_return_create.html')
@login_required
def sale_return_detail(request, pk):
sale_return = get_object_or_404(SaleReturn, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/sale_return_detail.html', {
'sale_return': sale_return,
'settings': settings
})
@login_required
def delete_sale_return(request, pk): return redirect('sales_returns')
@csrf_exempt @csrf_exempt
def create_lpo_api(request): return JsonResponse({'success': True}) def create_sale_return_api(request): return JsonResponse({'success': True})
# --- Purchase Returns ---
@login_required
def purchase_returns(request): return render(request, 'core/purchase_returns.html', {'returns': PurchaseReturn.objects.all()})
@login_required
def purchase_return_create(request): return render(request, 'core/purchase_return_create.html')
@login_required
def purchase_return_detail(request, pk):
purchase_return = get_object_or_404(PurchaseReturn, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/purchase_return_detail.html', {
'purchase_return': purchase_return,
'settings': settings
})
@login_required
def delete_purchase_return(request, pk): return redirect('purchase_returns')
@csrf_exempt
def create_purchase_return_api(request): return JsonResponse({'success': True})
# --- Other Stubs ---
@login_required
def customer_statement(request): return render(request, 'core/customer_statement.html')
@login_required
def supplier_statement(request): return render(request, 'core/supplier_statement.html')
@login_required
def cashflow_report(request): return render(request, 'core/cashflow_report.html')
@login_required
def expense_list(request): return render(request, 'core/expenses.html')
@login_required
def purchase_list(request): return render(request, 'core/purchases.html')
@login_required
def suppliers_list(request): return render(request, 'core/suppliers.html')
@login_required
def customers_list(request): return render(request, 'core/customers.html')
@login_required
def add_device(request): return redirect('settings')
@login_required
def edit_device(request, pk): return redirect('settings')
@login_required
def delete_device(request, pk): return redirect('settings')
@csrf_exempt
def pos_sync_update(request): return JsonResponse({'status': 'ok'})
@csrf_exempt
def pos_sync_state(request): return JsonResponse({'state': {}})
@login_required
def customer_display(request): return render(request, 'core/customer_display.html')
@login_required
def supplier_payments(request): return render(request, 'core/supplier_payments.html')
@csrf_exempt
def update_purchase_api(request, pk): return JsonResponse({'success': True})
@login_required
def add_sale_payment(request, pk):
sale = get_object_or_404(Sale, pk=pk)
if request.method == 'POST':
try:
amount = decimal.Decimal(request.POST.get('amount', 0))
payment_method_id = request.POST.get('payment_method_id')
notes = request.POST.get('notes', '')
if amount > 0:
with transaction.atomic():
SalePayment.objects.create(
sale=sale,
amount=amount,
payment_method_id=payment_method_id,
created_by=request.user,
notes=notes
)
# Recalculate totals
total_paid = SalePayment.objects.filter(sale=sale).aggregate(Sum('amount'))['amount__sum'] or 0
sale.paid_amount = total_paid
sale.balance_due = sale.total_amount - total_paid
if sale.balance_due <= 0:
sale.status = 'paid'
elif sale.paid_amount > 0:
sale.status = 'partial'
else:
sale.status = 'unpaid'
sale.save()
messages.success(request, f"Payment of {amount} recorded successfully.")
else:
messages.error(request, "Amount must be greater than 0.")
except Exception as e:
messages.error(request, f"Error recording payment: {e}")
return redirect('invoices')
@login_required
def sale_receipt(request, pk): return render(request, 'core/sale_receipt.html')
@login_required
def edit_invoice(request, pk): return redirect('invoices')
@login_required
def customer_payments(request): return render(request, 'core/customer_payments.html')
@login_required
def customer_payment_receipt(request, pk): return render(request, 'core/payment_receipt.html')
@csrf_exempt
def hold_sale_api(request): return JsonResponse({'success': True})
@csrf_exempt
def get_held_sales_api(request): return JsonResponse({'sales': []})
@csrf_exempt
def recall_held_sale_api(request, pk): return JsonResponse({'success': True})
@csrf_exempt
def delete_held_sale_api(request, pk): return JsonResponse({'success': True})
@login_required
def add_purchase_payment(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
if request.method == 'POST':
try:
amount = decimal.Decimal(request.POST.get('amount', 0))
payment_method_id = request.POST.get('payment_method_id')
notes = request.POST.get('notes', '')
if amount > 0:
with transaction.atomic():
PurchasePayment.objects.create(
purchase=purchase,
amount=amount,
payment_method_id=payment_method_id,
created_by=request.user,
notes=notes
)
# Recalculate totals
total_paid = PurchasePayment.objects.filter(purchase=purchase).aggregate(Sum('amount'))['amount__sum'] or 0
purchase.paid_amount = total_paid
purchase.balance_due = purchase.total_amount - total_paid
if purchase.balance_due <= 0:
purchase.status = 'paid'
elif purchase.paid_amount > 0:
purchase.status = 'partial'
else:
purchase.status = 'unpaid'
purchase.save()
messages.success(request, f"Payment of {amount} recorded successfully.")
else:
messages.error(request, "Amount must be greater than 0.")
except Exception as e:
messages.error(request, f"Error recording payment: {e}")
return redirect('purchases')
@login_required @login_required
def cashier_registry(request): return render(request, 'core/cashier_registry.html') def cashier_registry(request): return render(request, 'core/cashier_registry.html')
# --- Sessions ---
@login_required @login_required
def cashier_session_list(request): return render(request, 'core/session_list.html') def cashier_session_list(request): return render(request, 'core/session_list.html')
@login_required @login_required
@ -567,53 +1099,19 @@ def start_session(request): return redirect('pos')
def close_session(request): return redirect('index') def close_session(request): return redirect('index')
@login_required @login_required
def session_detail(request, pk): return render(request, 'core/session_detail.html') def session_detail(request, pk): return render(request, 'core/session_detail.html')
# --- Reports ---
@login_required @login_required
def reports(request): return render(request, 'core/reports.html') def reports(request): return render(request, 'core/reports.html')
@login_required @login_required
def expense_report(request): return render(request, 'core/expense_report.html') def expense_report(request): return render(request, 'core/expense_report.html')
@login_required @login_required
def export_expenses_excel(request): return redirect('expenses') def export_expenses_excel(request): return redirect('expenses')
# --- Sales Returns ---
@login_required @login_required
def sales_returns(request): return render(request, 'core/sales_returns.html') def profile_view(request): return render(request, 'core/profile.html')
@login_required @login_required
def sale_return_create(request): return render(request, 'core/sale_return_create.html') def user_management(request): return render(request, 'core/user_management.html')
@login_required
def sale_return_detail(request, pk): return render(request, 'core/sale_return_detail.html')
@login_required
def delete_sale_return(request, pk): return redirect('sales_returns')
@csrf_exempt
def create_sale_return_api(request): return JsonResponse({'success': True})
# --- Purchase Returns ---
@login_required
def purchase_returns(request): return render(request, 'core/purchase_returns.html')
@login_required
def purchase_return_create(request): return render(request, 'core/purchase_return_create.html')
@login_required
def purchase_return_detail(request, pk): return render(request, 'core/purchase_return_detail.html')
@login_required
def delete_purchase_return(request, pk): return redirect('purchase_returns')
@csrf_exempt
def create_purchase_return_api(request): return JsonResponse({'success': True})
# --- MISSING VIEWS ---
@login_required
def profile_view(request):
return render(request, 'core/profile.html')
@login_required
def user_management(request):
return render(request, 'core/user_management.html')
@csrf_exempt @csrf_exempt
@login_required @login_required
def group_details_api(request, pk): def group_details_api(request, pk): return JsonResponse({'success': True, 'group': {}})
return JsonResponse({'success': True, 'group': {}})
@csrf_exempt @csrf_exempt
@login_required @login_required
def search_customers_api(request): def search_customers_api(request):

211
patch_payments_v2.py Normal file
View File

@ -0,0 +1,211 @@
import os
import decimal
from django.db import transaction
from django.db.models import Sum
file_path = 'core/views.py'
with open(file_path, 'r') as f:
content = f.read()
# 1. Update invoice_list
old_invoice_list = """@login_required
def invoice_list(request):
sales = Sale.objects.all().order_by('-created_at')
paginator = Paginator(sales, 25)
return render(request, 'core/invoices.html', {
'sales': paginator.get_page(request.GET.get('page')),
'customers': Customer.objects.all(),
'site_settings': SystemSetting.objects.first()
})"""
new_invoice_list = """@login_required
def invoice_list(request):
sales = Sale.objects.all().order_by('-created_at')
paginator = Paginator(sales, 25)
return render(request, 'core/invoices.html', {
'sales': paginator.get_page(request.GET.get('page')),
'customers': Customer.objects.all(),
'site_settings': SystemSetting.objects.first(),
'payment_methods': PaymentMethod.objects.filter(is_active=True)
})"""
if old_invoice_list in content:
content = content.replace(old_invoice_list, new_invoice_list)
else:
print("Could not find old_invoice_list")
# 2. Update purchases
old_purchases = """@login_required
def purchases(request):
purchases = Purchase.objects.all().order_by('-created_at')
paginator = Paginator(purchases, 25)
return render(request, 'core/purchases.html', {'purchases': paginator.get_page(request.GET.get('page'))})"""
new_purchases = """@login_required
def purchases(request):
purchases = Purchase.objects.all().order_by('-created_at')
paginator = Paginator(purchases, 25)
return render(request, 'core/purchases.html', {
'purchases': paginator.get_page(request.GET.get('page')),
'payment_methods': PaymentMethod.objects.filter(is_active=True)
})"""
if old_purchases in content:
content = content.replace(old_purchases, new_purchases)
else:
print("Could not find old_purchases")
# 3. Update invoice_detail
old_invoice_detail = """@login_required
def invoice_detail(request, pk):
sale = get_object_or_404(Sale, pk=pk)
settings = SystemSetting.objects.first()
amount_in_words = number_to_words_en(sale.total_amount)
return render(request, 'core/invoice_detail.html', {
'sale': sale,
'settings': settings,
'amount_in_words': amount_in_words
})"""
new_invoice_detail = """@login_required
def invoice_detail(request, pk):
sale = get_object_or_404(Sale, pk=pk)
settings = SystemSetting.objects.first()
amount_in_words = number_to_words_en(sale.total_amount)
return render(request, 'core/invoice_detail.html', {
'sale': sale,
'settings': settings,
'amount_in_words': amount_in_words,
'payment_methods': PaymentMethod.objects.filter(is_active=True)
})"""
if old_invoice_detail in content:
content = content.replace(old_invoice_detail, new_invoice_detail)
else:
print("Could not find old_invoice_detail")
# 4. Update purchase_detail
old_purchase_detail = """@login_required
def purchase_detail(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/purchase_detail.html', {
'purchase': purchase,
'settings': settings
})"""
new_purchase_detail = """@login_required
def purchase_detail(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/purchase_detail.html', {
'purchase': purchase,
'settings': settings,
'payment_methods': PaymentMethod.objects.filter(is_active=True)
})"""
if old_purchase_detail in content:
content = content.replace(old_purchase_detail, new_purchase_detail)
else:
print("Could not find old_purchase_detail")
# 5. Replace add_sale_payment stub
old_add_sale_payment = """@login_required
def add_sale_payment(request, pk): return redirect('invoices')"""
new_add_sale_payment = """@login_required
def add_sale_payment(request, pk):
sale = get_object_or_404(Sale, pk=pk)
if request.method == 'POST':
try:
amount = decimal.Decimal(request.POST.get('amount', 0))
payment_method_id = request.POST.get('payment_method_id')
notes = request.POST.get('notes', '')
if amount > 0:
with transaction.atomic():
SalePayment.objects.create(
sale=sale,
amount=amount,
payment_method_id=payment_method_id,
created_by=request.user,
notes=notes
)
# Recalculate totals
total_paid = SalePayment.objects.filter(sale=sale).aggregate(Sum('amount'))['amount__sum'] or 0
sale.paid_amount = total_paid
sale.balance_due = sale.total_amount - total_paid
if sale.balance_due <= 0:
sale.status = 'paid'
elif sale.paid_amount > 0:
sale.status = 'partial'
else:
sale.status = 'unpaid'
sale.save()
messages.success(request, f"Payment of {amount} recorded successfully.")
else:
messages.error(request, "Amount must be greater than 0.")
except Exception as e:
messages.error(request, f"Error recording payment: {e}")
return redirect('invoices')"""
if old_add_sale_payment in content:
content = content.replace(old_add_sale_payment, new_add_sale_payment)
else:
print("Could not find old_add_sale_payment")
# 6. Replace add_purchase_payment stub
old_add_purchase_payment = """@login_required
def add_purchase_payment(request, pk): return redirect('purchases')"""
new_add_purchase_payment = """@login_required
def add_purchase_payment(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
if request.method == 'POST':
try:
amount = decimal.Decimal(request.POST.get('amount', 0))
payment_method_id = request.POST.get('payment_method_id')
notes = request.POST.get('notes', '')
if amount > 0:
with transaction.atomic():
PurchasePayment.objects.create(
purchase=purchase,
amount=amount,
payment_method_id=payment_method_id,
created_by=request.user,
notes=notes
)
# Recalculate totals
total_paid = PurchasePayment.objects.filter(purchase=purchase).aggregate(Sum('amount'))['amount__sum'] or 0
purchase.paid_amount = total_paid
purchase.balance_due = purchase.total_amount - total_paid
if purchase.balance_due <= 0:
purchase.status = 'paid'
elif purchase.paid_amount > 0:
purchase.status = 'partial'
else:
purchase.status = 'unpaid'
purchase.save()
messages.success(request, f"Payment of {amount} recorded successfully.")
else:
messages.error(request, "Amount must be greater than 0.")
except Exception as e:
messages.error(request, f"Error recording payment: {e}")
return redirect('purchases')"""
if old_add_purchase_payment in content:
content = content.replace(old_add_purchase_payment, new_add_purchase_payment)
else:
print("Could not find old_add_purchase_payment")
with open(file_path, 'w') as f:
f.write(content)