From b2602b999fc2da9eb438bba10173ac1c59e38c3e Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 10 Feb 2026 04:02:23 +0000 Subject: [PATCH] Autosave: 20260210-040222 --- core/__pycache__/views.cpython-311.pyc | Bin 74279 -> 80455 bytes core/views.py | 149 ++++++++++++++++++++++- patch_returns_setup.py | 17 +++ patch_returns_v3.py | 159 +++++++++++++++++++++++++ 4 files changed, 319 insertions(+), 6 deletions(-) create mode 100644 patch_returns_setup.py create mode 100644 patch_returns_v3.py diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index b57f53e9ef57c761816fb0851f8459e4abfb3f38..69a477cd1e64e3cb4f52edbaf0cbf65cac074da5 100644 GIT binary patch delta 6125 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 O_+DPL4qYe(2LAzRL)0_? 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)