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:
|
||||
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
|
||||
|
||||
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 ---
|
||||
|
||||
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()
|
||||
|
||||
@ -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()
|
||||
17
manage.py
17
manage.py
@ -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()
|
||||
@ -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