diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 9f06b66..4c043ee 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/views.py b/core/views.py index 090fa61..0c14f07 100644 --- a/core/views.py +++ b/core/views.py @@ -50,11 +50,9 @@ def index(request): except Exception as e: logger.error(f"Migration Fix Failed: {e}") - settings = None - try: - settings = SystemSetting.objects.first() - except Exception as e: - logger.error(f"Failed to load settings in index: {e}") + settings = SystemSetting.objects.first() + if not settings: + settings = SystemSetting.objects.create() today = timezone.now().date() @@ -178,13 +176,9 @@ def inventory(request): next_30_days = today + datetime.timedelta(days=30) expired_products = products.filter(has_expiry=True, expiry_date__lt=today) - expiring_soon_products = products.filter(has_expiry=True, expiry_date__gte=today, expiry_date__lte=next_30_days) - - settings = None - try: - settings = SystemSetting.objects.first() - except Exception: - pass + settings = SystemSetting.objects.first() + if not settings: + settings = SystemSetting.objects.create() context = { 'products': products, @@ -209,14 +203,9 @@ def suppliers(request): @login_required def settings_view(request): - settings = None - try: - settings = SystemSetting.objects.first() - if not settings: - settings = SystemSetting.objects.create() - except Exception: - # Create a dummy object or just pass None if DB is broken - pass + settings = SystemSetting.objects.first() + if not settings: + settings = SystemSetting.objects.create() payment_methods = PaymentMethod.objects.filter(is_active=True) expense_categories = ExpenseCategory.objects.all() @@ -325,11 +314,9 @@ def pos(request): messages.warning(request, _("Please open a session to start selling.")) return redirect('start_session') - settings = None - try: - settings = SystemSetting.objects.first() - except Exception: - pass + settings = SystemSetting.objects.first() + if not settings: + settings = SystemSetting.objects.create() products = Product.objects.filter(is_active=True) @@ -398,14 +385,11 @@ def invoice_list(request): Q(invoice_number__icontains=query) ) - paginator = Paginator(sales, 25) - - settings = None - try: - settings = SystemSetting.objects.first() - except Exception: - pass + settings = SystemSetting.objects.first() + if not settings: + settings = SystemSetting.objects.create() + paginator = Paginator(sales, 20) context = { 'sales': paginator.get_page(request.GET.get('page')), 'customers': Customer.objects.all(), @@ -415,9 +399,25 @@ def invoice_list(request): } return render(request, 'core/invoices.html', context) +@login_required @login_required def invoice_create(request): - return redirect('pos') + customers = Customer.objects.all().order_by('name') + products = Product.objects.filter(is_active=True).select_related('category') + payment_methods = PaymentMethod.objects.filter(is_active=True) + site_settings = SystemSetting.objects.first() + if not site_settings: + site_settings = SystemSetting.objects.create() + + decimal_places = site_settings.decimal_places or 2 + + return render(request, 'core/invoice_create.html', { + 'customers': customers, + 'products': products, + 'payment_methods': payment_methods, + 'site_settings': site_settings, + 'decimal_places': decimal_places, + }) @login_required def invoice_detail(request, pk): @@ -430,11 +430,9 @@ def edit_invoice(request, 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 = None - try: - site_settings = SystemSetting.objects.first() - except Exception: - pass + site_settings = SystemSetting.objects.first() + if not site_settings: + site_settings = SystemSetting.objects.create() decimal_places = 2 if site_settings: @@ -638,11 +636,19 @@ def purchases(request): purchases = Purchase.objects.all().order_by('-created_at') return render(request, 'core/purchases.html', {'purchases': purchases}) +@login_required @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}) + payment_methods = PaymentMethod.objects.filter(is_active=True) + settings = SystemSetting.objects.first() + return render(request, 'core/purchase_create.html', { + 'suppliers': suppliers, + 'products': products, + 'payment_methods': payment_methods, + 'decimal_places': settings.decimal_places if settings else 3 + }) @login_required def purchase_detail(request, pk): @@ -888,10 +894,10 @@ def cashflow_report(request): @login_required def add_product(request): if request.method == 'POST': - form = ProductForm(request.POST, request.FILES, instance=product) + form = ProductForm(request.POST, request.FILES) if form.is_valid(): form.save() - messages.success(request, _("Product updated.")) + messages.success(request, _("Product added.")) return redirect(reverse('inventory') + '#items') return redirect('inventory') @@ -1125,7 +1131,7 @@ def send_invoice_whatsapp(request): receipt_url = request.build_absolute_uri(reverse('sale_receipt', args=[sale.pk])) message = ( - f"Hello {sale.customer.name if sale.customer else 'Guest'}, + f"Hello {sale.customer.name if sale.customer else 'Guest'},\n" f"Here is your invoice #{sale.invoice_number or sale.id}.\n" f"Total: {sale.total_amount}\n" f"View Invoice: {receipt_url}\n" @@ -1226,13 +1232,18 @@ def create_sale_api(request): with transaction.atomic(): sale = Sale.objects.create( customer_id=data.get('customer_id') or None, + invoice_number=data.get('invoice_number', ''), + subtotal=data.get('subtotal', 0), + vat_amount=data.get('vat_amount', 0), 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', discount=data.get('discount', 0), - loyalty_points_redeemed=data.get('loyalty_points_redeemed', 0) + loyalty_points_redeemed=data.get('loyalty_points_redeemed', 0), + notes=data.get('notes', ''), + due_date=data.get('due_date') if data.get('due_date') else None ) for item in data.get('items', []): SaleItem.objects.create( @@ -1367,20 +1378,36 @@ def create_purchase_api(request): try: data = json.loads(request.body) with transaction.atomic(): + payment_type = data.get('payment_type', 'cash') + paid_amount = decimal.Decimal(str(data.get('paid_amount', 0))) + total_amount = decimal.Decimal(str(data.get('total_amount', 0))) + + status = 'paid' + if payment_type == 'credit': + status = 'unpaid' + elif payment_type == 'partial' or (paid_amount < total_amount and paid_amount > 0): + status = 'partial' + elif paid_amount == 0 and total_amount > 0: + status = 'unpaid' + 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), + invoice_number=data.get('invoice_number', ''), + total_amount=total_amount, + paid_amount=paid_amount, + payment_type=payment_type, + due_date=data.get('due_date') or None, + notes=data.get('notes', ''), created_by=request.user, - status='paid' if data.get('payment_type') == 'cash' else 'partial' + status=status ) 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']) + cost_price=item['price'], + line_total=float(item['quantity']) * float(item['price']) ) # Increase Stock Product.objects.filter(pk=item['id']).update(stock_quantity=F('stock_quantity') + item['quantity']) @@ -1566,4 +1593,4 @@ def recall_held_sale_api(request, pk): @login_required def delete_held_sale_api(request, pk): - return JsonResponse({'success': True}) + return JsonResponse({'success': True}) \ No newline at end of file diff --git a/media/profile_pics/meezan.png b/media/profile_pics/meezan.png new file mode 100644 index 0000000..3bd1e19 Binary files /dev/null and b/media/profile_pics/meezan.png differ diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css index cb28598..ff3be5e 100644 --- a/staticfiles/css/custom.css +++ b/staticfiles/css/custom.css @@ -57,6 +57,7 @@ body { position: sticky; top: 0; height: 100vh; + overflow-y: auto; } #sidebar.active { @@ -64,16 +65,16 @@ body { } #sidebar .sidebar-header { - padding: 20px; + padding: 15px 20px; background: var(--meezan-sidebar-bg); } #sidebar ul.components { - padding: 20px 0; + padding: 10px 0; } #sidebar ul li a { - padding: 12px 25px; + padding: 8px 25px; font-size: 1rem; display: flex; align-items: center; @@ -103,7 +104,7 @@ body { /* Collapsible Sidebar Styles */ #sidebar ul.components li.sidebar-group-header > a { - padding: 12px 25px; + padding: 8px 25px; font-size: 0.85rem; text-transform: uppercase; font-weight: 700; @@ -306,4 +307,4 @@ body { #productGrid .col { width: 100% !important; } -} +} \ No newline at end of file diff --git a/staticfiles/pasted-20260210-082544-8d8f3bac.png b/staticfiles/pasted-20260210-082544-8d8f3bac.png new file mode 100644 index 0000000..d1435f7 Binary files /dev/null and b/staticfiles/pasted-20260210-082544-8d8f3bac.png differ