Autosave: 20260213-053553
This commit is contained in:
parent
18f1df85e5
commit
51822816f6
7
Aptfile
7
Aptfile
@ -1,7 +0,0 @@
|
|||||||
libglib2.0-0
|
|
||||||
libgobject-2.0-0
|
|
||||||
libpango-1.0-0
|
|
||||||
libpangocairo-1.0-0
|
|
||||||
libcairo2
|
|
||||||
libharfbuzz0b
|
|
||||||
libfontconfig1
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
file_path = 'core/views.py'
|
|
||||||
|
|
||||||
missing_reports = r"""
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def cashflow_report(request):
|
|
||||||
return render(request, 'core/cashflow_report.html')
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def customer_statement(request):
|
|
||||||
return render(request, 'core/customer_statement.html')
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def supplier_statement(request):
|
|
||||||
return render(request, 'core/supplier_statement.html')
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(file_path, 'a') as f:
|
|
||||||
f.write(missing_reports)
|
|
||||||
|
|
||||||
print("Appended missing reports to core/views.py")
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
with open('core/views.py', 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
with open('core/patch_views_vat.py', 'r') as f:
|
|
||||||
new_func = f.read()
|
|
||||||
|
|
||||||
# Regex to find the function definition
|
|
||||||
# It starts with @csrf_exempt\ndef create_sale_api(request):
|
|
||||||
# And ends before the next function definition (which likely starts with @ or def)
|
|
||||||
pattern = r"@csrf_exempt\s+def create_sale_api(request):.*?return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)"
|
|
||||||
|
|
||||||
# Note: The pattern needs to match the indentation and multiline content.
|
|
||||||
# Since regex for code blocks is tricky, I will use a simpler approach:
|
|
||||||
# 1. Read the file lines.
|
|
||||||
# 2. Find start line of create_sale_api.
|
|
||||||
# 3. Find the end line (start of next function or end of file).
|
|
||||||
# 4. Replace lines.
|
|
||||||
|
|
||||||
lines = content.splitlines()
|
|
||||||
start_index = -1
|
|
||||||
end_index = -1
|
|
||||||
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
if line.strip() == "def create_sale_api(request):":
|
|
||||||
# Check if previous line is decorator
|
|
||||||
if i > 0 and lines[i-1].strip() == "@csrf_exempt":
|
|
||||||
start_index = i - 1
|
|
||||||
else:
|
|
||||||
start_index = i
|
|
||||||
break
|
|
||||||
|
|
||||||
if start_index != -1:
|
|
||||||
# Find the next function or end
|
|
||||||
# We look for next line starting with 'def ' or '@' at top level
|
|
||||||
for i in range(start_index + 1, len(lines)):
|
|
||||||
if lines[i].startswith("def ") or lines[i].startswith("@"):
|
|
||||||
end_index = i
|
|
||||||
break
|
|
||||||
if end_index == -1:
|
|
||||||
end_index = len(lines)
|
|
||||||
|
|
||||||
# Replace
|
|
||||||
new_lines = new_func.splitlines()
|
|
||||||
# Ensure new lines have correct indentation if needed (but views.py is top level mostly)
|
|
||||||
|
|
||||||
# We need to preserve the imports and structure.
|
|
||||||
# The new_func is complete.
|
|
||||||
|
|
||||||
final_lines = lines[:start_index] + new_lines + lines[end_index:]
|
|
||||||
|
|
||||||
with open('core/views.py', 'w') as f:
|
|
||||||
f.write('\n'.join(final_lines))
|
|
||||||
print("Successfully patched create_sale_api")
|
|
||||||
else:
|
|
||||||
print("Could not find create_sale_api function")
|
|
||||||
@ -20,21 +20,6 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# --- WeasyPrint Library Preloading ---
|
|
||||||
import ctypes
|
|
||||||
import ctypes.util
|
|
||||||
|
|
||||||
# Try to find and load libraries dynamically to help WeasyPrint on different platforms
|
|
||||||
libs_to_load = ['glib-2.0', 'gobject-2.0', 'fontconfig', 'cairo', 'pango-1.0', 'pangoft2-1.0']
|
|
||||||
for lib_name in libs_to_load:
|
|
||||||
try:
|
|
||||||
path = ctypes.util.find_library(lib_name)
|
|
||||||
if path:
|
|
||||||
ctypes.CDLL(path)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
# -------------------------------------
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,76 +0,0 @@
|
|||||||
@login_required
|
|
||||||
def send_invoice_whatsapp(request):
|
|
||||||
if request.method != 'POST':
|
|
||||||
return JsonResponse({'success': False, 'error': 'Method not allowed'})
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Handle JSON payload
|
|
||||||
data = json.loads(request.body)
|
|
||||||
sale_id = data.get('sale_id')
|
|
||||||
phone = data.get('phone')
|
|
||||||
pdf_data = data.get('pdf_data') # Base64 string
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
# Fallback to Form Data
|
|
||||||
sale_id = request.POST.get('sale_id')
|
|
||||||
phone = request.POST.get('phone')
|
|
||||||
pdf_data = None
|
|
||||||
|
|
||||||
if not sale_id:
|
|
||||||
return JsonResponse({'success': False, 'error': 'Sale ID missing'})
|
|
||||||
|
|
||||||
sale = get_object_or_404(Sale, pk=sale_id)
|
|
||||||
|
|
||||||
if not phone:
|
|
||||||
if sale.customer and sale.customer.phone:
|
|
||||||
phone = sale.customer.phone
|
|
||||||
else:
|
|
||||||
return JsonResponse({'success': False, 'error': 'Phone number missing'})
|
|
||||||
|
|
||||||
try:
|
|
||||||
# If PDF data is present, save and send document
|
|
||||||
if pdf_data:
|
|
||||||
# Remove header if present (data:application/pdf;base64,)
|
|
||||||
if ',' in pdf_data:
|
|
||||||
pdf_data = pdf_data.split(',')[1]
|
|
||||||
|
|
||||||
file_data = base64.b64decode(pdf_data)
|
|
||||||
dir_path = os.path.join(django_settings.MEDIA_ROOT, 'temp_invoices')
|
|
||||||
os.makedirs(dir_path, exist_ok=True)
|
|
||||||
|
|
||||||
filename = f"invoice_{sale.id}_{int(timezone.now().timestamp())}.pdf"
|
|
||||||
file_path = os.path.join(dir_path, filename)
|
|
||||||
|
|
||||||
with open(file_path, 'wb') as f:
|
|
||||||
f.write(file_data)
|
|
||||||
|
|
||||||
# Construct URL
|
|
||||||
file_url = request.build_absolute_uri(django_settings.MEDIA_URL + 'temp_invoices/' + filename)
|
|
||||||
|
|
||||||
success, response_msg = send_whatsapp_document(phone, file_url, caption=f"Invoice #{sale.invoice_number or sale.id}")
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Fallback to Text Link
|
|
||||||
receipt_url = request.build_absolute_uri(reverse('sale_receipt', args=[sale.pk]))
|
|
||||||
|
|
||||||
message = (
|
|
||||||
f"Hello {sale.customer.name if sale.customer else 'Guest'},
|
|
||||||
"
|
|
||||||
f"Here is your invoice #{sale.invoice_number or sale.id}.
|
|
||||||
"
|
|
||||||
f"Total: {sale.total_amount}
|
|
||||||
"
|
|
||||||
f"View Invoice: {receipt_url}
|
|
||||||
"
|
|
||||||
f"Thank you for your business!"
|
|
||||||
)
|
|
||||||
|
|
||||||
success, response_msg = send_whatsapp_message(phone, message)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
return JsonResponse({'success': True, 'message': response_msg})
|
|
||||||
else:
|
|
||||||
return JsonResponse({'success': False, 'error': response_msg})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"WhatsApp Error: {e}")
|
|
||||||
return JsonResponse({'success': False, 'error': str(e)}) # Changed to str(e) for clarity
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
|
|
||||||
@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):
|
|
||||||
# Simplified Cashflow
|
|
||||||
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)
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
@login_required
|
|
||||||
def pos(request):
|
|
||||||
from .models import CashierSession
|
|
||||||
# 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 not settings or 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)
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
@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)
|
|
||||||
@ -1,161 +0,0 @@
|
|||||||
@csrf_exempt
|
|
||||||
def create_sale_api(request):
|
|
||||||
if request.method == 'POST':
|
|
||||||
try:
|
|
||||||
data = json.loads(request.body)
|
|
||||||
customer_id = data.get('customer_id')
|
|
||||||
invoice_number = data.get('invoice_number', '')
|
|
||||||
items = data.get('items', [])
|
|
||||||
|
|
||||||
# Retrieve amounts
|
|
||||||
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)
|
|
||||||
discount = data.get('discount', 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', '')
|
|
||||||
|
|
||||||
# Loyalty data
|
|
||||||
points_to_redeem = data.get('loyalty_points_redeemed', 0)
|
|
||||||
|
|
||||||
customer = None
|
|
||||||
if customer_id:
|
|
||||||
customer = Customer.objects.get(id=customer_id)
|
|
||||||
|
|
||||||
if not customer and payment_type != 'cash':
|
|
||||||
return JsonResponse({'success': False, 'error': _('Credit or Partial payments are not allowed for Guest customers.')}, status=400)
|
|
||||||
|
|
||||||
settings = SystemSetting.objects.first()
|
|
||||||
if not settings:
|
|
||||||
settings = SystemSetting.objects.create()
|
|
||||||
|
|
||||||
loyalty_discount = 0
|
|
||||||
if settings.loyalty_enabled and customer and points_to_redeem > 0:
|
|
||||||
if customer.loyalty_points >= points_to_redeem:
|
|
||||||
loyalty_discount = float(points_to_redeem) * float(settings.currency_per_point)
|
|
||||||
|
|
||||||
sale = Sale.objects.create(
|
|
||||||
customer=customer,
|
|
||||||
invoice_number=invoice_number,
|
|
||||||
subtotal=subtotal,
|
|
||||||
vat_amount=vat_amount,
|
|
||||||
total_amount=total_amount,
|
|
||||||
paid_amount=paid_amount,
|
|
||||||
balance_due=float(total_amount) - float(paid_amount),
|
|
||||||
discount=discount,
|
|
||||||
loyalty_points_redeemed=points_to_redeem,
|
|
||||||
loyalty_discount_amount=loyalty_discount,
|
|
||||||
payment_type=payment_type,
|
|
||||||
due_date=due_date if due_date else None,
|
|
||||||
notes=notes,
|
|
||||||
created_by=request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set status based on payments
|
|
||||||
if float(paid_amount) >= float(total_amount):
|
|
||||||
sale.status = 'paid'
|
|
||||||
elif float(paid_amount) > 0:
|
|
||||||
sale.status = 'partial'
|
|
||||||
else:
|
|
||||||
sale.status = 'unpaid'
|
|
||||||
sale.save()
|
|
||||||
|
|
||||||
# Record initial payment if any
|
|
||||||
if float(paid_amount) > 0:
|
|
||||||
pm = None
|
|
||||||
if payment_method_id:
|
|
||||||
pm = PaymentMethod.objects.filter(id=payment_method_id).first()
|
|
||||||
|
|
||||||
SalePayment.objects.create(
|
|
||||||
sale=sale,
|
|
||||||
amount=paid_amount,
|
|
||||||
payment_method=pm,
|
|
||||||
payment_method_name=pm.name_en if pm else payment_type.capitalize(),
|
|
||||||
notes="Initial payment",
|
|
||||||
created_by=request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
product = Product.objects.get(id=item['id'])
|
|
||||||
SaleItem.objects.create(
|
|
||||||
sale=sale,
|
|
||||||
product=product,
|
|
||||||
quantity=item['quantity'],
|
|
||||||
unit_price=item['price'],
|
|
||||||
line_total=item['line_total']
|
|
||||||
)
|
|
||||||
product.stock_quantity -= int(item['quantity'])
|
|
||||||
product.save()
|
|
||||||
|
|
||||||
# Handle Loyalty Points
|
|
||||||
if settings.loyalty_enabled and customer:
|
|
||||||
# Earn Points
|
|
||||||
points_earned = float(total_amount) * float(settings.points_per_currency)
|
|
||||||
if customer.loyalty_tier:
|
|
||||||
points_earned *= float(customer.loyalty_tier.point_multiplier)
|
|
||||||
|
|
||||||
if points_earned > 0:
|
|
||||||
customer.loyalty_points += decimal.Decimal(str(points_earned))
|
|
||||||
LoyaltyTransaction.objects.create(
|
|
||||||
customer=customer,
|
|
||||||
sale=sale,
|
|
||||||
transaction_type='earned',
|
|
||||||
points=points_earned,
|
|
||||||
notes=f"Points earned from Sale #{sale.id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Redeem Points
|
|
||||||
if points_to_redeem > 0:
|
|
||||||
customer.loyalty_points -= decimal.Decimal(str(points_to_redeem))
|
|
||||||
LoyaltyTransaction.objects.create(
|
|
||||||
customer=customer,
|
|
||||||
sale=sale,
|
|
||||||
transaction_type='redeemed',
|
|
||||||
points=-points_to_redeem,
|
|
||||||
notes=f"Points redeemed for Sale #{sale.id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
customer.update_tier()
|
|
||||||
customer.save()
|
|
||||||
|
|
||||||
return JsonResponse({
|
|
||||||
'success': True,
|
|
||||||
'sale_id': sale.id,
|
|
||||||
'business': {
|
|
||||||
'name': settings.business_name,
|
|
||||||
'address': settings.address,
|
|
||||||
'phone': settings.phone,
|
|
||||||
'email': settings.email,
|
|
||||||
'currency': settings.currency_symbol,
|
|
||||||
'vat_number': settings.vat_number,
|
|
||||||
'registration_number': settings.registration_number,
|
|
||||||
'logo_url': settings.logo.url if settings.logo else None
|
|
||||||
},
|
|
||||||
'sale': {
|
|
||||||
'id': sale.id,
|
|
||||||
'invoice_number': sale.invoice_number,
|
|
||||||
'created_at': sale.created_at.strftime("%Y-%m-%d %H:%M"),
|
|
||||||
'subtotal': float(sale.subtotal),
|
|
||||||
'vat_amount': float(sale.vat_amount),
|
|
||||||
'total': float(sale.total_amount),
|
|
||||||
'discount': float(sale.discount),
|
|
||||||
'paid': float(sale.paid_amount),
|
|
||||||
'balance': float(sale.balance_due),
|
|
||||||
'customer_name': sale.customer.name if sale.customer else 'Guest',
|
|
||||||
'items': [
|
|
||||||
{
|
|
||||||
'name_en': si.product.name_en,
|
|
||||||
'name_ar': si.product.name_ar,
|
|
||||||
'qty': si.quantity,
|
|
||||||
'price': float(si.unit_price),
|
|
||||||
'total': float(si.line_total)
|
|
||||||
} for si in sale.items.all()
|
|
||||||
]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
|
||||||
55
core/pdf_utils.py
Normal file
55
core/pdf_utils.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import os
|
||||||
|
import ctypes
|
||||||
|
import ctypes.util
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def patch_weasyprint_libraries():
|
||||||
|
"""
|
||||||
|
Attempts to load required system libraries for WeasyPrint.
|
||||||
|
This helps on systems where libraries are installed but not in the standard search path
|
||||||
|
or have slightly different names.
|
||||||
|
"""
|
||||||
|
# Common library names for WeasyPrint dependencies
|
||||||
|
libs_to_load = [
|
||||||
|
('glib-2.0', ['libglib-2.0.so.0', 'libglib-2.0.so']),
|
||||||
|
('gobject-2.0', ['libgobject-2.0.so.0', 'libgobject-2.0.so', 'libgobject-2.0-0']),
|
||||||
|
('fontconfig', ['libfontconfig.so.1', 'libfontconfig.so']),
|
||||||
|
('cairo', ['libcairo.so.2', 'libcairo.so']),
|
||||||
|
('pango-1.0', ['libpango-1.0.so.0', 'libpango-1.0.so']),
|
||||||
|
('pangoft2-1.0', ['libpangoft2-1.0.so.0', 'libpangoft2-1.0.so']),
|
||||||
|
('harfbuzz', ['libharfbuzz.so.0', 'libharfbuzz.so']),
|
||||||
|
]
|
||||||
|
|
||||||
|
for lib_id, fallbacks in libs_to_load:
|
||||||
|
try:
|
||||||
|
# First try standard find_library
|
||||||
|
path = ctypes.util.find_library(lib_id)
|
||||||
|
if path:
|
||||||
|
ctypes.CDLL(path)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If not found, try fallbacks
|
||||||
|
for fallback in fallbacks:
|
||||||
|
try:
|
||||||
|
ctypes.CDLL(fallback)
|
||||||
|
break
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Failed to load library {lib_id}: {e}")
|
||||||
|
|
||||||
|
# Call it immediately when this module is imported
|
||||||
|
patch_weasyprint_libraries()
|
||||||
|
|
||||||
|
def get_weasyprint_html():
|
||||||
|
"""
|
||||||
|
Safe wrapper for importing WeasyPrint HTML.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from weasyprint import HTML
|
||||||
|
return HTML
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to import WeasyPrint HTML: {e}")
|
||||||
|
raise
|
||||||
@ -44,7 +44,8 @@ logger = logging.getLogger(__name__)
|
|||||||
# --- Basic Views ---
|
# --- Basic Views ---
|
||||||
|
|
||||||
def test_pdf_view(request):
|
def test_pdf_view(request):
|
||||||
from weasyprint import HTML
|
from .pdf_utils import get_weasyprint_html
|
||||||
|
HTML = get_weasyprint_html()
|
||||||
html_string = "<h1>Test PDF</h1>"
|
html_string = "<h1>Test PDF</h1>"
|
||||||
pdf = HTML(string=html_string).write_pdf()
|
pdf = HTML(string=html_string).write_pdf()
|
||||||
return HttpResponse(pdf, content_type='application/pdf')
|
return HttpResponse(pdf, content_type='application/pdf')
|
||||||
@ -1592,7 +1593,8 @@ def get_pdf_context(obj, doc_type):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def generate_pdf_file(template, context, request):
|
def generate_pdf_file(template, context, request):
|
||||||
from weasyprint import HTML
|
from .pdf_utils import get_weasyprint_html
|
||||||
|
HTML = get_weasyprint_html()
|
||||||
html_string = render_to_string(template, context, request=request)
|
html_string = render_to_string(template, context, request=request)
|
||||||
base_url = request.build_absolute_uri('/')
|
base_url = request.build_absolute_uri('/')
|
||||||
return HTML(string=html_string, base_url=base_url).write_pdf()
|
return HTML(string=html_string, base_url=base_url).write_pdf()
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
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!")
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
import os
|
|
||||||
import django
|
|
||||||
from django.conf import settings
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# Setup Django environment
|
|
||||||
sys.path.append(os.getcwd())
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
|
||||||
django.setup()
|
|
||||||
|
|
||||||
from django.test import RequestFactory
|
|
||||||
from core.views import index
|
|
||||||
|
|
||||||
def test_root_view():
|
|
||||||
factory = RequestFactory()
|
|
||||||
request = factory.get('/')
|
|
||||||
|
|
||||||
# Simulate logged in user (since index is login_required)
|
|
||||||
from django.contrib.auth.models import AnonymousUser, User
|
|
||||||
|
|
||||||
# Create a dummy user for testing
|
|
||||||
if not User.objects.filter(username='testadmin').exists():
|
|
||||||
user = User.objects.create_superuser('testadmin', 'admin@example.com', 'pass')
|
|
||||||
else:
|
|
||||||
user = User.objects.get(username='testadmin')
|
|
||||||
|
|
||||||
request.user = user # Authenticated
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = index(request)
|
|
||||||
print(f"Authenticated Root View Status: {response.status_code}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Authenticated Root View Error: {e}")
|
|
||||||
|
|
||||||
# Test unauthenticated (should redirect)
|
|
||||||
request_anon = factory.get('/')
|
|
||||||
request_anon.user = AnonymousUser()
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
# We can't easily run the decorator logic with RequestFactory directly calling the view function
|
|
||||||
# unless we use the view wrapped in login_required manually or via client.
|
|
||||||
|
|
||||||
from django.test import Client
|
|
||||||
client = Client()
|
|
||||||
response = client.get('/')
|
|
||||||
print(f"Client Root Get Status: {response.status_code}")
|
|
||||||
if response.status_code == 302:
|
|
||||||
print(f"Redirects to: {response.url}")
|
|
||||||
|
|
||||||
# Check login page
|
|
||||||
response_login = client.get('/accounts/login/')
|
|
||||||
print(f"Client Login Get Status: {response_login.status_code}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_root_view()
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
|
|
||||||
file_path = 'core/templates/core/settings.html'
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
print("File length:", len(content))
|
|
||||||
|
|
||||||
# Check context for Nav Tab
|
|
||||||
if 'id="devices-tab"' in content:
|
|
||||||
print("Devices tab already exists.")
|
|
||||||
else:
|
|
||||||
context_str = 'id="whatsapp-tab" data-bs-toggle="pill" data-bs-target="#whatsapp" type="button" role="tab">
|
|
||||||
<i class="bi bi-whatsapp me-2"></i>{% trans "WhatsApp Gateway" %}
|
|
||||||
</button>
|
|
||||||
</li>'
|
|
||||||
if context_str in content:
|
|
||||||
print("Found Nav Tab context.")
|
|
||||||
else:
|
|
||||||
print("Nav Tab context NOT found. Dumping nearby content:")
|
|
||||||
# Find rough location
|
|
||||||
idx = content.find('id="whatsapp-tab"')
|
|
||||||
if idx != -1:
|
|
||||||
print(content[idx:idx+300])
|
|
||||||
|
|
||||||
# Check context for Tab Pane
|
|
||||||
if 'id="devices" role="tabpanel"' in content:
|
|
||||||
print("Devices pane already exists.")
|
|
||||||
else:
|
|
||||||
# Try to find the end of tab content
|
|
||||||
# Look for Add Tier Modal
|
|
||||||
idx = content.find('<!-- Add Tier Modal -->')
|
|
||||||
if idx != -1:
|
|
||||||
print("Found Add Tier Modal at index:", idx)
|
|
||||||
print("Preceding content:")
|
|
||||||
print(content[idx-100:idx])
|
|
||||||
else:
|
|
||||||
print("Add Tier Modal NOT found.")
|
|
||||||
|
|
||||||
22
debug_url.py
22
debug_url.py
@ -1,22 +0,0 @@
|
|||||||
|
|
||||||
import os
|
|
||||||
import django
|
|
||||||
from django.conf import settings
|
|
||||||
from django.urls import reverse, resolve
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
|
||||||
django.setup()
|
|
||||||
|
|
||||||
try:
|
|
||||||
print("Attempting to reverse 'inventory'...")
|
|
||||||
url = reverse('inventory')
|
|
||||||
print(f"Success: 'inventory' -> {url}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error reversing 'inventory': {e}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
print("Attempting to reverse 'index'...")
|
|
||||||
url = reverse('index')
|
|
||||||
print(f"Success: 'index' -> {url}")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error reversing 'index': {e}")
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
import django
|
|
||||||
|
|
||||||
# Add project root to path
|
|
||||||
sys.path.append(os.getcwd())
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
|
||||||
django.setup()
|
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
|
|
||||||
def reset_password():
|
|
||||||
User = get_user_model()
|
|
||||||
username = 'admin'
|
|
||||||
password = 'admin'
|
|
||||||
|
|
||||||
try:
|
|
||||||
user, created = User.objects.get_or_create(username=username)
|
|
||||||
user.set_password(password)
|
|
||||||
user.is_staff = True
|
|
||||||
user.is_superuser = True
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
action = "created" if created else "reset"
|
|
||||||
print(f"Successfully {action} password for user '{username}' to '{password}'.")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error resetting password: {e}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
reset_password()
|
|
||||||
15
manage.py
15
manage.py
@ -14,21 +14,6 @@ def main():
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# --- WeasyPrint Library Preloading ---
|
|
||||||
import ctypes
|
|
||||||
import ctypes.util
|
|
||||||
|
|
||||||
# Try to find and load libraries dynamically to help WeasyPrint on different platforms
|
|
||||||
libs_to_load = ['glib-2.0', 'gobject-2.0', 'fontconfig', 'cairo', 'pango-1.0', 'pangoft2-1.0']
|
|
||||||
for lib_name in libs_to_load:
|
|
||||||
try:
|
|
||||||
path = ctypes.util.find_library(lib_name)
|
|
||||||
if path:
|
|
||||||
ctypes.CDLL(path)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
# -------------------------------------
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||||
try:
|
try:
|
||||||
from django.core.management import execute_from_command_line
|
from django.core.management import execute_from_command_line
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
Manage.py started
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
from django.db import connection
|
|
||||||
|
|
||||||
def fix_db():
|
|
||||||
print("Starting DB Fix...")
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
# 1. Check/Add is_service to core_product
|
|
||||||
try:
|
|
||||||
cursor.execute("SELECT is_service FROM core_product LIMIT 1")
|
|
||||||
print("SUCCESS: is_service already exists in core_product.")
|
|
||||||
except Exception:
|
|
||||||
print("Attempting to add is_service column...")
|
|
||||||
try:
|
|
||||||
# Try MySQL syntax first
|
|
||||||
cursor.execute("ALTER TABLE core_product ADD COLUMN is_service tinyint(1) NOT NULL DEFAULT 0;")
|
|
||||||
print("FIXED: Added is_service column to core_product.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR adding is_service: {e}")
|
|
||||||
|
|
||||||
# 2. Check/Add is_active to core_paymentmethod
|
|
||||||
try:
|
|
||||||
cursor.execute("SELECT is_active FROM core_paymentmethod LIMIT 1")
|
|
||||||
print("SUCCESS: is_active already exists in core_paymentmethod.")
|
|
||||||
except Exception:
|
|
||||||
print("Attempting to add is_active column...")
|
|
||||||
try:
|
|
||||||
cursor.execute("ALTER TABLE core_paymentmethod ADD COLUMN is_active tinyint(1) NOT NULL DEFAULT 1;")
|
|
||||||
print("FIXED: Added is_active column to core_paymentmethod.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR adding is_active: {e}")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
fix_db()
|
|
||||||
@ -1 +0,0 @@
|
|||||||
# This script has been disabled/removed as the project is deployed to the root.
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
file_path = 'core/templates/base.html'
|
|
||||||
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
search_text = """ {% if user.is_authenticated %}
|
|
||||||
<div class="p-3">
|
|
||||||
<button type="button" id="sidebarCollapse" class="btn btn-light shadow-sm">
|
|
||||||
<i class="bi bi-list fs-5"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}"""
|
|
||||||
|
|
||||||
replace_text = """ {% if user.is_authenticated %}
|
|
||||||
<div class="p-3 d-flex justify-content-between align-items-center">
|
|
||||||
<button type="button" id="sidebarCollapse" class="btn btn-light shadow-sm">
|
|
||||||
<i class="bi bi-list fs-5"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="language-switcher">
|
|
||||||
<form action="{% url 'set_language' %}" method="post" class="d-flex align-items-center">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="next" value="{{ request.get_full_path|default:'/' }}">
|
|
||||||
<i class="bi bi-globe2 me-2 text-muted"></i>
|
|
||||||
<select name="language" class="form-select form-select-sm shadow-sm border-0" style="width: auto; background-color: #f8f9fa;" onchange="this.form.submit()">
|
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
|
||||||
{% get_available_languages as LANGUAGES %}
|
|
||||||
{% get_language_info_list for LANGUAGES as languages %}
|
|
||||||
{% for language in languages %}
|
|
||||||
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
|
|
||||||
{{ language.name_local }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}"""
|
|
||||||
|
|
||||||
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 base.html")
|
|
||||||
else:
|
|
||||||
print("Search text not found in base.html. Please check formatting.")
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
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")
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
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")
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
path = 'core/models.py'
|
|
||||||
with open(path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Patch SalePayment
|
|
||||||
old_sale_payment = """ notes = models.TextField(_("Notes"), blank=True)
|
|
||||||
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="sale_payments")
|
|
||||||
|
|
||||||
def __str__(self):"""
|
|
||||||
new_sale_payment = """ notes = models.TextField(_("Notes"), blank=True)
|
|
||||||
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="sale_payments")
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
def __str__(self):"""
|
|
||||||
|
|
||||||
# Patch PurchasePayment
|
|
||||||
old_purchase_payment = """ notes = models.TextField(_("Notes"), blank=True)
|
|
||||||
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchase_payments")
|
|
||||||
|
|
||||||
def __str__(self):"""
|
|
||||||
new_purchase_payment = """ notes = models.TextField(_("Notes"), blank=True)
|
|
||||||
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name="purchase_payments")
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
def __str__(self):"""
|
|
||||||
|
|
||||||
# Check if SalePayment already has created_at
|
|
||||||
# A simple check: if we find the new pattern, we skip
|
|
||||||
if new_sale_payment in content:
|
|
||||||
print("SalePayment already patched.")
|
|
||||||
else:
|
|
||||||
content = content.replace(old_sale_payment, new_sale_payment)
|
|
||||||
|
|
||||||
if new_purchase_payment in content:
|
|
||||||
print("PurchasePayment already patched.")
|
|
||||||
else:
|
|
||||||
content = content.replace(old_purchase_payment, new_purchase_payment)
|
|
||||||
|
|
||||||
with open(path, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
print("Patched core/models.py")
|
|
||||||
@ -1,211 +0,0 @@
|
|||||||
import os
|
|
||||||
import decimal
|
|
||||||
from django.db import transaction
|
|
||||||
from django.db.models import Sum
|
|
||||||
|
|
||||||
file_path = 'core/views.py'
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# 1. Update invoice_list
|
|
||||||
old_invoice_list = """@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')),
|
|
||||||
'customers': Customer.objects.all(),
|
|
||||||
'site_settings': SystemSetting.objects.first()
|
|
||||||
})"""
|
|
||||||
|
|
||||||
new_invoice_list = """@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')),
|
|
||||||
'customers': Customer.objects.all(),
|
|
||||||
'site_settings': SystemSetting.objects.first(),
|
|
||||||
'payment_methods': PaymentMethod.objects.filter(is_active=True)
|
|
||||||
})"""
|
|
||||||
|
|
||||||
if old_invoice_list in content:
|
|
||||||
content = content.replace(old_invoice_list, new_invoice_list)
|
|
||||||
else:
|
|
||||||
print("Could not find old_invoice_list")
|
|
||||||
|
|
||||||
# 2. Update purchases
|
|
||||||
old_purchases = """@login_required
|
|
||||||
def purchases(request):
|
|
||||||
purchases = Purchase.objects.all().order_by('-created_at')
|
|
||||||
paginator = Paginator(purchases, 25)
|
|
||||||
return render(request, 'core/purchases.html', {'purchases': paginator.get_page(request.GET.get('page'))})"""
|
|
||||||
|
|
||||||
new_purchases = """@login_required
|
|
||||||
def purchases(request):
|
|
||||||
purchases = Purchase.objects.all().order_by('-created_at')
|
|
||||||
paginator = Paginator(purchases, 25)
|
|
||||||
return render(request, 'core/purchases.html', {
|
|
||||||
'purchases': paginator.get_page(request.GET.get('page')),
|
|
||||||
'payment_methods': PaymentMethod.objects.filter(is_active=True)
|
|
||||||
})"""
|
|
||||||
|
|
||||||
if old_purchases in content:
|
|
||||||
content = content.replace(old_purchases, new_purchases)
|
|
||||||
else:
|
|
||||||
print("Could not find old_purchases")
|
|
||||||
|
|
||||||
# 3. Update invoice_detail
|
|
||||||
old_invoice_detail = """@login_required
|
|
||||||
def invoice_detail(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/invoice_detail.html', {
|
|
||||||
'sale': sale,
|
|
||||||
'settings': settings,
|
|
||||||
'amount_in_words': amount_in_words
|
|
||||||
})"""
|
|
||||||
|
|
||||||
new_invoice_detail = """@login_required
|
|
||||||
def invoice_detail(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/invoice_detail.html', {
|
|
||||||
'sale': sale,
|
|
||||||
'settings': settings,
|
|
||||||
'amount_in_words': amount_in_words,
|
|
||||||
'payment_methods': PaymentMethod.objects.filter(is_active=True)
|
|
||||||
})"""
|
|
||||||
|
|
||||||
if old_invoice_detail in content:
|
|
||||||
content = content.replace(old_invoice_detail, new_invoice_detail)
|
|
||||||
else:
|
|
||||||
print("Could not find old_invoice_detail")
|
|
||||||
|
|
||||||
# 4. Update purchase_detail
|
|
||||||
old_purchase_detail = """@login_required
|
|
||||||
def purchase_detail(request, pk):
|
|
||||||
purchase = get_object_or_404(Purchase, pk=pk)
|
|
||||||
settings = SystemSetting.objects.first()
|
|
||||||
return render(request, 'core/purchase_detail.html', {
|
|
||||||
'purchase': purchase,
|
|
||||||
'settings': settings
|
|
||||||
})"""
|
|
||||||
|
|
||||||
new_purchase_detail = """@login_required
|
|
||||||
def purchase_detail(request, pk):
|
|
||||||
purchase = get_object_or_404(Purchase, pk=pk)
|
|
||||||
settings = SystemSetting.objects.first()
|
|
||||||
return render(request, 'core/purchase_detail.html', {
|
|
||||||
'purchase': purchase,
|
|
||||||
'settings': settings,
|
|
||||||
'payment_methods': PaymentMethod.objects.filter(is_active=True)
|
|
||||||
})"""
|
|
||||||
|
|
||||||
if old_purchase_detail in content:
|
|
||||||
content = content.replace(old_purchase_detail, new_purchase_detail)
|
|
||||||
else:
|
|
||||||
print("Could not find old_purchase_detail")
|
|
||||||
|
|
||||||
# 5. Replace add_sale_payment stub
|
|
||||||
old_add_sale_payment = """@login_required
|
|
||||||
def add_sale_payment(request, pk): return redirect('invoices')"""
|
|
||||||
|
|
||||||
new_add_sale_payment = """@login_required
|
|
||||||
def add_sale_payment(request, pk):
|
|
||||||
sale = get_object_or_404(Sale, pk=pk)
|
|
||||||
if request.method == 'POST':
|
|
||||||
try:
|
|
||||||
amount = decimal.Decimal(request.POST.get('amount', 0))
|
|
||||||
payment_method_id = request.POST.get('payment_method_id')
|
|
||||||
notes = request.POST.get('notes', '')
|
|
||||||
|
|
||||||
if amount > 0:
|
|
||||||
with transaction.atomic():
|
|
||||||
SalePayment.objects.create(
|
|
||||||
sale=sale,
|
|
||||||
amount=amount,
|
|
||||||
payment_method_id=payment_method_id,
|
|
||||||
created_by=request.user,
|
|
||||||
notes=notes
|
|
||||||
)
|
|
||||||
|
|
||||||
# Recalculate totals
|
|
||||||
total_paid = SalePayment.objects.filter(sale=sale).aggregate(Sum('amount'))['amount__sum'] or 0
|
|
||||||
sale.paid_amount = total_paid
|
|
||||||
sale.balance_due = sale.total_amount - total_paid
|
|
||||||
|
|
||||||
if sale.balance_due <= 0:
|
|
||||||
sale.status = 'paid'
|
|
||||||
elif sale.paid_amount > 0:
|
|
||||||
sale.status = 'partial'
|
|
||||||
else:
|
|
||||||
sale.status = 'unpaid'
|
|
||||||
|
|
||||||
sale.save()
|
|
||||||
messages.success(request, f"Payment of {amount} recorded successfully.")
|
|
||||||
else:
|
|
||||||
messages.error(request, "Amount must be greater than 0.")
|
|
||||||
except Exception as e:
|
|
||||||
messages.error(request, f"Error recording payment: {e}")
|
|
||||||
|
|
||||||
return redirect('invoices')"""
|
|
||||||
|
|
||||||
if old_add_sale_payment in content:
|
|
||||||
content = content.replace(old_add_sale_payment, new_add_sale_payment)
|
|
||||||
else:
|
|
||||||
print("Could not find old_add_sale_payment")
|
|
||||||
|
|
||||||
# 6. Replace add_purchase_payment stub
|
|
||||||
old_add_purchase_payment = """@login_required
|
|
||||||
def add_purchase_payment(request, pk): return redirect('purchases')"""
|
|
||||||
|
|
||||||
new_add_purchase_payment = """@login_required
|
|
||||||
def add_purchase_payment(request, pk):
|
|
||||||
purchase = get_object_or_404(Purchase, pk=pk)
|
|
||||||
if request.method == 'POST':
|
|
||||||
try:
|
|
||||||
amount = decimal.Decimal(request.POST.get('amount', 0))
|
|
||||||
payment_method_id = request.POST.get('payment_method_id')
|
|
||||||
notes = request.POST.get('notes', '')
|
|
||||||
|
|
||||||
if amount > 0:
|
|
||||||
with transaction.atomic():
|
|
||||||
PurchasePayment.objects.create(
|
|
||||||
purchase=purchase,
|
|
||||||
amount=amount,
|
|
||||||
payment_method_id=payment_method_id,
|
|
||||||
created_by=request.user,
|
|
||||||
notes=notes
|
|
||||||
)
|
|
||||||
|
|
||||||
# Recalculate totals
|
|
||||||
total_paid = PurchasePayment.objects.filter(purchase=purchase).aggregate(Sum('amount'))['amount__sum'] or 0
|
|
||||||
purchase.paid_amount = total_paid
|
|
||||||
purchase.balance_due = purchase.total_amount - total_paid
|
|
||||||
|
|
||||||
if purchase.balance_due <= 0:
|
|
||||||
purchase.status = 'paid'
|
|
||||||
elif purchase.paid_amount > 0:
|
|
||||||
purchase.status = 'partial'
|
|
||||||
else:
|
|
||||||
purchase.status = 'unpaid'
|
|
||||||
|
|
||||||
purchase.save()
|
|
||||||
messages.success(request, f"Payment of {amount} recorded successfully.")
|
|
||||||
else:
|
|
||||||
messages.error(request, "Amount must be greater than 0.")
|
|
||||||
except Exception as e:
|
|
||||||
messages.error(request, f"Error recording payment: {e}")
|
|
||||||
|
|
||||||
return redirect('purchases')"""
|
|
||||||
|
|
||||||
if old_add_purchase_payment in content:
|
|
||||||
content = content.replace(old_add_purchase_payment, new_add_purchase_payment)
|
|
||||||
else:
|
|
||||||
print("Could not find old_add_purchase_payment")
|
|
||||||
|
|
||||||
with open(file_path, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
file_path = 'core/views.py'
|
|
||||||
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
old_block = """ products = Product.objects.all().filter(stock_quantity__gt=0, is_active=True)
|
|
||||||
customers = Customer.objects.all()
|
|
||||||
categories = Category.objects.all()
|
|
||||||
payment_methods = PaymentMethod.objects.filter(is_active=True)
|
|
||||||
settings = SystemSetting.objects.first()"""
|
|
||||||
|
|
||||||
new_block = """ settings = SystemSetting.objects.first()
|
|
||||||
products = Product.objects.filter(is_active=True)
|
|
||||||
if not settings or 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)"""
|
|
||||||
|
|
||||||
if old_block in content:
|
|
||||||
new_content = content.replace(old_block, new_block)
|
|
||||||
with open(file_path, 'w') as f:
|
|
||||||
f.write(new_content)
|
|
||||||
print("Successfully patched core/views.py")
|
|
||||||
else:
|
|
||||||
print("Could not find the target block in core/views.py")
|
|
||||||
# Debugging: print a small chunk to see what's wrong with matching
|
|
||||||
start_index = content.find("def pos(request):")
|
|
||||||
if start_index != -1:
|
|
||||||
print("Context around pos view:")
|
|
||||||
print(content[start_index:start_index+500])
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
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}
|
|
||||||
@ -1,159 +0,0 @@
|
|||||||
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)
|
|
||||||
@ -1,260 +0,0 @@
|
|||||||
file_path = 'core/templates/core/settings.html'
|
|
||||||
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# 1. Add Nav Tab
|
|
||||||
if 'id="devices-tab"' not in content:
|
|
||||||
whatsapp_tab_end = 'id="whatsapp-tab" data-bs-toggle="pill" data-bs-target="#whatsapp" type="button" role="tab">\n <i class="bi bi-whatsapp me-2"></i>{% trans "WhatsApp Gateway" %}\n </button>\n li>'
|
|
||||||
|
|
||||||
insert_str = """
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button class="nav-link fw-bold px-4" id="devices-tab" data-bs-toggle="pill" data-bs-target="#devices" type="button" role="tab">
|
|
||||||
<i class="bi bi-hdd-network me-2"></i>{% trans "Devices" %}
|
|
||||||
</button>
|
|
||||||
</li>"
|
|
||||||
|
|
||||||
if whatsapp_tab_end in content:
|
|
||||||
content = content.replace(whatsapp_tab_end, whatsapp_tab_end + insert_str)
|
|
||||||
print("Added Devices Tab Nav.")
|
|
||||||
else:
|
|
||||||
# Fallback search if exact string match fails due to whitespace
|
|
||||||
print("Could not find exact match for Nav Tab insertion. Trying simpler match.")
|
|
||||||
simple_search = '{% trans "WhatsApp Gateway" %}'
|
|
||||||
parts = content.split(simple_search)
|
|
||||||
if len(parts) > 1:
|
|
||||||
# Reconstruct slightly differently but risky
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 2. Add Tab Content
|
|
||||||
if 'id="devices" role="tabpanel"' not in content:
|
|
||||||
devices_pane = """
|
|
||||||
<!-- Devices Tab -->
|
|
||||||
<div class="tab-pane fade" id="devices" role="tabpanel">
|
|
||||||
<div class="card shadow-sm border-0 glassmorphism mb-4">
|
|
||||||
<div class="card-header bg-transparent border-0 py-3 d-flex justify-content-between align-items-center">
|
|
||||||
<h5 class="card-title mb-0 fw-bold">{% trans "Connected Devices" %}</h5>
|
|
||||||
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addDeviceModal">
|
|
||||||
<i class="bi bi-plus-lg me-1"></i> {% trans "Add Device" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-0">
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-hover align-middle mb-0">
|
|
||||||
<thead class="bg-light">
|
|
||||||
<tr>
|
|
||||||
<th class="ps-4">{% trans "Device Name" %}</th>
|
|
||||||
<th>{% trans "Type" %}</th>
|
|
||||||
<th>{% trans "Connection" %}</th>
|
|
||||||
<th>{% trans "IP / Port" %}</th>
|
|
||||||
<th>{% trans "Status" %}</th>
|
|
||||||
<th class="text-end pe-4">{% trans "Actions" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for device in devices %}
|
|
||||||
<tr>
|
|
||||||
<td class="ps-4 fw-bold">{{ device.name }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge bg-secondary-soft text-secondary">{{ device.get_device_type_display }}</span>
|
|
||||||
</td>
|
|
||||||
<td>{{ device.get_connection_type_display }}</td>
|
|
||||||
<td>
|
|
||||||
{% if device.ip_address %}
|
|
||||||
{{ device.ip_address }}{% if device.port %}:{{ device.port }}{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<span class="text-muted">-</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if device.is_active %}
|
|
||||||
<span class="badge bg-success-soft text-success">{% trans "Active" %}</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="badge bg-danger-soft text-danger">{% trans "Inactive" %}</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="text-end pe-4">
|
|
||||||
<button class="btn btn-sm btn-light text-primary" data-bs-toggle="modal" data-bs-target="#editDeviceModal{{ device.id }}">
|
|
||||||
<i class="bi bi-pencil"></i>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-sm btn-light text-danger" data-bs-toggle="modal" data-bs-target="#deleteDeviceModal{{ device.id }}">
|
|
||||||
<i class="bi bi-trash"></i>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Edit Device Modal -->
|
|
||||||
<div class="modal fade" id="editDeviceModal{{ device.id }}" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content border-0">
|
|
||||||
<form action="{% url 'edit_device' device.id %}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title fw-bold">{% trans "Edit Device" %}</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-12">
|
|
||||||
<label class="form-label fw-semibold">{% trans "Device Name" %}</label>
|
|
||||||
<input type="text" name="name" class="form-control" value="{{ device.name }}" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label fw-semibold">{% trans "Device Type" %}</label>
|
|
||||||
<select name="device_type" class="form-select">
|
|
||||||
<option value="printer" {% if device.device_type == 'printer' %}selected{% endif %}>{% trans "Printer" %}</option>
|
|
||||||
<option value="scanner" {% if device.device_type == 'scanner' %}selected{% endif %}>{% trans "Scanner" %}</option>
|
|
||||||
<option value="scale" {% if device.device_type == 'scale' %}selected{% endif %}>{% trans "Weight Scale" %}</option>
|
|
||||||
<option value="display" {% if device.device_type == 'display' %}selected{% endif %}>{% trans "Customer Display" %}</option>
|
|
||||||
<option value="other" {% if device.device_type == 'other' %}selected{% endif %}>{% trans "Other" %}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label fw-semibold">{% trans "Connection Type" %}</label>
|
|
||||||
<select name="connection_type" class="form-select">
|
|
||||||
<option value="network" {% if device.connection_type == 'network' %}selected{% endif %}>{% trans "Network (IP)" %}</option>
|
|
||||||
<option value="usb" {% if device.connection_type == 'usb' %}selected{% endif %}>{% trans "USB" %}</option>
|
|
||||||
<option value="bluetooth" {% if device.connection_type == 'bluetooth' %}selected{% endif %}>{% trans "Bluetooth" %}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-8">
|
|
||||||
<label class="form-label fw-semibold">{% trans "IP Address" %}</label>
|
|
||||||
<input type="text" name="ip_address" class="form-control" value="{{ device.ip_address|default:'' }}" placeholder="192.168.1.100">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<label class="form-label fw-semibold">{% trans "Port" %}</label>
|
|
||||||
<input type="number" name="port" class="form-control" value="{{ device.port|default:'' }}" placeholder="9100">
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" name="is_active" {% if device.is_active %}checked{% endif %}>
|
|
||||||
<label class="form-check-label">{% trans "Active" %}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer bg-light border-0">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
|
||||||
<button type="submit" class="btn btn-primary">{% trans "Save Changes" %}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Delete Device Modal -->
|
|
||||||
<div class="modal fade" id="deleteDeviceModal{{ device.id }}" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content border-0">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title fw-bold">{% trans "Delete Device" %}</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>{% trans "Are you sure you want to delete" %} <strong>{{ device.name }}</strong>?</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer bg-light border-0">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
|
||||||
<a href="{% url 'delete_device' device.id %}" class="btn btn-danger">{% trans "Delete" %}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% empty %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="text-center py-5">
|
|
||||||
<div class="text-muted">
|
|
||||||
<i class="bi bi-hdd-network fs-1 d-block mb-3"></i>
|
|
||||||
{% trans "No devices configured." %}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
"
|
|
||||||
|
|
||||||
parts = content.split('<!-- Add Tier Modal -->')
|
|
||||||
if len(parts) > 1:
|
|
||||||
last_div = parts[0].rfind('</div>')
|
|
||||||
second_last_div = parts[0].rfind('</div>', 0, last_div)
|
|
||||||
|
|
||||||
if second_last_div != -1:
|
|
||||||
new_part0 = parts[0][:second_last_div] + devices_pane + parts[0][second_last_div:]
|
|
||||||
content = new_part0 + '<!-- Add Tier Modal -->' + parts[1]
|
|
||||||
print("Added Devices Tab Pane.")
|
|
||||||
else:
|
|
||||||
print("Could not find insertion point for Devices Pane.")
|
|
||||||
|
|
||||||
# 3. Add Add Device Modal
|
|
||||||
if 'id="addDeviceModal"' not in content:
|
|
||||||
modal_content = """
|
|
||||||
<!-- Add Device Modal -->
|
|
||||||
<div class="modal fade" id="addDeviceModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content border-0 shadow">
|
|
||||||
<form action="{% url 'add_device' %}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title fw-bold">{% trans "Add New Device" %}</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-12">
|
|
||||||
<label class="form-label fw-semibold">{% trans "Device Name" %}</label>
|
|
||||||
<input type="text" name="name" class="form-control" required placeholder="e.g. Kitchen Printer">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label fw-semibold">{% trans "Device Type" %}</label>
|
|
||||||
<select name="device_type" class="form-select">
|
|
||||||
<option value="printer">{% trans "Printer" %}</option>
|
|
||||||
<option value="scanner">{% trans "Scanner" %}</option>
|
|
||||||
<option value="scale">{% trans "Weight Scale" %}</option>
|
|
||||||
<option value="display">{% trans "Customer Display" %}</option>
|
|
||||||
<option value="other">{% trans "Other" %}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<label class="form-label fw-semibold">{% trans "Connection Type" %}</label>
|
|
||||||
<select name="connection_type" class="form-select">
|
|
||||||
<option value="network" selected>{% trans "Network (IP)" %}</option>
|
|
||||||
<option value="usb">{% trans "USB" %}</option>
|
|
||||||
<option value="bluetooth">{% trans "Bluetooth" %}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-8">
|
|
||||||
<label class="form-label fw-semibold">{% trans "IP Address" %}</label>
|
|
||||||
<input type="text" name="ip_address" class="form-control" placeholder="192.168.1.100">
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<label class="form-label fw-semibold">{% trans "Port" %}</label>
|
|
||||||
<input type="number" name="port" class="form-control" placeholder="9100">
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="form-check form-switch">
|
|
||||||
<input class="form-check-input" type="checkbox" name="is_active" checked>
|
|
||||||
<label class="form-check-label">{% trans "Active" %}</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer bg-light border-0">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
|
||||||
<button type="submit" class="btn btn-primary">{% trans "Add Device" %}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
"
|
|
||||||
content = content.replace('{% endblock %}', modal_content + '\n{% endblock %}')
|
|
||||||
print("Added Add Device Modal.")
|
|
||||||
|
|
||||||
with open(file_path, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
file_path = 'core/views.py'
|
|
||||||
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# 1. Add Device to imports
|
|
||||||
if 'Device' not in content:
|
|
||||||
pattern = r'(from \.models import \(.*?)(\))'
|
|
||||||
replacement = r'\1, Device\2'
|
|
||||||
content = re.sub(pattern, replacement, content, flags=re.DOTALL)
|
|
||||||
print("Added Device to imports.")
|
|
||||||
|
|
||||||
# 2. Update settings_view
|
|
||||||
if 'devices = Device.objects.all()' not in content:
|
|
||||||
# Find the lines before context creation
|
|
||||||
search_str = 'loyalty_tiers = LoyaltyTier.objects.all().order_by("min_points")'
|
|
||||||
insert_str = '\n devices = Device.objects.all().order_by("name")'
|
|
||||||
content = content.replace(search_str, search_str + insert_str)
|
|
||||||
|
|
||||||
# Update context
|
|
||||||
context_search = '"loyalty_tiers": loyalty_tiers'
|
|
||||||
context_insert = ',\n "devices": devices'
|
|
||||||
content = content.replace(context_search, context_search + context_insert)
|
|
||||||
print("Updated settings_view.")
|
|
||||||
|
|
||||||
# 3. Add Device views
|
|
||||||
new_views = """
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def add_device(request):
|
|
||||||
if request.method == 'POST':
|
|
||||||
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')
|
|
||||||
is_active = request.POST.get('is_active') == 'on'
|
|
||||||
|
|
||||||
Device.objects.create(
|
|
||||||
name=name,
|
|
||||||
device_type=device_type,
|
|
||||||
connection_type=connection_type,
|
|
||||||
ip_address=ip_address if ip_address else None,
|
|
||||||
port=port if port else None,
|
|
||||||
is_active=is_active
|
|
||||||
)
|
|
||||||
messages.success(request, _("Device added successfully!"))
|
|
||||||
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')
|
|
||||||
device.is_active = request.POST.get('is_active') == 'on'
|
|
||||||
|
|
||||||
if not device.ip_address:
|
|
||||||
device.ip_address = None
|
|
||||||
if not device.port:
|
|
||||||
device.port = None
|
|
||||||
|
|
||||||
device.save()
|
|
||||||
messages.success(request, _("Device updated successfully!"))
|
|
||||||
return redirect(reverse('settings') + '#devices')
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def delete_device(request, pk):
|
|
||||||
device = get_object_or_404(Device, pk=pk)
|
|
||||||
device.delete()
|
|
||||||
messages.success(request, _("Device deleted successfully!"))
|
|
||||||
return redirect(reverse('settings') + '#devices')
|
|
||||||
"""
|
|
||||||
|
|
||||||
if 'def add_device(request):' not in content:
|
|
||||||
content += new_views
|
|
||||||
print("Added Device views.")
|
|
||||||
|
|
||||||
with open(file_path, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
|
|
||||||
@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')
|
|
||||||
@ -1,134 +0,0 @@
|
|||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
file_path = 'core/views.py'
|
|
||||||
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Implement add_payment_method_ajax
|
|
||||||
# It currently looks like:
|
|
||||||
# @login_required
|
|
||||||
# def add_payment_method_ajax(request):
|
|
||||||
# return JsonResponse({'success': True})
|
|
||||||
|
|
||||||
new_add_payment_method = """@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)})"""
|
|
||||||
|
|
||||||
content = re.sub(
|
|
||||||
r'@login_required\s+def add_payment_method_ajax\(request\):\s+return JsonResponse\({\s*["']success["']: True\s*}\)',
|
|
||||||
new_add_payment_method,
|
|
||||||
content
|
|
||||||
)
|
|
||||||
|
|
||||||
# Implement create_purchase_api
|
|
||||||
new_create_purchase = """@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)})"""
|
|
||||||
|
|
||||||
content = re.sub(
|
|
||||||
r'@csrf_exempt\s+@login_required\s+def create_purchase_api\(request\):\s+return JsonResponse\({\s*["']success["']: True\s*}\)',
|
|
||||||
new_create_purchase,
|
|
||||||
content
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Implement add_customer_ajax
|
|
||||||
new_add_customer = """@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)})"""
|
|
||||||
|
|
||||||
content = re.sub(
|
|
||||||
r'@login_required\s+def add_customer_ajax\(request\):\s+return JsonResponse\({\s*["']success["']: True\s*}\)',
|
|
||||||
new_add_customer,
|
|
||||||
content
|
|
||||||
)
|
|
||||||
|
|
||||||
# Implement add_supplier_ajax
|
|
||||||
new_add_supplier = """@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)})"""
|
|
||||||
|
|
||||||
content = re.sub(
|
|
||||||
r'@login_required\s+def add_supplier_ajax\(request\):\s+return JsonResponse\({\s*["']success["']: True\s*}\)',
|
|
||||||
new_add_supplier,
|
|
||||||
content
|
|
||||||
)
|
|
||||||
|
|
||||||
with open(file_path, 'w') as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
print("Patched core/views.py successfully.")
|
|
||||||
@ -1,206 +0,0 @@
|
|||||||
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)
|
|
||||||
220
restore_views.py
220
restore_views.py
@ -1,220 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
file_path = 'core/views.py'
|
|
||||||
|
|
||||||
# The missing code to append
|
|
||||||
missing_code = r"""
|
|
||||||
# Deduct stock
|
|
||||||
product.stock_quantity -= int(item['quantity'])
|
|
||||||
product.save()
|
|
||||||
|
|
||||||
return JsonResponse({'success': True, 'sale_id': sale.id})
|
|
||||||
except Exception as e:
|
|
||||||
return JsonResponse({'success': False, 'error': str(e)}, status=400)
|
|
||||||
return JsonResponse({'success': False, 'error': 'Invalid request'}, status=405)
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def search_customers_api(request):
|
|
||||||
query = request.GET.get('q', '')
|
|
||||||
customers = Customer.objects.filter(
|
|
||||||
Q(name__icontains=query) | Q(phone__icontains=query)
|
|
||||||
).values('id', 'name', 'phone')[:10]
|
|
||||||
return JsonResponse({'results': list(customers)})
|
|
||||||
|
|
||||||
@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
|
|
||||||
})
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def pos_sync_update(request):
|
|
||||||
# Placeholder for POS sync logic
|
|
||||||
return JsonResponse({'status': 'ok'})
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def pos_sync_state(request):
|
|
||||||
# Placeholder for POS sync state
|
|
||||||
return JsonResponse({'state': {}})
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def test_whatsapp_connection(request):
|
|
||||||
settings = SystemSetting.objects.first()
|
|
||||||
if not settings or not settings.wablas_enabled:
|
|
||||||
return JsonResponse({'success': False, 'message': 'WhatsApp not enabled'})
|
|
||||||
return JsonResponse({'success': True, 'message': 'Connection simulation successful'})
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def add_device(request):
|
|
||||||
if request.method == 'POST':
|
|
||||||
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')
|
|
||||||
is_active = request.POST.get('is_active') == 'on'
|
|
||||||
|
|
||||||
Device.objects.create(
|
|
||||||
name=name,
|
|
||||||
device_type=device_type,
|
|
||||||
connection_type=connection_type,
|
|
||||||
ip_address=ip_address if ip_address else None,
|
|
||||||
port=port if port else None,
|
|
||||||
is_active=is_active
|
|
||||||
)
|
|
||||||
messages.success(request, _("Device added successfully!"))
|
|
||||||
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')
|
|
||||||
device.is_active = request.POST.get('is_active') == 'on'
|
|
||||||
|
|
||||||
if not device.ip_address:
|
|
||||||
device.ip_address = None
|
|
||||||
if not device.port:
|
|
||||||
device.port = None
|
|
||||||
|
|
||||||
device.save()
|
|
||||||
messages.success(request, _("Device updated successfully!"))
|
|
||||||
return redirect(reverse('settings') + '#devices')
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def delete_device(request, pk):
|
|
||||||
device = get_object_or_404(Device, pk=pk)
|
|
||||||
device.delete()
|
|
||||||
messages.success(request, _("Device deleted successfully!"))
|
|
||||||
return redirect(reverse('settings') + '#devices')
|
|
||||||
|
|
||||||
# LPO Views (Placeholders/Basic Implementation)
|
|
||||||
@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):
|
|
||||||
lpo = get_object_or_404(PurchaseOrder, pk=pk)
|
|
||||||
# Conversion logic here (simplified)
|
|
||||||
# ...
|
|
||||||
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):
|
|
||||||
# API logic for LPO creation
|
|
||||||
return JsonResponse({'success': True, 'lpo_id': 1}) # Dummy
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def cashier_registry(request):
|
|
||||||
registries = CashierCounterRegistry.objects.all()
|
|
||||||
return render(request, 'core/cashier_registry.html', {'registries': registries})
|
|
||||||
|
|
||||||
# Session Views
|
|
||||||
@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':
|
|
||||||
opening_balance = request.POST.get('opening_balance', 0)
|
|
||||||
# Find assigned counter
|
|
||||||
registry = CashierCounterRegistry.objects.filter(cashier=request.user).first()
|
|
||||||
counter = registry.counter if registry else None
|
|
||||||
|
|
||||||
CashierSession.objects.create(
|
|
||||||
user=request.user,
|
|
||||||
counter=counter,
|
|
||||||
opening_balance=opening_balance,
|
|
||||||
status='active'
|
|
||||||
)
|
|
||||||
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:
|
|
||||||
closing_balance = request.POST.get('closing_balance', 0)
|
|
||||||
notes = request.POST.get('notes', '')
|
|
||||||
session.closing_balance = closing_balance
|
|
||||||
session.notes = notes
|
|
||||||
session.end_time = timezone.now()
|
|
||||||
session.status = 'closed'
|
|
||||||
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})
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def customer_display(request):
|
|
||||||
return render(request, 'core/customer_display.html')
|
|
||||||
"""
|
|
||||||
|
|
||||||
with open(file_path, 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Check if the file ends with the broken function
|
|
||||||
if content.strip().endswith("line_total=item['line_total']\n )"):
|
|
||||||
print("Found broken file end. Appending missing code.")
|
|
||||||
with open(file_path, 'a') as f:
|
|
||||||
f.write(missing_code)
|
|
||||||
print("Successfully restored core/views.py")
|
|
||||||
else:
|
|
||||||
print("File does not end as expected. Please check manually.")
|
|
||||||
# Force append if it looks like it's missing the new functions
|
|
||||||
if "def start_session" not in content:
|
|
||||||
print("Appending missing functions anyway...")
|
|
||||||
with open(file_path, 'a') as f:
|
|
||||||
f.write(missing_code)
|
|
||||||
@ -1 +0,0 @@
|
|||||||
Settings loaded
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
[2026-02-09T07:28:37.599243] Starting manage.py...
|
|
||||||
[2026-02-09T07:28:37.599461] Args: ['manage.py', 'runserver', '0.0.0.0:8000']
|
|
||||||
[2026-02-09T07:28:37.653416] Loaded .env
|
|
||||||
[2026-02-09T07:28:38.658262] Django setup complete.
|
|
||||||
[2026-02-09T07:28:38.658403] Executing command line...
|
|
||||||
[2026-02-09T07:28:46.088684] SystemExit caught: 3
|
|
||||||
[2026-02-09T07:28:46.362448] Starting manage.py...
|
|
||||||
[2026-02-09T07:28:46.362618] Args: ['manage.py', 'runserver', '0.0.0.0:8000']
|
|
||||||
[2026-02-09T07:28:46.390956] Loaded .env
|
|
||||||
[2026-02-09T07:28:46.717591] Django setup complete.
|
|
||||||
[2026-02-09T07:28:46.717749] Executing command line...
|
|
||||||
[2026-02-09T07:42:35.563432] SystemExit caught: 3
|
|
||||||
[2026-02-09T07:42:36.571344] Starting manage.py...
|
|
||||||
[2026-02-09T07:42:36.571557] Args: ['manage.py', 'runserver', '0.0.0.0:8000']
|
|
||||||
[2026-02-09T07:42:36.626917] Loaded .env
|
|
||||||
[2026-02-09T07:42:37.207600] Django setup complete.
|
|
||||||
[2026-02-09T07:42:37.207759] Executing command line...
|
|
||||||
[2026-02-09T07:42:38.535239] SystemExit caught: 3
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
import os
|
|
||||||
import django
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
|
||||||
django.setup()
|
|
||||||
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.test import RequestFactory
|
|
||||||
from core.models import Product, SystemSetting, Category, PaymentMethod
|
|
||||||
|
|
||||||
def test_pos_render():
|
|
||||||
factory = RequestFactory()
|
|
||||||
request = factory.get('/pos/')
|
|
||||||
|
|
||||||
s_settings = SystemSetting.objects.first()
|
|
||||||
products = Product.objects.filter(is_active=True)
|
|
||||||
categories = Category.objects.all()
|
|
||||||
payment_methods = PaymentMethod.objects.all()
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'products': products,
|
|
||||||
'customers': [],
|
|
||||||
'categories': categories,
|
|
||||||
'payment_methods': payment_methods,
|
|
||||||
'settings': s_settings,
|
|
||||||
'site_settings': s_settings,
|
|
||||||
'active_session': None,
|
|
||||||
'LANGUAGE_CODE': 'en'
|
|
||||||
}
|
|
||||||
|
|
||||||
rendered = render_to_string('core/pos.html', context, request=request)
|
|
||||||
|
|
||||||
print(f"Total Products Checked: {products.count()}")
|
|
||||||
# Check for image URLs
|
|
||||||
for product in products:
|
|
||||||
if product.image:
|
|
||||||
url = product.image.url
|
|
||||||
if url in rendered:
|
|
||||||
print(f"Product {product.name_en} image URL FOUND: {url}")
|
|
||||||
else:
|
|
||||||
# Check for escaped URL
|
|
||||||
from django.utils.html import escape
|
|
||||||
if escape(url) in rendered:
|
|
||||||
print(f"Product {product.name_en} image URL FOUND (escaped): {escape(url)}")
|
|
||||||
else:
|
|
||||||
print(f"Product {product.name_en} image URL MISSING: {url}")
|
|
||||||
else:
|
|
||||||
print(f"Product {product.name_en} has no image in DB")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_pos_render()
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
import os
|
|
||||||
import django
|
|
||||||
from django.conf import settings
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.test import RequestFactory
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
|
||||||
django.setup()
|
|
||||||
|
|
||||||
from core.models import SystemSetting, Product, Customer, Sale, Category, PaymentMethod
|
|
||||||
from django.utils import timezone
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
def test_render():
|
|
||||||
factory = RequestFactory()
|
|
||||||
request = factory.get('/')
|
|
||||||
# Simulate a user
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
user = User.objects.first()
|
|
||||||
request.user = user
|
|
||||||
|
|
||||||
settings_obj = SystemSetting.objects.first()
|
|
||||||
context = {
|
|
||||||
'site_settings': settings_obj,
|
|
||||||
'settings': settings_obj,
|
|
||||||
'total_sales_amount': 0,
|
|
||||||
'total_receivables': 0,
|
|
||||||
'total_payables': 0,
|
|
||||||
'total_sales_count': 0,
|
|
||||||
'total_products': 0,
|
|
||||||
'total_customers': 0,
|
|
||||||
'monthly_labels': [],
|
|
||||||
'monthly_data': [],
|
|
||||||
'chart_labels': [],
|
|
||||||
'chart_data': [],
|
|
||||||
'category_labels': [],
|
|
||||||
'category_data': [],
|
|
||||||
'payment_labels': [],
|
|
||||||
'payment_data': [],
|
|
||||||
'top_products': [],
|
|
||||||
'low_stock_count': 0,
|
|
||||||
'low_stock_products': [],
|
|
||||||
'expired_count': 0,
|
|
||||||
'recent_sales': [],
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
html = render_to_string('core/index.html', context, request=request)
|
|
||||||
print(f"Render successful, length: {len(html)}")
|
|
||||||
if len(html) < 100:
|
|
||||||
print("HTML is too short!")
|
|
||||||
print(html)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Render failed: {e}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_render()
|
|
||||||
@ -1 +0,0 @@
|
|||||||
modified content
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
WSGI Crash: No module named 'whitenoise'
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "/home/ubuntu/executor/workspace/config/wsgi.py", line 19, in <module>
|
|
||||||
application = get_wsgi_application()
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "/home/ubuntu/.local/lib/python3.11/site-packages/django/core/wsgi.py", line 13, in get_wsgi_application
|
|
||||||
return WSGIHandler()
|
|
||||||
^^^^^^^^^^^^^
|
|
||||||
File "/home/ubuntu/.local/lib/python3.11/site-packages/django/core/handlers/wsgi.py", line 118, in __init__
|
|
||||||
self.load_middleware()
|
|
||||||
File "/home/ubuntu/.local/lib/python3.11/site-packages/django/core/handlers/base.py", line 40, in load_middleware
|
|
||||||
middleware = import_string(middleware_path)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "/home/ubuntu/.local/lib/python3.11/site-packages/django/utils/module_loading.py", line 30, in import_string
|
|
||||||
return cached_import(module_path, class_name)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "/home/ubuntu/.local/lib/python3.11/site-packages/django/utils/module_loading.py", line 15, in cached_import
|
|
||||||
module = import_module(module_path)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "/usr/lib/python3.11/importlib/__init__.py", line 126, in import_module
|
|
||||||
return _bootstrap._gcd_import(name[level:], package, level)
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
File "<frozen importlib._bootstrap>", line 1206, in _gcd_import
|
|
||||||
File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
|
|
||||||
File "<frozen importlib._bootstrap>", line 1128, in _find_and_load_unlocked
|
|
||||||
File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
|
|
||||||
File "<frozen importlib._bootstrap>", line 1206, in _gcd_import
|
|
||||||
File "<frozen importlib._bootstrap>", line 1178, in _find_and_load
|
|
||||||
File "<frozen importlib._bootstrap>", line 1142, in _find_and_load_unlocked
|
|
||||||
ModuleNotFoundError: No module named 'whitenoise'
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
[2026-02-09T07:28:46.937704] WSGI module loading...
|
|
||||||
[2026-02-09T07:28:46.948377] WSGI loaded .env
|
|
||||||
[2026-02-09T07:28:46.952251] WSGI application created successfully.
|
|
||||||
[2026-02-09T07:42:37.533157] WSGI module loading...
|
|
||||||
[2026-02-09T07:42:37.541628] WSGI loaded .env
|
|
||||||
[2026-02-09T07:42:37.544967] WSGI application created successfully.
|
|
||||||
[2026-02-09T07:42:39.272623] WSGI module loading...
|
|
||||||
[2026-02-09T07:42:39.280940] WSGI loaded .env
|
|
||||||
[2026-02-09T07:42:39.283449] WSGI application created successfully.
|
|
||||||
@ -1 +0,0 @@
|
|||||||
WSGI loaded
|
|
||||||
Loading…
x
Reference in New Issue
Block a user