38086-vm/core/views.py
2026-02-10 06:55:31 +00:00

1281 lines
43 KiB
Python

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.contrib.auth import update_session_auth_hash
from django.urls import reverse
from django.http import JsonResponse, HttpResponse
from django.core.paginator import Paginator
from django.db import transaction
from django.db.models import Sum, Q, Count, F
from django.db.models.functions import TruncMonth, TruncDay
from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from django.utils.translation import gettext as _
import json
import decimal
import datetime
import logging
from .models import (
SystemSetting, Customer, Supplier, Product, Category, Unit,
Sale, SaleItem, SalePayment, SaleReturn, SaleReturnItem,
Purchase, PurchaseItem, PurchasePayment, PurchaseReturn, PurchaseReturnItem,
Expense, ExpenseCategory, PaymentMethod, LoyaltyTier, LoyaltyTransaction,
Device, CashierSession, CashierCounterRegistry, PurchaseOrder, PurchaseOrderItem,
UserProfile, HeldSale, Quotation, QuotationItem
)
from .forms import (
SystemSettingForm, CustomerForm, SupplierForm, ProductForm, CategoryForm,
UnitForm, ExpenseForm, CashierSessionStartForm, CashierSessionCloseForm
)
from .utils import number_to_words_en
from .views_import import *
logger = logging.getLogger(__name__)
# --- Basic Views ---
@login_required
def index(request):
settings = SystemSetting.objects.first()
today = timezone.now().date()
# 1. Financials
total_sales_amount = Sale.objects.aggregate(Sum('total_amount'))['total_amount__sum'] or 0
total_receivables = Sale.objects.aggregate(Sum('balance_due'))['balance_due__sum'] or 0
total_payables = Purchase.objects.aggregate(Sum('balance_due'))['balance_due__sum'] or 0
# 2. Counts
total_sales_count = Sale.objects.count()
total_products = Product.objects.count()
total_customers = Customer.objects.count()
# 3. Charts: Monthly Sales (Last 12 months)
last_12_months = timezone.now() - datetime.timedelta(days=365)
monthly_sales = (Sale.objects.filter(created_at__gte=last_12_months)
.annotate(month=TruncMonth('created_at'))
.values('month')
.annotate(total=Sum('total_amount'))
.order_by('month'))
monthly_labels = [m['month'].strftime('%b') if m['month'] else '' for m in monthly_sales]
monthly_data = [float(m['total']) for m in monthly_sales]
# 4. Charts: Daily Sales (Last 7 days)
last_7_days = timezone.now() - datetime.timedelta(days=7)
daily_sales = (Sale.objects.filter(created_at__gte=last_7_days)
.annotate(day=TruncDay('created_at'))
.values('day')
.annotate(total=Sum('total_amount'))
.order_by('day'))
chart_labels = [d['day'].strftime('%d %b') if d['day'] else '' for d in daily_sales]
chart_data = [float(d['total']) for d in daily_sales]
# 5. Category Distribution
category_dist = (SaleItem.objects.values('product__category__name_en', 'product__category__name_ar')
.annotate(total=Sum('line_total'))
.order_by('-total')[:5])
category_labels = [c['product__category__name_en'] or 'Uncategorized' for c in category_dist]
category_data = [float(c['total']) for c in category_dist]
# 6. Payment Methods
payment_dist = (SalePayment.objects.values('payment_method__name_en')
.annotate(total=Sum('amount'))
.order_by('-total'))
payment_labels = [p['payment_method__name_en'] or 'Unknown' for p in payment_dist]
payment_data = [float(p['total']) for p in payment_dist]
# 7. Top 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_rev')[:5]
# 8. Low Stock & Expired
# Low stock
low_stock_qs = Product.objects.filter(stock_quantity__lte=F('min_stock_level'))
low_stock_count = low_stock_qs.count()
low_stock_products = low_stock_qs[:5] # Limit for display
# Expired
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')[:10]
context = {
'site_settings': settings,
'settings': settings, # Keep both for safety
'total_sales_amount': total_sales_amount,
'total_receivables': total_receivables,
'total_payables': total_payables,
'total_sales_count': total_sales_count,
'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_count': low_stock_count,
'low_stock_products': low_stock_products,
'expired_count': expired_count,
'recent_sales': recent_sales,
}
return render(request, 'core/index.html', context)
@login_required
def inventory(request):
products = Product.objects.all().order_by('name_en')
categories = Category.objects.all()
units = Unit.objects.all()
context = {
'products': products,
'categories': categories,
'units': units
}
return render(request, 'core/inventory.html', context)
@login_required
def customers(request):
customers = Customer.objects.all().order_by('name')
return render(request, 'core/customers.html', {'customers': customers})
@login_required
def suppliers(request):
suppliers = Supplier.objects.all().order_by('name')
return render(request, 'core/suppliers.html', {'suppliers': suppliers})
@login_required
def settings_view(request):
settings = SystemSetting.objects.first()
if not settings:
settings = SystemSetting.objects.create()
payment_methods = PaymentMethod.objects.filter(is_active=True)
expense_categories = ExpenseCategory.objects.all()
loyalty_tiers = LoyaltyTier.objects.all().order_by("min_points")
devices = Device.objects.all().order_by("name")
if request.method == 'POST':
form = SystemSettingForm(request.POST, request.FILES, instance=settings)
if form.is_valid():
form.save()
messages.success(request, "Settings updated.")
return redirect('settings')
else:
form = SystemSettingForm(instance=settings)
return render(request, 'core/settings.html', {
'form': form,
'settings': settings,
'payment_methods': payment_methods,
'expense_categories': expense_categories,
'loyalty_tiers': loyalty_tiers,
'devices': devices
})
@login_required
def profile_view(request):
user = request.user
# Ensure profile exists
UserProfile.objects.get_or_create(user=user)
if request.method == 'POST':
# Check if it's profile update or password update
if 'password' in request.POST and 'confirm_password' in request.POST:
password = request.POST.get('password')
confirm_password = request.POST.get('confirm_password')
if password:
if password == confirm_password:
user.set_password(password)
user.save()
update_session_auth_hash(request, user)
messages.success(request, _("Password updated successfully!"))
else:
messages.error(request, _("Passwords do not match."))
else:
# Profile Update
user.first_name = request.POST.get('first_name', user.first_name)
user.last_name = request.POST.get('last_name', user.last_name)
user.email = request.POST.get('email', user.email)
user.save()
profile = user.profile
profile.phone = request.POST.get('phone', profile.phone)
profile.bio = request.POST.get('bio', profile.bio)
if 'image' in request.FILES:
profile.image = request.FILES['image']
profile.save()
messages.success(request, _("Profile updated successfully!"))
return redirect('profile')
return render(request, 'core/profile.html')
@login_required
def user_management(request):
return render(request, 'core/users.html')
@login_required
def group_details_api(request, pk):
return JsonResponse({})
# --- POS Views ---
@login_required
def pos(request):
# Check for active session
active_session = CashierSession.objects.filter(user=request.user, status='active').first()
if not active_session:
# Check if user is a cashier (assigned to a counter)
if hasattr(request.user, 'counter_assignment'):
messages.warning(request, _("Please open a session to start selling."))
return redirect('start_session')
settings = SystemSetting.objects.first()
products = Product.objects.filter(is_active=True)
if settings and not settings.allow_zero_stock_sales:
products = products.filter(stock_quantity__gt=0)
customers = Customer.objects.all()
categories = Category.objects.all()
payment_methods = PaymentMethod.objects.filter(is_active=True)
# Ensure at least Cash exists
if not payment_methods.exists():
PaymentMethod.objects.create(name_en="Cash", name_ar="نقدي", is_active=True)
payment_methods = PaymentMethod.objects.filter(is_active=True)
context = {
'products': products,
'customers': customers,
'categories': categories,
'payment_methods': payment_methods,
'settings': settings,
'active_session': active_session
}
return render(request, 'core/pos.html', context)
@login_required
def customer_display(request):
return render(request, 'core/customer_display.html')
@csrf_exempt
def pos_sync_update(request):
return JsonResponse({'status': 'ok'})
@csrf_exempt
def pos_sync_state(request):
return JsonResponse({'state': {}})
# --- Sales / Invoices ---
@login_required
def invoice_list(request):
sales = Sale.objects.all().order_by('-created_at')
start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date')
if start_date:
sales = sales.filter(created_at__date__gte=start_date)
if end_date:
sales = sales.filter(created_at__date__lte=end_date)
customer_id = request.GET.get('customer')
if customer_id:
sales = sales.filter(customer_id=customer_id)
status = request.GET.get('status')
if status:
sales = sales.filter(status=status)
paginator = Paginator(sales, 25)
context = {
'sales': paginator.get_page(request.GET.get('page')),
'customers': Customer.objects.all(),
'payment_methods': PaymentMethod.objects.filter(is_active=True),
'site_settings': SystemSetting.objects.first(),
}
return render(request, 'core/invoices.html', context)
@login_required
def invoice_create(request):
return redirect('pos')
@login_required
def invoice_detail(request, pk):
sale = get_object_or_404(Sale, pk=pk)
return render(request, 'core/invoice_detail.html', {'sale': sale})
@login_required
def edit_invoice(request, pk):
sale = get_object_or_404(Sale, pk=pk)
customers = Customer.objects.all()
products = Product.objects.filter(is_active=True).select_related('category')
payment_methods = PaymentMethod.objects.filter(is_active=True)
site_settings = SystemSetting.objects.first()
decimal_places = 2
if site_settings:
decimal_places = site_settings.decimal_places
cart_items = []
for item in sale.items.all().select_related('product'):
cart_items.append({
'id': item.product.id,
'name_en': item.product.name_en,
'name_ar': item.product.name_ar,
'sku': item.product.sku,
'price': float(item.unit_price),
'quantity': float(item.quantity),
'stock': float(item.product.stock_quantity)
})
cart_json = json.dumps(cart_items)
payment_method_id = ""
first_payment = sale.payments.first()
if first_payment and first_payment.payment_method:
payment_method_id = first_payment.payment_method.id
context = {
'sale': sale,
'customers': customers,
'products': products,
'payment_methods': payment_methods,
'site_settings': site_settings,
'decimal_places': decimal_places,
'cart_json': cart_json,
'payment_method_id': payment_method_id
}
return render(request, 'core/invoice_edit.html', context)
@login_required
def delete_sale(request, pk):
sale = get_object_or_404(Sale, pk=pk)
# Restore stock
for item in sale.items.all():
item.product.stock_quantity += item.quantity
item.product.save()
sale.delete()
messages.success(request, _("Sale deleted successfully."))
return redirect('invoices')
@login_required
def add_sale_payment(request, pk):
sale = get_object_or_404(Sale, pk=pk)
if request.method == 'POST':
amount = decimal.Decimal(request.POST.get('amount', 0))
payment_method_id = request.POST.get('payment_method')
SalePayment.objects.create(
sale=sale,
amount=amount,
payment_method_id=payment_method_id,
created_by=request.user,
notes=request.POST.get('notes', '')
)
sale.update_balance()
messages.success(request, _("Payment added."))
return redirect('invoice_detail', pk=pk)
@login_required
def customer_payments(request):
payments = SalePayment.objects.select_related('sale', 'sale__customer').order_by('-payment_date', '-created_at')
paginator = Paginator(payments, 25)
page_number = request.GET.get('page')
payments = paginator.get_page(page_number)
return render(request, 'core/customer_payments.html', {'payments': payments})
@login_required
def customer_payment_receipt(request, pk):
payment = get_object_or_404(SalePayment, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/payment_receipt.html', {
'payment': payment,
'settings': settings,
'amount_in_words': number_to_words_en(payment.amount)
})
@login_required
def sale_receipt(request, pk):
sale = get_object_or_404(Sale, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/sale_receipt.html', {
'sale': sale,
'settings': settings
})
# --- Quotations ---
@login_required
def quotations(request):
quotations = Quotation.objects.all().order_by('-created_at')
return render(request, 'core/quotations.html', {'quotations': quotations})
@login_required
def quotation_create(request):
customers = Customer.objects.all()
products = Product.objects.filter(is_active=True)
return render(request, 'core/quotation_create.html', {'customers': customers, 'products': products})
@login_required
def quotation_detail(request, pk):
quotation = get_object_or_404(Quotation, pk=pk)
return render(request, 'core/quotation_detail.html', {'quotation': quotation})
@login_required
def convert_quotation_to_invoice(request, pk):
quotation = get_object_or_404(Quotation, pk=pk)
if quotation.status != 'converted':
# Create Sale from Quotation
with transaction.atomic():
sale = Sale.objects.create(
customer=quotation.customer,
quotation=quotation,
total_amount=quotation.total_amount,
discount=quotation.discount,
status='unpaid',
balance_due=quotation.total_amount,
created_by=request.user
)
for item in quotation.items.all():
SaleItem.objects.create(
sale=sale,
product=item.product,
quantity=item.quantity,
unit_price=item.unit_price,
line_total=item.line_total
)
# Deduct Stock
item.product.stock_quantity -= item.quantity
item.product.save()
quotation.status = 'converted'
quotation.save()
messages.success(request, _("Quotation converted to Invoice."))
return redirect('invoice_detail', pk=sale.pk)
return redirect('quotations')
@login_required
def delete_quotation(request, pk):
quotation = get_object_or_404(Quotation, pk=pk)
quotation.delete()
messages.success(request, _("Quotation deleted."))
return redirect('quotations')
@csrf_exempt
@login_required
def create_quotation_api(request):
# Simplified API stub
return JsonResponse({'success': True})
# --- Sales Returns ---
@login_required
def sales_returns(request):
returns = SaleReturn.objects.all().order_by('-created_at')
return render(request, 'core/sales_returns.html', {'returns': returns})
@login_required
def sale_return_create(request):
customers = Customer.objects.all()
products = Product.objects.filter(is_active=True)
return render(request, 'core/sale_return_create.html', {
'customers': customers,
'products': products
})
@login_required
def sale_return_detail(request, pk):
sale_return = get_object_or_404(SaleReturn, pk=pk)
return render(request, 'core/sale_return_detail.html', {'sale_return': sale_return})
@login_required
def delete_sale_return(request, pk):
sale_return = get_object_or_404(SaleReturn, pk=pk)
# Restore stock (reverse of return)
for item in sale_return.items.all():
item.product.stock_quantity -= item.quantity
item.product.save()
sale_return.delete()
messages.success(request, _("Sale Return deleted."))
return redirect('sales_returns')
# --- Purchases ---
@login_required
def purchases(request):
purchases = Purchase.objects.all().order_by('-created_at')
return render(request, 'core/purchases.html', {'purchases': purchases})
@login_required
def purchase_create(request):
suppliers = Supplier.objects.all()
products = Product.objects.filter(is_active=True)
return render(request, 'core/purchase_create.html', {'suppliers': suppliers, 'products': products})
@login_required
def purchase_detail(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
return render(request, 'core/purchase_detail.html', {'purchase': purchase})
@login_required
def edit_purchase(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
# Simplified edit view
return render(request, 'core/purchase_edit.html', {'purchase': purchase})
@login_required
def add_purchase_payment(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
if request.method == 'POST':
amount = decimal.Decimal(request.POST.get('amount', 0))
payment_method_id = request.POST.get('payment_method')
PurchasePayment.objects.create(
purchase=purchase,
amount=amount,
payment_method_id=payment_method_id,
created_by=request.user,
notes=request.POST.get('notes', '')
)
purchase.update_balance()
messages.success(request, _("Payment added."))
return redirect('purchase_detail', pk=pk)
@login_required
def delete_purchase(request, pk):
purchase = get_object_or_404(Purchase, pk=pk)
# Restore stock (reverse of purchase)
for item in purchase.items.all():
item.product.stock_quantity -= item.quantity
item.product.save()
purchase.delete()
messages.success(request, _("Purchase deleted."))
return redirect('purchases')
@login_required
def supplier_payments(request):
payments = PurchasePayment.objects.all().order_by('-payment_date')
return render(request, 'core/supplier_payments.html', {'payments': payments})
# --- Purchase Returns ---
@login_required
def purchase_returns(request):
returns = PurchaseReturn.objects.all().order_by('-created_at')
return render(request, 'core/purchase_returns.html', {'returns': returns})
@login_required
def purchase_return_create(request):
suppliers = Supplier.objects.all()
products = Product.objects.filter(is_active=True)
return render(request, 'core/purchase_return_create.html', {
'suppliers': suppliers,
'products': products
})
@login_required
def purchase_return_detail(request, pk):
purchase_return = get_object_or_404(PurchaseReturn, pk=pk)
return render(request, 'core/purchase_return_detail.html', {'purchase_return': purchase_return})
@login_required
def delete_purchase_return(request, pk):
purchase_return = get_object_or_404(PurchaseReturn, pk=pk)
# Restore stock
for item in purchase_return.items.all():
item.product.stock_quantity += item.quantity
item.product.save()
purchase_return.delete()
messages.success(request, _("Purchase Return deleted."))
return redirect('purchase_returns')
# --- Expenses ---
@login_required
def expenses_view(request):
expenses = Expense.objects.all().order_by('-date')
return render(request, 'core/expenses.html', {'expenses': expenses})
@login_required
def expense_create_view(request):
if request.method == 'POST':
form = ExpenseForm(request.POST, request.FILES)
if form.is_valid():
expense = form.save(commit=False)
expense.created_by = request.user
expense.save()
messages.success(request, _("Expense added."))
return redirect('expenses')
else:
form = ExpenseForm()
return render(request, 'core/expense_form.html', {'form': form})
@login_required
def expense_edit_view(request, pk):
expense = get_object_or_404(Expense, pk=pk)
if request.method == 'POST':
form = ExpenseForm(request.POST, request.FILES, instance=expense)
if form.is_valid():
form.save()
messages.success(request, _("Expense updated."))
return redirect('expenses')
else:
form = ExpenseForm(instance=expense)
return render(request, 'core/expense_form.html', {'form': form})
@login_required
def expense_delete_view(request, pk):
expense = get_object_or_404(Expense, pk=pk)
expense.delete()
messages.success(request, _("Expense deleted."))
return redirect('expenses')
@login_required
def expense_categories_view(request):
categories = ExpenseCategory.objects.all()
if request.method == 'POST':
name_en = request.POST.get('name_en')
name_ar = request.POST.get('name_ar')
ExpenseCategory.objects.create(name_en=name_en, name_ar=name_ar)
messages.success(request, _("Category added."))
return redirect('expense_categories')
return render(request, 'core/expense_categories.html', {'categories': categories})
@login_required
def expense_category_delete_view(request, pk):
category = get_object_or_404(ExpenseCategory, pk=pk)
category.delete()
messages.success(request, _("Category deleted."))
return redirect('expense_categories')
@login_required
def expense_report(request):
return render(request, 'core/expense_report.html')
@login_required
def export_expenses_excel(request):
return redirect('expenses')
# --- Reports ---
@login_required
def reports(request):
return render(request, 'core/reports.html')
@login_required
def customer_statement(request):
customers = Customer.objects.all().order_by('name')
selected_customer = None
sales = []
customer_id = request.GET.get('customer')
start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date')
if customer_id:
selected_customer = get_object_or_404(Customer, id=customer_id)
sales = Sale.objects.filter(customer=selected_customer).order_by('-created_at')
if start_date:
sales = sales.filter(created_at__date__gte=start_date)
if end_date:
sales = sales.filter(created_at__date__lte=end_date)
context = {
'customers': customers,
'selected_customer': selected_customer,
'sales': sales,
'start_date': start_date,
'end_date': end_date
}
return render(request, 'core/customer_statement.html', context)
@login_required
def supplier_statement(request):
suppliers = Supplier.objects.all().order_by('name')
selected_supplier = None
purchases = []
supplier_id = request.GET.get('supplier')
start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date')
if supplier_id:
selected_supplier = get_object_or_404(Supplier, id=supplier_id)
purchases = Purchase.objects.filter(supplier=selected_supplier).order_by('-created_at')
if start_date:
purchases = purchases.filter(created_at__date__gte=start_date)
if end_date:
purchases = purchases.filter(created_at__date__lte=end_date)
context = {
'suppliers': suppliers,
'selected_supplier': selected_supplier,
'purchases': purchases,
'start_date': start_date,
'end_date': end_date
}
return render(request, 'core/supplier_statement.html', context)
@login_required
def cashflow_report(request):
start_date = request.GET.get('start_date')
end_date = request.GET.get('end_date')
sales = Sale.objects.all()
expenses = Expense.objects.all()
purchases = Purchase.objects.all()
if start_date:
sales = sales.filter(created_at__date__gte=start_date)
expenses = expenses.filter(date__gte=start_date)
purchases = purchases.filter(created_at__date__gte=start_date)
if end_date:
sales = sales.filter(created_at__date__lte=end_date)
expenses = expenses.filter(date__lte=end_date)
purchases = purchases.filter(created_at__date__lte=end_date)
total_sales = sales.aggregate(total=Sum('total_amount'))['total'] or 0
total_expenses = expenses.aggregate(total=Sum('amount'))['total'] or 0
total_purchases = purchases.aggregate(total=Sum('total_amount'))['total'] or 0
net_profit = total_sales - total_expenses - total_purchases
context = {
'total_sales': total_sales,
'total_expenses': total_expenses,
'total_purchases': total_purchases,
'net_profit': net_profit,
'start_date': start_date,
'end_date': end_date
}
return render(request, 'core/cashflow_report.html', context)
# --- Inventory / System ---
@login_required
def add_product(request):
if request.method == 'POST':
form = ProductForm(request.POST, request.FILES)
if form.is_valid():
form.save()
messages.success(request, _("Product added."))
return redirect(reverse('inventory') + '#items')
return redirect('inventory')
@login_required
def edit_product(request, pk):
product = get_object_or_404(Product, pk=pk)
if request.method == 'POST':
form = ProductForm(request.POST, request.FILES, instance=product)
if form.is_valid():
form.save()
messages.success(request, _("Product updated."))
return redirect(reverse('inventory') + '#items')
return redirect('inventory')
@login_required
def delete_product(request, pk):
product = get_object_or_404(Product, pk=pk)
product.delete()
messages.success(request, _("Product deleted."))
return redirect(reverse('inventory') + '#items')
@login_required
def barcode_labels(request):
return render(request, 'core/barcode_labels.html')
@login_required
def suggest_sku(request):
return JsonResponse({'sku': f"SKU-{int(timezone.now().timestamp())}"})
@login_required
def add_category(request):
if request.method == 'POST':
Category.objects.create(
name_en=request.POST.get('name_en'),
name_ar=request.POST.get('name_ar'),
slug=f"cat-{int(timezone.now().timestamp())}"
)
return redirect('inventory')
@login_required
def edit_category(request, pk):
return redirect('inventory')
@login_required
def delete_category(request, pk):
return redirect('inventory')
@login_required
def add_unit(request):
if request.method == 'POST':
Unit.objects.create(
name_en=request.POST.get('name_en'),
name_ar=request.POST.get('name_ar'),
short_name=request.POST.get('short_name')
)
return redirect('inventory')
@login_required
def edit_unit(request, pk):
return redirect('inventory')
@login_required
def delete_unit(request, pk):
return redirect('inventory')
@login_required
def add_customer(request):
if request.method == 'POST':
Customer.objects.create(name=request.POST.get('name'), phone=request.POST.get('phone', ''))
return redirect('customers')
@login_required
def edit_customer(request, pk):
customer = get_object_or_404(Customer, pk=pk)
if request.method == 'POST':
customer.name = request.POST.get('name')
customer.phone = request.POST.get('phone')
customer.save()
return redirect('customers')
@login_required
def delete_customer(request, pk):
customer = get_object_or_404(Customer, pk=pk)
customer.delete()
return redirect('customers')
@login_required
def add_supplier(request):
if request.method == 'POST':
Supplier.objects.create(name=request.POST.get('name'), phone=request.POST.get('phone', ''))
return redirect('suppliers')
@login_required
def edit_supplier(request, pk):
supplier = get_object_or_404(Supplier, pk=pk)
if request.method == 'POST':
supplier.name = request.POST.get('name')
supplier.phone = request.POST.get('phone')
supplier.save()
return redirect('suppliers')
@login_required
def delete_supplier(request, pk):
supplier = get_object_or_404(Supplier, pk=pk)
supplier.delete()
return redirect('suppliers')
@login_required
def add_payment_method(request):
if request.method == 'POST':
PaymentMethod.objects.create(name_en=request.POST.get('name_en'), name_ar=request.POST.get('name_ar'))
return redirect('settings')
@login_required
def edit_payment_method(request, pk):
return redirect('settings')
@login_required
def delete_payment_method(request, pk):
return redirect('settings')
@login_required
def add_loyalty_tier(request):
return redirect('settings')
@login_required
def edit_loyalty_tier(request, pk):
return redirect('settings')
@login_required
def delete_loyalty_tier(request, pk):
return redirect('settings')
@login_required
def add_device(request):
if request.method == 'POST':
Device.objects.create(
name=request.POST.get('name'),
device_type=request.POST.get('device_type'),
connection_type=request.POST.get('connection_type'),
ip_address=request.POST.get('ip_address'),
port=request.POST.get('port') or None,
is_active=request.POST.get('is_active') == 'on'
)
return redirect(reverse('settings') + '#devices')
@login_required
def edit_device(request, pk):
device = get_object_or_404(Device, pk=pk)
if request.method == 'POST':
device.name = request.POST.get('name')
device.device_type = request.POST.get('device_type')
device.connection_type = request.POST.get('connection_type')
device.ip_address = request.POST.get('ip_address')
device.port = request.POST.get('port') or None
device.is_active = request.POST.get('is_active') == 'on'
device.save()
return redirect(reverse('settings') + '#devices')
@login_required
def delete_device(request, pk):
device = get_object_or_404(Device, pk=pk)
device.delete()
return redirect(reverse('settings') + '#devices')
@login_required
def test_whatsapp_connection(request):
return JsonResponse({'success': True, 'message': 'Connected'})
@login_required
def send_invoice_whatsapp(request):
return JsonResponse({'success': True})
# --- LPO ---
@login_required
def lpo_list(request):
lpos = PurchaseOrder.objects.all().order_by('-created_at')
return render(request, 'core/lpo_list.html', {'lpos': lpos})
@login_required
def lpo_create(request):
suppliers = Supplier.objects.all()
products = Product.objects.filter(is_active=True)
return render(request, 'core/lpo_create.html', {'suppliers': suppliers, 'products': products})
@login_required
def lpo_detail(request, pk):
lpo = get_object_or_404(PurchaseOrder, pk=pk)
settings = SystemSetting.objects.first()
return render(request, 'core/lpo_detail.html', {'lpo': lpo, 'settings': settings})
@login_required
def convert_lpo_to_purchase(request, pk):
return redirect('purchases')
@login_required
def lpo_delete(request, pk):
lpo = get_object_or_404(PurchaseOrder, pk=pk)
lpo.delete()
return redirect('lpo_list')
@csrf_exempt
@login_required
def create_lpo_api(request):
return JsonResponse({'success': True})
# --- Cashier / Sessions ---
@login_required
def cashier_registry(request):
registries = CashierCounterRegistry.objects.all()
return render(request, 'core/cashier_registry.html', {'registries': registries})
@login_required
def cashier_session_list(request):
sessions = CashierSession.objects.all().order_by('-start_time')
return render(request, 'core/session_list.html', {'sessions': sessions})
@login_required
def start_session(request):
if request.method == 'POST':
CashierSession.objects.create(user=request.user, opening_balance=request.POST.get('opening_balance', 0))
return redirect('pos')
return render(request, 'core/start_session.html')
@login_required
def close_session(request):
session = CashierSession.objects.filter(user=request.user, status='active').first()
if request.method == 'POST' and session:
session.closing_balance = request.POST.get('closing_balance', 0)
session.status = 'closed'
session.end_time = timezone.now()
session.save()
return redirect('index')
return render(request, 'core/close_session.html', {'session': session})
@login_required
def session_detail(request, pk):
session = get_object_or_404(CashierSession, pk=pk)
return render(request, 'core/session_detail.html', {'session': session})
# --- APIs ---
@csrf_exempt
@login_required
def create_sale_api(request):
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid request'})
try:
data = json.loads(request.body)
with transaction.atomic():
sale = Sale.objects.create(
customer_id=data.get('customer_id') or None,
total_amount=data.get('total_amount', 0),
paid_amount=data.get('paid_amount', 0),
payment_type=data.get('payment_type', 'cash'),
created_by=request.user,
status='paid' if data.get('payment_type') == 'cash' else 'partial'
)
for item in data.get('items', []):
SaleItem.objects.create(
sale=sale,
product_id=item['id'],
quantity=item['quantity'],
unit_price=item['price'],
line_total=float(item['quantity']) * float(item['price'])
)
Product.objects.filter(pk=item['id']).update(stock_quantity=F('stock_quantity') - item['quantity'])
# Payment
if sale.paid_amount > 0:
SalePayment.objects.create(
sale=sale,
amount=sale.paid_amount,
payment_method_id=data.get('payment_method_id'),
created_by=request.user
)
return JsonResponse({'success': True, 'sale_id': sale.id})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@csrf_exempt
@login_required
def update_sale_api(request, pk):
# Simplified update stub
return JsonResponse({'success': True})
@csrf_exempt
@login_required
def create_purchase_api(request):
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid request'})
try:
data = json.loads(request.body)
with transaction.atomic():
purchase = Purchase.objects.create(
supplier_id=data.get('supplier_id') or None,
total_amount=data.get('total_amount', 0),
paid_amount=data.get('paid_amount', 0),
created_by=request.user,
status='paid' if data.get('payment_type') == 'cash' else 'partial'
)
for item in data.get('items', []):
PurchaseItem.objects.create(
purchase=purchase,
product_id=item['id'],
quantity=item['quantity'],
cost_price=item['cost'],
line_total=float(item['quantity']) * float(item['cost'])
)
# Increase Stock
Product.objects.filter(pk=item['id']).update(stock_quantity=F('stock_quantity') + item['quantity'])
# Payment
if purchase.paid_amount > 0:
PurchasePayment.objects.create(
purchase=purchase,
amount=purchase.paid_amount,
payment_method_id=data.get('payment_method_id'),
created_by=request.user
)
purchase.update_balance()
return JsonResponse({'success': True, 'purchase_id': purchase.id})
except Exception as e:
logger.error(f"Error creating purchase: {e}")
return JsonResponse({'success': False, 'error': str(e)})
@csrf_exempt
@login_required
def update_purchase_api(request, pk):
return JsonResponse({'success': True})
@csrf_exempt
@login_required
def create_sale_return_api(request):
if request.method != 'POST':
return JsonResponse({'success': False})
try:
data = json.loads(request.body)
with transaction.atomic():
sale_return = SaleReturn.objects.create(
customer_id=data.get('customer_id'),
created_by=request.user,
total_amount=0
)
for item in data.get('items', []):
SaleReturnItem.objects.create(
sale_return=sale_return,
product_id=item['id'],
quantity=item['quantity'],
unit_price=item['price'],
line_total=float(item['quantity']) * float(item['price'])
)
Product.objects.filter(pk=item['id']).update(stock_quantity=F('stock_quantity') + item['quantity'])
sale_return.total_amount = sum([i.line_total for i in sale_return.items.all()])
sale_return.save()
return JsonResponse({'success': True})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@csrf_exempt
@login_required
def create_purchase_return_api(request):
if request.method != 'POST':
return JsonResponse({'success': False})
try:
data = json.loads(request.body)
with transaction.atomic():
pr = PurchaseReturn.objects.create(
supplier_id=data.get('supplier_id'),
created_by=request.user,
total_amount=0
)
for item in data.get('items', []):
PurchaseReturnItem.objects.create(
purchase_return=pr,
product_id=item['id'],
quantity=item['quantity'],
cost_price=item['price'],
line_total=float(item['quantity']) * float(item['price'])
)
Product.objects.filter(pk=item['id']).update(stock_quantity=F('stock_quantity') - item['quantity'])
pr.total_amount = sum([i.line_total for i in pr.items.all()])
pr.save()
return JsonResponse({'success': True})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@csrf_exempt
@login_required
def add_customer_ajax(request):
if request.method != 'POST':
return JsonResponse({'success': False})
try:
data = json.loads(request.body)
Customer.objects.create(name=data.get('name'), phone=data.get('phone', ''))
return JsonResponse({'success': True})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@login_required
def search_customers_api(request):
query = request.GET.get('q', '')
customers = Customer.objects.filter(name__icontains=query).values('id', 'name', 'phone')[:10]
return JsonResponse({'results': list(customers)})
@csrf_exempt
@login_required
def add_supplier_ajax(request):
if request.method != 'POST':
return JsonResponse({'success': False})
try:
data = json.loads(request.body)
Supplier.objects.create(name=data.get('name'), phone=data.get('phone', ''))
return JsonResponse({'success': True})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@csrf_exempt
@login_required
def add_category_ajax(request):
if request.method != 'POST':
return JsonResponse({'success': False})
try:
data = json.loads(request.body)
Category.objects.create(
name_en=data.get('name_en'),
name_ar=data.get('name_ar'),
slug=f"cat-{int(timezone.now().timestamp())}"
)
return JsonResponse({'success': True})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@csrf_exempt
@login_required
def add_unit_ajax(request):
if request.method != 'POST':
return JsonResponse({'success': False})
try:
data = json.loads(request.body)
Unit.objects.create(
name_en=data.get('name_en'),
name_ar=data.get('name_ar'),
short_name=data.get('short_name')
)
return JsonResponse({'success': True})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@csrf_exempt
@login_required
def add_payment_method_ajax(request):
if request.method != 'POST':
return JsonResponse({'success': False, 'error': 'Invalid method'})
try:
data = json.loads(request.body)
pm = PaymentMethod.objects.create(
name_en=data.get('name_en'),
name_ar=data.get('name_ar'),
is_active=data.get('is_active', True)
)
return JsonResponse({
'success': True,
'id': pm.id,
'name_en': pm.name_en,
'name_ar': pm.name_ar
})
except Exception as e:
return JsonResponse({'success': False, 'error': str(e)})
@login_required
def get_customer_loyalty_api(request, pk):
return JsonResponse({'points': 0})
@csrf_exempt
@login_required
def hold_sale_api(request):
return JsonResponse({'success': True})
@login_required
def get_held_sales_api(request):
return JsonResponse({'sales': []})
@login_required
def recall_held_sale_api(request, pk):
return JsonResponse({'success': True})
@login_required
def delete_held_sale_api(request, pk):
return JsonResponse({'success': True})