diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index b57f53e..69a477c 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 38dff8c..9e77d8d 100644 --- a/core/views.py +++ b/core/views.py @@ -927,8 +927,16 @@ def lpo_delete(request, pk): # --- 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') +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) @@ -937,16 +945,75 @@ def sale_return_detail(request, pk): 'sale_return': sale_return, 'settings': settings }) + @login_required def delete_sale_return(request, pk): return redirect('sales_returns') + @csrf_exempt -def create_sale_return_api(request): return JsonResponse({'success': True}) +@login_required +def create_sale_return_api(request): + if request.method != 'POST': + return JsonResponse({'success': False, 'error': 'Invalid method'}) + try: + data = json.loads(request.body) + customer_id = data.get('customer_id') + items = data.get('items', []) + + customer = None + if customer_id: + customer = get_object_or_404(Customer, pk=customer_id) + + with transaction.atomic(): + sale_return = SaleReturn.objects.create( + customer=customer, + created_by=request.user, + total_amount=0, + return_number=f"SR-{int(timezone.now().timestamp())}", + notes=data.get('notes', '') + ) + + total = decimal.Decimal(0) + for item in items: + qty = decimal.Decimal(str(item.get('quantity', 0))) + price = decimal.Decimal(str(item.get('price', 0))) + line_total = qty * price + + SaleReturnItem.objects.create( + sale_return=sale_return, + product_id=item['id'], + quantity=qty, + unit_price=price, + line_total=line_total + ) + + # Update stock: Returns from customer mean stock comes IN + product = Product.objects.get(pk=item['id']) + product.stock_quantity += qty + product.save() + + total += line_total + + sale_return.total_amount = total + sale_return.save() + + return JsonResponse({'success': True, 'id': sale_return.id}) + except Exception as e: + logger.exception("Error creating sale return") + return JsonResponse({'success': False, 'error': str(e)}) # --- 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') +def purchase_return_create(request): + suppliers = Supplier.objects.filter(is_active=True) + 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) @@ -955,10 +1022,59 @@ def purchase_return_detail(request, pk): '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}) +@login_required +def create_purchase_return_api(request): + if request.method != 'POST': + return JsonResponse({'success': False, 'error': 'Invalid method'}) + try: + data = json.loads(request.body) + supplier_id = data.get('supplier_id') + items = data.get('items', []) + + supplier = get_object_or_404(Supplier, pk=supplier_id) + + with transaction.atomic(): + purchase_return = PurchaseReturn.objects.create( + supplier=supplier, + created_by=request.user, + total_amount=0, + return_number=f"PR-{int(timezone.now().timestamp())}", + notes=data.get('notes', '') + ) + + total = decimal.Decimal(0) + for item in items: + qty = decimal.Decimal(str(item.get('quantity', 0))) + cost = decimal.Decimal(str(item.get('price', 0))) # Frontend sends 'price' + line_total = qty * cost + + PurchaseReturnItem.objects.create( + purchase_return=purchase_return, + product_id=item['id'], + quantity=qty, + cost_price=cost, + line_total=line_total + ) + + # Update stock: Returns to supplier mean stock goes OUT + product = Product.objects.get(pk=item['id']) + product.stock_quantity -= qty + product.save() + + total += line_total + + purchase_return.total_amount = total + purchase_return.save() + + return JsonResponse({'success': True, 'id': purchase_return.id}) + except Exception as e: + logger.exception("Error creating purchase return") + return JsonResponse({'success': False, 'error': str(e)}) # --- Other Stubs --- @login_required @@ -1033,13 +1149,34 @@ def add_sale_payment(request, pk): return redirect('invoices') @login_required -def sale_receipt(request, pk): return render(request, 'core/sale_receipt.html') +def sale_receipt(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/sale_receipt.html', { + 'sale': sale, + 'settings': settings, + 'amount_in_words': amount_in_words + }) + @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') +def customer_payment_receipt(request, pk): + payment = get_object_or_404(SalePayment, pk=pk) + sale = payment.sale + settings = SystemSetting.objects.first() + amount_in_words = number_to_words_en(payment.amount) + return render(request, 'core/payment_receipt.html', { + 'payment': payment, + 'sale': sale, + 'settings': settings, + 'amount_in_words': amount_in_words + }) + @csrf_exempt def hold_sale_api(request): return JsonResponse({'success': True}) @csrf_exempt diff --git a/patch_returns_setup.py b/patch_returns_setup.py new file mode 100644 index 0000000..70a5292 --- /dev/null +++ b/patch_returns_setup.py @@ -0,0 +1,17 @@ +from core.views import ( + purchase_return_create, sale_return_create, + create_sale_return_api, create_purchase_return_api, + Supplier, Product, Customer, SaleReturn, SaleReturnItem, + PurchaseReturn, PurchaseReturnItem, transaction, timezone, + decimal, json, JsonResponse, get_object_or_404, login_required, csrf_exempt, logger +) + +def patch_purchase_return_create(request): + suppliers = Supplier.objects.filter(is_active=True) + products = Product.objects.filter(is_active=True) + return {'suppliers': suppliers, 'products': products} + +def patch_sale_return_create(request): + customers = Customer.objects.all() + products = Product.objects.filter(is_active=True) + return {'customers': customers, 'products': products} diff --git a/patch_returns_v3.py b/patch_returns_v3.py new file mode 100644 index 0000000..fdc6d74 --- /dev/null +++ b/patch_returns_v3.py @@ -0,0 +1,159 @@ +import os + +file_path = 'core/views.py' + +with open(file_path, 'r') as f: + content = f.read() + +# Replacement 1: sale_return_create +old_sale_create = "def sale_return_create(request): return render(request, 'core/sale_return_create.html')" +new_sale_create = """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 + })""" + +# Replacement 2: create_sale_return_api +old_sale_api = "@csrf_exempt\ndef create_sale_return_api(request): return JsonResponse({'success': True})" +new_sale_api = """@csrf_exempt +@login_required +def create_sale_return_api(request): + if request.method != 'POST': + return JsonResponse({'success': False, 'error': 'Invalid method'}) + try: + data = json.loads(request.body) + customer_id = data.get('customer_id') + items = data.get('items', []) + + customer = None + if customer_id: + customer = get_object_or_404(Customer, pk=customer_id) + + with transaction.atomic(): + sale_return = SaleReturn.objects.create( + customer=customer, + created_by=request.user, + total_amount=0, + return_number=f"SR-{{int(timezone.now().timestamp())}}", + notes=data.get('notes', '') + ) + + total = decimal.Decimal(0) + for item in items: + qty = decimal.Decimal(str(item.get('quantity', 0))) + price = decimal.Decimal(str(item.get('price', 0))) + line_total = qty * price + + SaleReturnItem.objects.create( + sale_return=sale_return, + product_id=item['id'], + quantity=qty, + unit_price=price, + line_total=line_total + ) + + # Update stock: Returns from customer mean stock comes IN + product = Product.objects.get(pk=item['id']) + product.stock_quantity += qty + product.save() + + total += line_total + + sale_return.total_amount = total + sale_return.save() + + return JsonResponse({'success': True, 'id': sale_return.id}) + except Exception as e: + logger.exception("Error creating sale return") + return JsonResponse({'success': False, 'error': str(e)})""" + +# Replacement 3: purchase_return_create +old_purchase_create = "def purchase_return_create(request): return render(request, 'core/purchase_return_create.html')" +new_purchase_create = """def purchase_return_create(request): + suppliers = Supplier.objects.filter(is_active=True) + products = Product.objects.filter(is_active=True) + return render(request, 'core/purchase_return_create.html', { + 'suppliers': suppliers, + 'products': products + })""" + +# Replacement 4: create_purchase_return_api +old_purchase_api = "@csrf_exempt\ndef create_purchase_return_api(request): return JsonResponse({'success': True})" +new_purchase_api = """@csrf_exempt +@login_required +def create_purchase_return_api(request): + if request.method != 'POST': + return JsonResponse({'success': False, 'error': 'Invalid method'}) + try: + data = json.loads(request.body) + supplier_id = data.get('supplier_id') + items = data.get('items', []) + + supplier = get_object_or_404(Supplier, pk=supplier_id) + + with transaction.atomic(): + purchase_return = PurchaseReturn.objects.create( + supplier=supplier, + created_by=request.user, + total_amount=0, + return_number=f"PR-{{int(timezone.now().timestamp())}}", + notes=data.get('notes', '') + ) + + total = decimal.Decimal(0) + for item in items: + qty = decimal.Decimal(str(item.get('quantity', 0))) + cost = decimal.Decimal(str(item.get('price', 0))) + line_total = qty * cost + + PurchaseReturnItem.objects.create( + purchase_return=purchase_return, + product_id=item['id'], + quantity=qty, + cost_price=cost, + line_total=line_total + ) + + # Update stock: Returns to supplier mean stock goes OUT + product = Product.objects.get(pk=item['id']) + product.stock_quantity -= qty + product.save() + + total += line_total + + purchase_return.total_amount = total + purchase_return.save() + + return JsonResponse({'success': True, 'id': purchase_return.id}) + except Exception as e: + logger.exception("Error creating purchase return") + return JsonResponse({'success': False, 'error': str(e)})""" + +if old_sale_create in content: + content = content.replace(old_sale_create, new_sale_create) + print("Patched sale_return_create") +else: + print("Could not find sale_return_create stub") + +if old_sale_api in content: + content = content.replace(old_sale_api, new_sale_api) + print("Patched create_sale_return_api") +else: + print("Could not find create_sale_return_api stub") + +if old_purchase_create in content: + content = content.replace(old_purchase_create, new_purchase_create) + print("Patched purchase_return_create") +else: + print("Could not find purchase_return_create stub") + +if old_purchase_api in content: + content = content.replace(old_purchase_api, new_purchase_api) + print("Patched create_purchase_return_api") +else: + print("Could not find create_purchase_return_api stub") + +with open(file_path, 'w') as f: + f.write(content)