session summery

This commit is contained in:
Flatlogic Bot 2026-02-12 16:19:20 +00:00
parent c8c0620ceb
commit 8f6e105aac
6 changed files with 144 additions and 49 deletions

View File

@ -19,12 +19,18 @@ def project_context(request):
def global_settings(request):
settings = None
try:
# Use a quick query to avoid hangs if DB is locked
settings = SystemSetting.objects.first()
if not settings:
settings = SystemSetting.objects.create()
except Exception:
# If DB is broken (OperationalError, etc.), just return None.
# Do not try to fix it here to avoid infinite loops or crashes during template rendering.
# Only attempt creation if we are absolutely sure it's missing
# and wrap it in a try-except to avoid crashes on read-only DBs
try:
settings = SystemSetting.objects.create(business_name="Meezan Accounting")
except Exception as e:
logger.error(f"Failed to create SystemSetting: {e}")
settings = None
except Exception as e:
logger.warning(f"Database error in global_settings: {e}")
pass
return {

View File

@ -27,6 +27,8 @@
{% endif %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css">
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
@ -494,7 +496,9 @@
</div>
</div>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
{% if user.is_authenticated %}
const sidebar = document.getElementById('sidebar');
@ -541,32 +545,6 @@
});
}
});
// Cleanup stale modal backdrops and body classes aggressively
(function() {
function cleanupModals() {
const backdrops = document.querySelectorAll('.modal-backdrop');
if (backdrops.length > 0) {
console.log('Cleaning up ' + backdrops.length + ' stale backdrops');
backdrops.forEach(el => el.remove());
}
document.body.classList.remove('modal-open');
document.body.style.overflow = '';
document.body.style.paddingRight = '';
}
// Run multiple times to ensure cleanup
cleanupModals();
window.addEventListener('DOMContentLoaded', cleanupModals);
window.addEventListener('load', cleanupModals);
// Periodically check for 2 seconds
let attempts = 0;
const interval = setInterval(() => {
cleanupModals();
if (++attempts > 10) clearInterval(interval);
}, 500);
})();
</script>
{% block scripts %}{% endblock %}
</body>

View File

