Autosave: 20260213-053553

This commit is contained in:
Flatlogic Bot 2026-02-13 05:35:53 +00:00
parent 18f1df85e5
commit 51822816f6
46 changed files with 60 additions and 2441 deletions

View File

@ -1,7 +0,0 @@
libglib2.0-0
libgobject-2.0-0
libpango-1.0-0
libpangocairo-1.0-0
libcairo2
libharfbuzz0b
libfontconfig1

View File

@ -1 +0,0 @@
web: gunicorn config.wsgi --log-file -

View File

@ -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")

View File

@ -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")

View File

@ -20,21 +20,6 @@ try:
except ImportError:
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')
from django.core.wsgi import get_wsgi_application

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
View 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

View File

@ -44,7 +44,8 @@ logger = logging.getLogger(__name__)
# --- Basic Views ---
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>"
pdf = HTML(string=html_string).write_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):
from weasyprint import HTML
from .pdf_utils import get_weasyprint_html
HTML = get_weasyprint_html()
html_string = render_to_string(template, context, request=request)
base_url = request.build_absolute_uri('/')
return HTML(string=html_string, base_url=base_url).write_pdf()

View File

@ -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!")

View File

@ -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()

View File

@ -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.")

View File

@ -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}")

View File

@ -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()

View File

@ -14,21 +14,6 @@ def main():
except ImportError:
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')
try:
from django.core.management import execute_from_command_line
@ -41,4 +26,4 @@ def main():
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
main()

View File

@ -1 +0,0 @@
Manage.py started

View File

@ -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()

View File

@ -1 +0,0 @@
# This script has been disabled/removed as the project is deployed to the root.

View File

@ -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.")

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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)

View File

@ -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])

View File

@ -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}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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')

View File

@ -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.")

View File

@ -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)

View File

@ -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)

View File

@ -1 +0,0 @@
Settings loaded

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -1 +0,0 @@
modified content

View File

@ -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'

View File

@ -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.

View File

@ -1 +0,0 @@
WSGI loaded