update final
This commit is contained in:
parent
8150c2ba43
commit
ee5b4ff280
Binary file not shown.
Binary file not shown.
@ -24,16 +24,39 @@ def create_journal_entry(obj, description, items):
|
|||||||
if not items:
|
if not items:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Filter out items with 0 amount
|
||||||
|
valid_items = []
|
||||||
|
for item in items:
|
||||||
|
try:
|
||||||
|
# Ensure amount is Decimal
|
||||||
|
amount = Decimal(str(item['amount']))
|
||||||
|
if amount > 0:
|
||||||
|
item['amount'] = amount
|
||||||
|
valid_items.append(item)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not valid_items:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Determine Entry Date
|
||||||
|
entry_date = timezone.now().date()
|
||||||
|
if hasattr(obj, 'date'):
|
||||||
|
entry_date = obj.date
|
||||||
|
elif hasattr(obj, 'payment_date'):
|
||||||
|
entry_date = obj.payment_date
|
||||||
|
elif hasattr(obj, 'created_at'):
|
||||||
|
entry_date = obj.created_at.date()
|
||||||
|
|
||||||
entry = JournalEntry.objects.create(
|
entry = JournalEntry.objects.create(
|
||||||
date=getattr(obj, 'created_at', timezone.now()).date() if hasattr(obj, 'created_at') else timezone.now().date(),
|
date=entry_date,
|
||||||
description=description,
|
description=description,
|
||||||
content_type=content_type,
|
content_type=content_type,
|
||||||
object_id=obj.id,
|
object_id=obj.id,
|
||||||
reference=f"{obj.__class__.__name__} #{obj.id}"
|
reference=f"{obj.__class__.__name__} #{obj.id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
for item in items:
|
for item in valid_items:
|
||||||
if item['amount'] > 0:
|
|
||||||
JournalItem.objects.create(
|
JournalItem.objects.create(
|
||||||
entry=entry,
|
entry=entry,
|
||||||
account=item['account'],
|
account=item['account'],
|
||||||
@ -51,15 +74,10 @@ def sale_accounting_handler(sender, instance, created, **kwargs):
|
|||||||
|
|
||||||
ar_acc = get_account('1200')
|
ar_acc = get_account('1200')
|
||||||
sales_acc = get_account('4000')
|
sales_acc = get_account('4000')
|
||||||
vat_acc = get_account('2100')
|
|
||||||
|
|
||||||
if not ar_acc or not sales_acc:
|
if not ar_acc or not sales_acc:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Subtotal and VAT logic (assuming total_amount includes VAT for now as per Sale model simplicity)
|
|
||||||
# Actually Sale model has total_amount and discount.
|
|
||||||
# Let's assume total_amount is the final amount.
|
|
||||||
|
|
||||||
items = [
|
items = [
|
||||||
{'account': ar_acc, 'type': 'debit', 'amount': instance.total_amount},
|
{'account': ar_acc, 'type': 'debit', 'amount': instance.total_amount},
|
||||||
{'account': sales_acc, 'type': 'credit', 'amount': instance.total_amount},
|
{'account': sales_acc, 'type': 'credit', 'amount': instance.total_amount},
|
||||||
|
|||||||
@ -4,7 +4,8 @@ from django.contrib.auth.decorators import login_required
|
|||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from .models import Account, JournalEntry, JournalItem
|
from .models import Account, JournalEntry, JournalItem
|
||||||
from .forms import AccountForm, JournalEntryForm
|
from .forms import AccountForm, JournalEntryForm
|
||||||
from django.db.models import Sum, Q
|
from django.db.models import Sum, Q, Value, DecimalField
|
||||||
|
from django.db.models.functions import Coalesce
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
@ -70,8 +71,8 @@ def account_create_update(request, pk=None):
|
|||||||
@login_required
|
@login_required
|
||||||
def journal_entries(request):
|
def journal_entries(request):
|
||||||
entries = JournalEntry.objects.annotate(
|
entries = JournalEntry.objects.annotate(
|
||||||
total_debit=Sum('items__amount', filter=Q(items__type='debit')),
|
total_debit=Coalesce(Sum('items__amount', filter=Q(items__type='debit')), Value(0), output_field=DecimalField()),
|
||||||
total_credit=Sum('items__amount', filter=Q(items__type='credit'))
|
total_credit=Coalesce(Sum('items__amount', filter=Q(items__type='credit')), Value(0), output_field=DecimalField())
|
||||||
).prefetch_related('items__account').order_by('-date', '-id')
|
).prefetch_related('items__account').order_by('-date', '-id')
|
||||||
return render(request, 'accounting/journal_entries.html', {'entries': entries})
|
return render(request, 'accounting/journal_entries.html', {'entries': entries})
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
31
core/patch_views_sales_list.py
Normal file
31
core/patch_views_sales_list.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
@login_required
|
||||||
|
def invoice_list(request):
|
||||||
|
sales = Sale.objects.all().order_by('-created_at')
|
||||||
|
|
||||||
|
# Filter by date range
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Filter by customer
|
||||||
|
customer_id = request.GET.get('customer')
|
||||||
|
if customer_id:
|
||||||
|
sales = sales.filter(customer_id=customer_id)
|
||||||
|
|
||||||
|
# Filter by status
|
||||||
|
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)
|
||||||
@ -117,12 +117,82 @@
|
|||||||
<i class="bi bi-paperclip"></i>
|
<i class="bi bi-paperclip"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<button class="btn btn-sm btn-light rounded-circle me-1" data-bs-toggle="modal" data-bs-target="#editExpenseModal{{ expense.id }}">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</button>
|
||||||
<button class="btn btn-sm btn-light rounded-circle text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal{{ expense.id }}">
|
<button class="btn btn-sm btn-light rounded-circle text-danger" data-bs-toggle="modal" data-bs-target="#deleteModal{{ expense.id }}">
|
||||||
<i class="bi bi-trash"></i>
|
<i class="bi bi-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div class="modal fade" id="editExpenseModal{{ expense.id }}" tabindex="-1">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content border-0 rounded-4 shadow">
|
||||||
|
<div class="modal-header p-4 border-0">
|
||||||
|
<h5 class="modal-title fw-bold">{% trans "Edit Expense" %}</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<form action="{% url 'expense_edit' expense.pk %}" method="post" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="modal-body p-4 text-start">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">{% trans "Category" %}</label>
|
||||||
|
<select name="category" class="form-select rounded-3" required>
|
||||||
|
<option value="">{% trans "Select Category" %}</option>
|
||||||
|
{% for category in categories %}
|
||||||
|
<option value="{{ category.id }}" {% if expense.category.id == category.id %}selected{% endif %}>{{ category.name_en }} / {{ category.name_ar }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">{% trans "Amount" %}</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text">{{ site_settings.currency_symbol }}</span>
|
||||||
|
<input type="number" step="0.001" name="amount" class="form-control rounded-end-3" value="{{ expense.amount|stringformat:'f' }}" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">{% trans "Date" %}</label>
|
||||||
|
<input type="date" name="date" class="form-control rounded-3" value="{{ expense.date|date:'Y-m-d' }}" required>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 mb-3">
|
||||||
|
<label class="form-label fw-bold">{% trans "Payment Method" %}</label>
|
||||||
|
<select name="payment_method" class="form-select rounded-3">
|
||||||
|
<option value="">{% trans "None" %}</option>
|
||||||
|
{% for pm in payment_methods %}
|
||||||
|
<option value="{{ pm.id }}" {% if expense.payment_method.id == pm.id %}selected{% endif %}>{{ pm.name_en }} / {{ pm.name_ar }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-bold">{% trans "Description" %}</label>
|
||||||
|
<textarea name="description" class="form-control rounded-3" rows="3">{{ expense.description|default_if_none:"" }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-0">
|
||||||
|
<label class="form-label fw-bold">{% trans "Attachment" %} <small class="text-muted fw-normal">({% trans "Optional" %})</small></label>
|
||||||
|
{% if expense.attachment %}
|
||||||
|
<div class="mb-2">
|
||||||
|
<a href="{{ expense.attachment.url }}" target="_blank" class="small text-decoration-none">
|
||||||
|
<i class="bi bi-paperclip me-1"></i>{% trans "Current Attachment" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<input type="file" name="attachment" class="form-control rounded-3">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer p-4 border-0">
|
||||||
|
<button type="button" class="btn btn-light rounded-3 px-4" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||||||
|
<button type="submit" class="btn btn-primary rounded-3 px-4">{% trans "Update Expense" %}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Delete Modal -->
|
<!-- Delete Modal -->
|
||||||
<div class="modal fade" id="deleteModal{{ expense.id }}" tabindex="-1">
|
<div class="modal fade" id="deleteModal{{ expense.id }}" tabindex="-1">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
|||||||
@ -62,6 +62,7 @@ urlpatterns = [
|
|||||||
# Expenses
|
# Expenses
|
||||||
path('expenses/', views.expenses_view, name='expenses'),
|
path('expenses/', views.expenses_view, name='expenses'),
|
||||||
path('expenses/create/', views.expense_create_view, name='expense_create'),
|
path('expenses/create/', views.expense_create_view, name='expense_create'),
|
||||||
|
path('expenses/edit/<int:pk>/', views.expense_edit_view, name='expense_edit'),
|
||||||
path('expenses/delete/<int:pk>/', views.expense_delete_view, name='expense_delete'),
|
path('expenses/delete/<int:pk>/', views.expense_delete_view, name='expense_delete'),
|
||||||
path('expenses/categories/', views.expense_categories_view, name='expense_categories'),
|
path('expenses/categories/', views.expense_categories_view, name='expense_categories'),
|
||||||
path('expenses/categories/delete/<int:pk>/', views.expense_category_delete_view, name='expense_category_delete'),
|
path('expenses/categories/delete/<int:pk>/', views.expense_category_delete_view, name='expense_category_delete'),
|
||||||
|
|||||||
332
core/views.py
332
core/views.py
@ -364,8 +364,34 @@ def cashflow_report(request):
|
|||||||
@login_required
|
@login_required
|
||||||
def invoice_list(request):
|
def invoice_list(request):
|
||||||
sales = Sale.objects.all().order_by('-created_at')
|
sales = Sale.objects.all().order_by('-created_at')
|
||||||
|
|
||||||
|
# Filter by date range
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Filter by customer
|
||||||
|
customer_id = request.GET.get('customer')
|
||||||
|
if customer_id:
|
||||||
|
sales = sales.filter(customer_id=customer_id)
|
||||||
|
|
||||||
|
# Filter by status
|
||||||
|
status = request.GET.get('status')
|
||||||
|
if status:
|
||||||
|
sales = sales.filter(status=status)
|
||||||
|
|
||||||
paginator = Paginator(sales, 25)
|
paginator = Paginator(sales, 25)
|
||||||
return render(request, 'core/invoices.html', {'sales': paginator.get_page(request.GET.get('page'))})
|
|
||||||
|
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
|
@login_required
|
||||||
def invoice_detail(request, pk):
|
def invoice_detail(request, pk):
|
||||||
@ -431,7 +457,129 @@ def create_purchase_return_api(request): return JsonResponse({'success': False})
|
|||||||
@login_required
|
@login_required
|
||||||
def export_expenses_excel(request): return redirect('expenses')
|
def export_expenses_excel(request): return redirect('expenses')
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def update_sale_api(request, pk): return JsonResponse({'success': False})
|
def update_sale_api(request, pk):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'error': 'Invalid request method'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
sale = Sale.objects.get(pk=pk)
|
||||||
|
data = json.loads(request.body)
|
||||||
|
|
||||||
|
customer_id = data.get('customer_id')
|
||||||
|
items = data.get('items', [])
|
||||||
|
discount = decimal.Decimal(str(data.get('discount', 0)))
|
||||||
|
paid_amount = decimal.Decimal(str(data.get('paid_amount', 0)))
|
||||||
|
payment_type = data.get('payment_type', 'cash')
|
||||||
|
payment_method_id = data.get('payment_method_id')
|
||||||
|
due_date = data.get('due_date')
|
||||||
|
notes = data.get('notes', '')
|
||||||
|
invoice_number = data.get('invoice_number')
|
||||||
|
|
||||||
|
if not items:
|
||||||
|
return JsonResponse({'success': False, 'error': 'No items in sale'})
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
# 1. Revert Stock
|
||||||
|
for item in sale.items.all():
|
||||||
|
product = item.product
|
||||||
|
product.stock_quantity += item.quantity
|
||||||
|
product.save()
|
||||||
|
|
||||||
|
# 2. Delete existing items
|
||||||
|
sale.items.all().delete()
|
||||||
|
|
||||||
|
# 3. Update Sale Details
|
||||||
|
if customer_id:
|
||||||
|
sale.customer_id = customer_id
|
||||||
|
else:
|
||||||
|
sale.customer = None
|
||||||
|
|
||||||
|
sale.discount = discount
|
||||||
|
sale.notes = notes
|
||||||
|
if invoice_number:
|
||||||
|
sale.invoice_number = invoice_number
|
||||||
|
|
||||||
|
if due_date:
|
||||||
|
sale.due_date = due_date
|
||||||
|
else:
|
||||||
|
sale.due_date = None
|
||||||
|
|
||||||
|
# 4. Create New Items and Deduct Stock
|
||||||
|
subtotal = decimal.Decimal(0)
|
||||||
|
|
||||||
|
for item_data in items:
|
||||||
|
product = Product.objects.get(pk=item_data['id'])
|
||||||
|
quantity = decimal.Decimal(str(item_data['quantity']))
|
||||||
|
price = decimal.Decimal(str(item_data['price']))
|
||||||
|
|
||||||
|
# Deduct stock
|
||||||
|
product.stock_quantity -= quantity
|
||||||
|
product.save()
|
||||||
|
|
||||||
|
line_total = price * quantity
|
||||||
|
subtotal += line_total
|
||||||
|
|
||||||
|
SaleItem.objects.create(
|
||||||
|
sale=sale,
|
||||||
|
product=product,
|
||||||
|
quantity=qty,
|
||||||
|
unit_price=price,
|
||||||
|
line_total=line_total
|
||||||
|
)
|
||||||
|
|
||||||
|
sale.subtotal = subtotal
|
||||||
|
sale.total_amount = subtotal - discount
|
||||||
|
|
||||||
|
# 5. Handle Payments
|
||||||
|
if payment_type == 'credit':
|
||||||
|
sale.status = 'unpaid'
|
||||||
|
sale.paid_amount = 0
|
||||||
|
sale.balance_due = sale.total_amount
|
||||||
|
sale.payments.all().delete()
|
||||||
|
|
||||||
|
elif payment_type == 'cash':
|
||||||
|
sale.status = 'paid'
|
||||||
|
sale.paid_amount = sale.total_amount
|
||||||
|
sale.balance_due = 0
|
||||||
|
|
||||||
|
sale.payments.all().delete()
|
||||||
|
SalePayment.objects.create(
|
||||||
|
sale=sale,
|
||||||
|
amount=sale.total_amount,
|
||||||
|
payment_method_id=payment_method_id if payment_method_id else None,
|
||||||
|
payment_date=timezone.now().date(),
|
||||||
|
notes='Full Payment (Edit)'
|
||||||
|
)
|
||||||
|
|
||||||
|
elif payment_type == 'partial':
|
||||||
|
sale.paid_amount = paid_amount
|
||||||
|
sale.balance_due = sale.total_amount - paid_amount
|
||||||
|
if sale.balance_due <= 0:
|
||||||
|
sale.status = 'paid'
|
||||||
|
sale.balance_due = 0
|
||||||
|
else:
|
||||||
|
sale.status = 'partial'
|
||||||
|
|
||||||
|
sale.payments.all().delete()
|
||||||
|
SalePayment.objects.create(
|
||||||
|
sale=sale,
|
||||||
|
amount=paid_amount,
|
||||||
|
payment_method_id=payment_method_id if payment_method_id else None,
|
||||||
|
payment_date=timezone.now().date(),
|
||||||
|
notes='Partial Payment (Edit)'
|
||||||
|
)
|
||||||
|
|
||||||
|
sale.save()
|
||||||
|
|
||||||
|
return JsonResponse({'success': True, 'sale_id': sale.id})
|
||||||
|
|
||||||
|
except Sale.DoesNotExist:
|
||||||
|
return JsonResponse({'success': False, 'error': 'Sale not found'})
|
||||||
|
except Product.DoesNotExist:
|
||||||
|
return JsonResponse({'success': False, 'error': 'Product not found'})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
def hold_sale_api(request): return JsonResponse({'success': False})
|
def hold_sale_api(request): return JsonResponse({'success': False})
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@ -524,16 +672,132 @@ def start_session(request): return redirect('cashier_session_list')
|
|||||||
def close_session(request): return redirect('cashier_session_list')
|
def close_session(request): return redirect('cashier_session_list')
|
||||||
@login_required
|
@login_required
|
||||||
def session_detail(request, pk): return redirect('cashier_session_list')
|
def session_detail(request, pk): return redirect('cashier_session_list')
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def expenses_view(request): return render(request, 'core/expenses.html')
|
def expenses_view(request):
|
||||||
|
expenses = Expense.objects.all().select_related('category', 'payment_method', 'created_by').order_by('-date')
|
||||||
|
categories = ExpenseCategory.objects.all()
|
||||||
|
payment_methods = PaymentMethod.objects.filter(is_active=True)
|
||||||
|
|
||||||
|
paginator = Paginator(expenses, 25)
|
||||||
|
page_number = request.GET.get('page')
|
||||||
|
page_obj = paginator.get_page(page_number)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'expenses': page_obj,
|
||||||
|
'categories': categories,
|
||||||
|
'payment_methods': payment_methods,
|
||||||
|
}
|
||||||
|
return render(request, 'core/expenses.html', context)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def expense_create_view(request): return redirect('expenses')
|
def expense_create_view(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
try:
|
||||||
|
category_id = request.POST.get('category')
|
||||||
|
amount = request.POST.get('amount')
|
||||||
|
date = request.POST.get('date')
|
||||||
|
description = request.POST.get('description')
|
||||||
|
payment_method_id = request.POST.get('payment_method')
|
||||||
|
|
||||||
|
category = get_object_or_404(ExpenseCategory, pk=category_id)
|
||||||
|
payment_method = get_object_or_404(PaymentMethod, pk=payment_method_id) if payment_method_id else None
|
||||||
|
|
||||||
|
expense = Expense.objects.create(
|
||||||
|
category=category,
|
||||||
|
amount=amount,
|
||||||
|
date=date or timezone.now().date(),
|
||||||
|
description=description,
|
||||||
|
payment_method=payment_method,
|
||||||
|
created_by=request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
if 'attachment' in request.FILES:
|
||||||
|
expense.attachment = request.FILES['attachment']
|
||||||
|
expense.save()
|
||||||
|
|
||||||
|
messages.success(request, _('Expense added successfully.'))
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request, _('Error adding expense: ') + str(e))
|
||||||
|
|
||||||
|
return redirect('expenses')
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def expense_delete_view(request, pk): return redirect('expenses')
|
def expense_edit_view(request, pk):
|
||||||
|
expense = get_object_or_404(Expense, pk=pk)
|
||||||
|
if request.method == 'POST':
|
||||||
|
try:
|
||||||
|
category_id = request.POST.get('category')
|
||||||
|
amount = request.POST.get('amount')
|
||||||
|
date = request.POST.get('date')
|
||||||
|
description = request.POST.get('description')
|
||||||
|
payment_method_id = request.POST.get('payment_method')
|
||||||
|
|
||||||
|
category = get_object_or_404(ExpenseCategory, pk=category_id)
|
||||||
|
payment_method = get_object_or_404(PaymentMethod, pk=payment_method_id) if payment_method_id else None
|
||||||
|
|
||||||
|
expense.category = category
|
||||||
|
expense.amount = amount
|
||||||
|
expense.date = date or expense.date
|
||||||
|
expense.description = description
|
||||||
|
expense.payment_method = payment_method
|
||||||
|
|
||||||
|
if 'attachment' in request.FILES:
|
||||||
|
expense.attachment = request.FILES['attachment']
|
||||||
|
|
||||||
|
expense.save()
|
||||||
|
messages.success(request, _('Expense updated successfully.'))
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request, _('Error updating expense: ') + str(e))
|
||||||
|
|
||||||
|
return redirect('expenses')
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def expense_categories_view(request): return render(request, 'core/expense_categories.html')
|
def expense_delete_view(request, pk):
|
||||||
|
expense = get_object_or_404(Expense, pk=pk)
|
||||||
|
expense.delete()
|
||||||
|
messages.success(request, _('Expense deleted successfully.'))
|
||||||
|
return redirect('expenses')
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def expense_category_delete_view(request, pk): return redirect('expense_categories')
|
def expense_categories_view(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
category_id = request.POST.get('category_id')
|
||||||
|
name_en = request.POST.get('name_en')
|
||||||
|
name_ar = request.POST.get('name_ar')
|
||||||
|
description = request.POST.get('description')
|
||||||
|
|
||||||
|
if category_id:
|
||||||
|
# Update existing category
|
||||||
|
category = get_object_or_404(ExpenseCategory, pk=category_id)
|
||||||
|
category.name_en = name_en
|
||||||
|
category.name_ar = name_ar
|
||||||
|
category.description = description
|
||||||
|
category.save()
|
||||||
|
messages.success(request, _('Expense category updated successfully.'))
|
||||||
|
else:
|
||||||
|
# Create new category
|
||||||
|
ExpenseCategory.objects.create(
|
||||||
|
name_en=name_en,
|
||||||
|
name_ar=name_ar,
|
||||||
|
description=description
|
||||||
|
)
|
||||||
|
messages.success(request, _('Expense category added successfully.'))
|
||||||
|
return redirect('expense_categories')
|
||||||
|
|
||||||
|
categories = ExpenseCategory.objects.all().order_by('-id')
|
||||||
|
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)
|
||||||
|
if category.expenses.exists():
|
||||||
|
messages.error(request, _('Cannot delete category because it has related expenses.'))
|
||||||
|
else:
|
||||||
|
category.delete()
|
||||||
|
messages.success(request, _('Expense category deleted successfully.'))
|
||||||
|
return redirect('expense_categories')
|
||||||
|
|
||||||
@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
|
||||||
@ -541,9 +805,59 @@ def customer_payments(request): return redirect('invoices')
|
|||||||
@login_required
|
@login_required
|
||||||
def customer_payment_receipt(request, pk): return redirect('invoices')
|
def customer_payment_receipt(request, pk): return redirect('invoices')
|
||||||
@login_required
|
@login_required
|
||||||
def sale_receipt(request, pk): return redirect('invoices')
|
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
|
||||||
|
})
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def edit_invoice(request, pk): return redirect('invoices')
|
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
|
||||||
|
|
||||||
|
# Serialize items for Vue
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Get first payment method if exists
|
||||||
|
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
|
@login_required
|
||||||
def add_sale_payment(request, pk): return redirect('invoices')
|
def add_sale_payment(request, pk): return redirect('invoices')
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
31
debug_accounting.py
Normal file
31
debug_accounting.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import os
|
||||||
|
import django
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
from accounting.models import Account, JournalEntry, JournalItem
|
||||||
|
from core.models import Expense
|
||||||
|
|
||||||
|
print("Checking Accounts...")
|
||||||
|
acc_1000 = Account.objects.filter(code='1000').first()
|
||||||
|
acc_5400 = Account.objects.filter(code='5400').first()
|
||||||
|
|
||||||
|
print(f"Account 1000 (Cash): {acc_1000}")
|
||||||
|
print(f"Account 5400 (General Expense): {acc_5400}")
|
||||||
|
|
||||||
|
print("\nChecking Journal Entries for Expenses...")
|
||||||
|
expenses = Expense.objects.all()
|
||||||
|
for exp in expenses:
|
||||||
|
print(f"Expense {exp.id}: {exp.description} - Amount: {exp.amount}")
|
||||||
|
# Find linked entry
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
ct = ContentType.objects.get_for_model(Expense)
|
||||||
|
entries = JournalEntry.objects.filter(content_type=ct, object_id=exp.id)
|
||||||
|
for entry in entries:
|
||||||
|
print(f" -> JournalEntry {entry.id}: {entry.description}")
|
||||||
|
items = entry.items.all()
|
||||||
|
if items.exists():
|
||||||
|
for item in items:
|
||||||
|
print(f" -> Item: {item.account.code} {item.type} {item.amount}")
|
||||||
|
else:
|
||||||
|
print(f" -> NO ITEMS FOUND!")
|
||||||
33
move_project.py
Normal file
33
move_project.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
DEST = 'meezan'
|
||||||
|
EXCLUDE = {'.git', '.gemini', DEST, 'move_project.py'}
|
||||||
|
|
||||||
|
def move_project():
|
||||||
|
# Ensure destination exists
|
||||||
|
if not os.path.exists(DEST):
|
||||||
|
os.makedirs(DEST)
|
||||||
|
print(f"Created directory: {DEST}")
|
||||||
|
|
||||||
|
# Iterate and move
|
||||||
|
for item in os.listdir('.'):
|
||||||
|
if item in EXCLUDE:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# specific check for .env to avoid errors if it doesn't exist yet but user mentioned it
|
||||||
|
if item == '.env':
|
||||||
|
pass # allow moving .env if it exists
|
||||||
|
|
||||||
|
src = item
|
||||||
|
dst = os.path.join(DEST, item)
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f"Moving {src} -> {dst}...")
|
||||||
|
shutil.move(src, dst)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error moving {src}: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
move_project()
|
||||||
|
print("Move complete.")
|
||||||
43
patch_expense_categories.py
Normal file
43
patch_expense_categories.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
file_path = 'core/views.py'
|
||||||
|
search_text = "@login_required\ndef expense_categories_view(request): return render(request, 'core/expense_categories.html')"
|
||||||
|
replace_text = """@login_required
|
||||||
|
def expense_categories_view(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
category_id = request.POST.get('category_id')
|
||||||
|
name_en = request.POST.get('name_en')
|
||||||
|
name_ar = request.POST.get('name_ar')
|
||||||
|
description = request.POST.get('description')
|
||||||
|
|
||||||
|
if category_id:
|
||||||
|
# Update existing category
|
||||||
|
category = get_object_or_404(ExpenseCategory, pk=category_id)
|
||||||
|
category.name_en = name_en
|
||||||
|
category.name_ar = name_ar
|
||||||
|
category.description = description
|
||||||
|
category.save()
|
||||||
|
messages.success(request, _('Expense category updated successfully.'))
|
||||||
|
else:
|
||||||
|
# Create new category
|
||||||
|
ExpenseCategory.objects.create(
|
||||||
|
name_en=name_en,
|
||||||
|
name_ar=name_ar,
|
||||||
|
description=description
|
||||||
|
)
|
||||||
|
messages.success(request, _('Expense category added successfully.'))
|
||||||
|
return redirect('expense_categories')
|
||||||
|
|
||||||
|
categories = ExpenseCategory.objects.all().order_by('-id')
|
||||||
|
return render(request, 'core/expense_categories.html', {'categories': categories})"""
|
||||||
|
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
if search_text in content:
|
||||||
|
new_content = content.replace(search_text, replace_text)
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
print("Successfully patched expense_categories_view")
|
||||||
|
else:
|
||||||
|
print("Could not find the target function to replace")
|
||||||
52
patch_invoice_list.py
Normal file
52
patch_invoice_list.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
file_path = 'core/views.py'
|
||||||
|
|
||||||
|
old_content = """@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'))})"""
|
||||||
|
|
||||||
|
new_content = """@login_required
|
||||||
|
def invoice_list(request):
|
||||||
|
sales = Sale.objects.all().order_by('-created_at')
|
||||||
|
|
||||||
|
# Filter by date range
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Filter by customer
|
||||||
|
customer_id = request.GET.get('customer')
|
||||||
|
if customer_id:
|
||||||
|
sales = sales.filter(customer_id=customer_id)
|
||||||
|
|
||||||
|
# Filter by status
|
||||||
|
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)"""
|
||||||
|
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
if old_content in content:
|
||||||
|
content = content.replace(old_content, new_content)
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
print("Successfully patched invoice_list")
|
||||||
|
else:
|
||||||
|
print("Could not find exact match for invoice_list function")
|
||||||
30
patch_views_expense_edit.py
Normal file
30
patch_views_expense_edit.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
@login_required
|
||||||
|
def expense_edit_view(request, pk):
|
||||||
|
expense = get_object_or_404(Expense, pk=pk)
|
||||||
|
if request.method == 'POST':
|
||||||
|
try:
|
||||||
|
category_id = request.POST.get('category')
|
||||||
|
amount = request.POST.get('amount')
|
||||||
|
date = request.POST.get('date')
|
||||||
|
description = request.POST.get('description')
|
||||||
|
payment_method_id = request.POST.get('payment_method')
|
||||||
|
|
||||||
|
category = get_object_or_404(ExpenseCategory, pk=category_id)
|
||||||
|
payment_method = get_object_or_404(PaymentMethod, pk=payment_method_id) if payment_method_id else None
|
||||||
|
|
||||||
|
expense.category = category
|
||||||
|
expense.amount = amount
|
||||||
|
expense.date = date or expense.date
|
||||||
|
expense.description = description
|
||||||
|
expense.payment_method = payment_method
|
||||||
|
|
||||||
|
if 'attachment' in request.FILES:
|
||||||
|
expense.attachment = request.FILES['attachment']
|
||||||
|
|
||||||
|
expense.save()
|
||||||
|
messages.success(request, _('Expense updated successfully.'))
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request, _('Error updating expense: ') + str(e))
|
||||||
|
|
||||||
|
return redirect('expenses')
|
||||||
206
patch_views_sales.py
Normal file
206
patch_views_sales.py
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
file_path = 'core/views.py'
|
||||||
|
|
||||||
|
# New Implementations
|
||||||
|
edit_invoice_code = """
|
||||||
|
@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
|
||||||
|
|
||||||
|
# Serialize items for Vue
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Get first payment method if exists
|
||||||
|
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)
|
||||||
|
"""
|
||||||
|
|
||||||
|
sale_receipt_code = """
|
||||||
|
@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
|
||||||
|
})
|
||||||
|
"""
|
||||||
|
|
||||||
|
update_sale_api_code = """
|
||||||
|
@csrf_exempt
|
||||||
|
def update_sale_api(request, pk):
|
||||||
|
if request.method != 'POST':
|
||||||
|
return JsonResponse({'success': False, 'error': 'Invalid request method'})
|
||||||
|
|
||||||
|
try:
|
||||||
|
sale = Sale.objects.get(pk=pk)
|
||||||
|
data = json.loads(request.body)
|
||||||
|
|
||||||
|
customer_id = data.get('customer_id')
|
||||||
|
items = data.get('items', [])
|
||||||
|
discount = decimal.Decimal(str(data.get('discount', 0)))
|
||||||
|
paid_amount = decimal.Decimal(str(data.get('paid_amount', 0)))
|
||||||
|
payment_type = data.get('payment_type', 'cash')
|
||||||
|
payment_method_id = data.get('payment_method_id')
|
||||||
|
due_date = data.get('due_date')
|
||||||
|
notes = data.get('notes', '')
|
||||||
|
invoice_number = data.get('invoice_number')
|
||||||
|
|
||||||
|
if not items:
|
||||||
|
return JsonResponse({'success': False, 'error': 'No items in sale'})
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
# 1. Revert Stock
|
||||||
|
for item in sale.items.all():
|
||||||
|
product = item.product
|
||||||
|
product.stock_quantity += item.quantity
|
||||||
|
product.save()
|
||||||
|
|
||||||
|
# 2. Delete existing items
|
||||||
|
sale.items.all().delete()
|
||||||
|
|
||||||
|
# 3. Update Sale Details
|
||||||
|
if customer_id:
|
||||||
|
sale.customer_id = customer_id
|
||||||
|
else:
|
||||||
|
sale.customer = None
|
||||||
|
|
||||||
|
sale.discount = discount
|
||||||
|
sale.notes = notes
|
||||||
|
if invoice_number:
|
||||||
|
sale.invoice_number = invoice_number
|
||||||
|
|
||||||
|
if due_date:
|
||||||
|
sale.due_date = due_date
|
||||||
|
else:
|
||||||
|
sale.due_date = None
|
||||||
|
|
||||||
|
# 4. Create New Items and Deduct Stock
|
||||||
|
subtotal = decimal.Decimal(0)
|
||||||
|
|
||||||
|
for item_data in items:
|
||||||
|
product = Product.objects.get(pk=item_data['id'])
|
||||||
|
quantity = decimal.Decimal(str(item_data['quantity']))
|
||||||
|
price = decimal.Decimal(str(item_data['price']))
|
||||||
|
|
||||||
|
# Deduct stock
|
||||||
|
product.stock_quantity -= quantity
|
||||||
|
product.save()
|
||||||
|
|
||||||
|
line_total = price * quantity
|
||||||
|
subtotal += line_total
|
||||||
|
|
||||||
|
SaleItem.objects.create(
|
||||||
|
sale=sale,
|
||||||
|
product=product,
|
||||||
|
quantity=quantity,
|
||||||
|
unit_price=price,
|
||||||
|
line_total=line_total
|
||||||
|
)
|
||||||
|
|
||||||
|
sale.subtotal = subtotal
|
||||||
|
sale.total_amount = subtotal - discount
|
||||||
|
|
||||||
|
# 5. Handle Payments
|
||||||
|
if payment_type == 'credit':
|
||||||
|
sale.status = 'unpaid'
|
||||||
|
sale.paid_amount = 0
|
||||||
|
sale.balance_due = sale.total_amount
|
||||||
|
sale.payments.all().delete()
|
||||||
|
|
||||||
|
elif payment_type == 'cash':
|
||||||
|
sale.status = 'paid'
|
||||||
|
sale.paid_amount = sale.total_amount
|
||||||
|
sale.balance_due = 0
|
||||||
|
|
||||||
|
sale.payments.all().delete()
|
||||||
|
SalePayment.objects.create(
|
||||||
|
sale=sale,
|
||||||
|
amount=sale.total_amount,
|
||||||
|
payment_method_id=payment_method_id if payment_method_id else None,
|
||||||
|
payment_date=timezone.now().date(),
|
||||||
|
notes='Full Payment (Edit)'
|
||||||
|
)
|
||||||
|
|
||||||
|
elif payment_type == 'partial':
|
||||||
|
sale.paid_amount = paid_amount
|
||||||
|
sale.balance_due = sale.total_amount - paid_amount
|
||||||
|
if sale.balance_due <= 0:
|
||||||
|
sale.status = 'paid'
|
||||||
|
sale.balance_due = 0
|
||||||
|
else:
|
||||||
|
sale.status = 'partial'
|
||||||
|
|
||||||
|
sale.payments.all().delete()
|
||||||
|
SalePayment.objects.create(
|
||||||
|
sale=sale,
|
||||||
|
amount=paid_amount,
|
||||||
|
payment_method_id=payment_method_id if payment_method_id else None,
|
||||||
|
payment_date=timezone.now().date(),
|
||||||
|
notes='Partial Payment (Edit)'
|
||||||
|
)
|
||||||
|
|
||||||
|
sale.save()
|
||||||
|
|
||||||
|
return JsonResponse({'success': True, 'sale_id': sale.id})
|
||||||
|
|
||||||
|
except Sale.DoesNotExist:
|
||||||
|
return JsonResponse({'success': False, 'error': 'Sale not found'})
|
||||||
|
except Product.DoesNotExist:
|
||||||
|
return JsonResponse({'success': False, 'error': 'Product not found'})
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'success': False, 'error': str(e)})
|
||||||
|
"""
|
||||||
|
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Replace stubs
|
||||||
|
content = content.replace("def sale_receipt(request, pk): return redirect('invoices')", sale_receipt_code)
|
||||||
|
content = content.replace("def edit_invoice(request, pk): return redirect('invoices')", edit_invoice_code)
|
||||||
|
content = content.replace("@csrf_exempt\ndef update_sale_api(request, pk): return JsonResponse({'success': False})", update_sale_api_code)
|
||||||
|
|
||||||
|
# Handle potential whitespace variations if single-line replace fails
|
||||||
|
if "def edit_invoice(request, pk): return redirect('invoices')" in content: # Check if it persisted
|
||||||
|
pass # worked
|
||||||
|
else:
|
||||||
|
# Fallback for manual check if needed (it should work given exact match from read_file)
|
||||||
|
pass
|
||||||
|
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
Loading…
x
Reference in New Issue
Block a user