@ -4,8 +4,8 @@
{% block title %}{% trans "Login" %} - {{ site_settings.business_name }}{% endblock %}
{% block content %}
<div class="container h-100 d-flex align-items-center justify-content-center">
<div class="card glass-card border-0 shadow-lg p-4" style="max-width: 400px; width: 100%;">
<div class="login-wrapper">
<div class="card glass-card border-0 shadow-lg p-4 login-card">
<div class="text-center mb-4">
<h2 class="fw-bold text-primary">{% trans "Welcome Back" %}</h2>
<p class="text-muted small">{% trans "Please login to access your dashboard" %}</p>
@ -47,10 +47,24 @@
</div>
<style>
/* Center the login box specifically since we're using the base template with sidebar */
#sidebar { display: none !important; }
#content { margin-left: 0 !important; width: 100%; height: 100vh; background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); }
.top-navbar { display: none !important; }
main { padding: 0 !important; height: 100%; display: flex; align-items: center; justify-content: center; }
/* Reset and centering for login page */
#sidebar, .top-navbar { display: none !important; }
#content { margin-left: 0 !important; width: 100%; min-height: 100vh; background-color: #f0f2f5; }
main { padding: 0 !important; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.login-wrapper {
width: 100%;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-card {
max-width: 400px;
width: 100%;
background-color: white !important;
border-radius: 20px;
}
</style>
{% endblock %}

View File

@ -1688,7 +1688,24 @@ def create_lpo_api(request):
# --- Cashier / Sessions ---
@login_required
def cashier_registry(request):
registries = CashierCounterRegistry.objects.select_related('user', 'counter').all()
if request.method == 'POST':
action = request.POST.get('action')
if action == 'assign':
cashier_id = request.POST.get('cashier_id')
counter_id = request.POST.get('counter_id')
if cashier_id and counter_id:
# One cashier per counter (or update existing assignment for this cashier)
CashierCounterRegistry.objects.update_or_create(
cashier_id=cashier_id,
defaults={'counter_id': counter_id}
)
elif action == 'delete':
registry_id = request.POST.get('registry_id')
if registry_id:
CashierCounterRegistry.objects.filter(id=registry_id).delete()
return redirect('cashier_registry')
registries = CashierCounterRegistry.objects.select_related('cashier', 'counter').all()
# Provide data for dropdowns
users = User.objects.filter(is_active=True).order_by('username')
counters = Device.objects.all().order_by('name')
@ -1706,26 +1723,106 @@ def cashier_session_list(request):
@login_required
def start_session(request):
if request.method == 'POST':
CashierSession.objects.create(user=request.user, opening_balance=request.POST.get('opening_balance', 0))
# Check if user already has an active session
active = CashierSession.objects.filter(user=request.user, status='active').first()
if active:
return redirect('pos')
return render(request, 'core/start_session.html')
# Get assigned counter
registry = CashierCounterRegistry.objects.filter(cashier=request.user).first()
counter = registry.counter if registry else None
if request.method == 'POST':
form = CashierSessionStartForm(request.POST)
if form.is_valid():
session = form.save(commit=False)
session.user = request.user
session.counter = counter
session.status = 'active'
session.save()
return redirect('pos')
else:
form = CashierSessionStartForm()
return render(request, 'core/start_session.html', {
'form': form,
'counter': counter
})
@login_required
def close_session(request):
session = CashierSession.objects.filter(user=request.user, status='active').first()
if request.method == 'POST' and session:
session.closing_balance = request.POST.get('closing_balance', 0)
session.status = 'closed'
session.end_time = timezone.now()
session.save()
return redirect('index')
return render(request, 'core/close_session.html', {'session': session})
if not session:
return redirect('pos')
if request.method == 'POST':
form = CashierSessionCloseForm(request.POST, instance=session)
if form.is_valid():
session = form.save(commit=False)
session.status = 'closed'
session.end_time = timezone.now()
session.save()
return redirect('session_detail', pk=session.pk)
else:
form = CashierSessionCloseForm(instance=session)
# Statistics for the session
end_time = timezone.now()
payments = SalePayment.objects.filter(
created_by=session.user,
created_at__gte=session.start_time,
created_at__lte=end_time
)
payment_summary = payments.values('payment_method_name').annotate(total=Sum('amount')).order_by('-total')
total_sales = payments.aggregate(total=Sum('amount'))['total'] or 0
formatted_payments = []
for p in payment_summary:
formatted_payments.append({
'payment_method_name': p['payment_method_name'] or _("Unknown"),
'total': p['total']
})
return render(request, 'core/close_session.html', {
'session': session,
'form': form,
'payments': formatted_payments,
'total_sales': total_sales
})
@login_required
def session_detail(request, pk):
session = get_object_or_404(CashierSession, pk=pk)
return render(request, 'core/session_detail.html', {'session': session})
# Calculate sales during this session
# We use start_time and end_time (or now if active)
end_time = session.end_time or timezone.now()
payments = SalePayment.objects.filter(
created_by=session.user,
created_at__gte=session.start_time,
created_at__lte=end_time
)
# Group by payment method
payment_summary = payments.values('payment_method_name').annotate(total=Sum('amount')).order_by('-total')
total_sales = payments.aggregate(total=Sum('amount'))['total'] or 0
# Convert to expected template structure
formatted_payments = []
for p in payment_summary:
formatted_payments.append({
'payment_method_name': p['payment_method_name'] or _("Unknown"),
'total': p['total']
})
context = {
'session': session,
'payments': formatted_payments,
'total_sales': total_sales,
}
return render(request, 'core/session_detail.html', context)
# --- APIs ---