Ver 11.01 better permisions

This commit is contained in:
Flatlogic Bot 2026-02-04 20:41:13 +00:00
parent 9c365aa4e1
commit 0fbf30ddc5
5 changed files with 153 additions and 18 deletions

View File

@ -33,17 +33,44 @@
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto align-items-center">
{% if user.is_authenticated %}
{% if user.is_staff or user.is_superuser or user.managed_teams.exists or user.assigned_projects.exists %}
{# Dashboard #}
{% if user.is_staff or user.is_superuser or perms.core.view_project or user.managed_teams.exists or user.assigned_projects.exists %}
<li class="nav-item"><a class="nav-link" href="{% url 'home' %}">Dashboard</a></li>
{% endif %}
<li class="nav-item"><a class="nav-link" href="{% url 'log_attendance' %}">Log Work</a></li>
{# Log Work #}
{% if user.is_staff or user.is_superuser or perms.core.add_worklog %}
<li class="nav-item"><a class="nav-link" href="{% url 'log_attendance' %}">Log Work</a></li>
{% else %}
{# Fallback for existing users if strict mode is not desired, but user requested hiding. #}
{# I will leave it visible ONLY if they are supervisors to maintain status quo for them if they lack permissions #}
{% if user.managed_teams.exists or user.assigned_projects.exists %}
<li class="nav-item"><a class="nav-link" href="{% url 'log_attendance' %}">Log Work</a></li>
{% endif %}
{% endif %}
{% if user.is_staff or user.is_superuser or user.managed_teams.exists or user.assigned_projects.exists %}
{# History #}
{% if user.is_staff or user.is_superuser or perms.core.view_worklog or user.managed_teams.exists or user.assigned_projects.exists %}
<li class="nav-item"><a class="nav-link" href="{% url 'work_log_list' %}">History</a></li>
{% endif %}
{# Payroll #}
{% if user.is_staff or user.is_superuser or perms.core.view_payrollrecord %}
<li class="nav-item"><a class="nav-link" href="{% url 'payroll_dashboard' %}">Payroll</a></li>
{% endif %}
{# Loans #}
{% if user.is_staff or user.is_superuser or perms.core.view_loan %}
<li class="nav-item"><a class="nav-link" href="{% url 'loan_list' %}">Loans</a></li>
{% endif %}
{# Receipts #}
{% if user.is_staff or user.is_superuser or perms.core.add_expensereceipt %}
<li class="nav-item"><a class="nav-link" href="{% url 'create_receipt' %}">Receipts</a></li>
{% endif %}
{# Manage #}
{% if user.is_staff or user.is_superuser or perms.core.change_worker %}
<li class="nav-item"><a class="nav-link" href="{% url 'manage_resources' %}">Manage</a></li>
{% endif %}

View File

@ -86,6 +86,17 @@
{% endif %}
</div>
<!-- Total Cost Estimation -->
<div class="card p-3 mb-4 bg-light border-0 shadow-sm">
<div class="d-flex justify-content-between align-items-center">
<div>
<h5 class="mb-0 text-muted">Estimated Cost</h5>
<small class="text-muted" id="calculationDetails">0 workers × 0 days</small>
</div>
<h3 class="mb-0 fw-bold text-dark font-monospace" id="estimatedTotal">R 0.00</h3>
</div>
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<a href="{% url 'home' %}" class="btn btn-light px-4">Cancel</a>
<button type="submit" class="btn btn-primary px-5">Save Work Log</button>
@ -153,13 +164,7 @@
const teamId = this.value;
if (teamId && teamWorkersMap[teamId]) {
const workerIds = teamWorkersMap[teamId];
// Uncheck all first? No, maybe append. Let's append as per common expectations unless explicit clear needed.
// Actually, if I change team, I probably expect to select THAT team's workers.
// Let's clear and select.
// But maybe I want to mix teams.
// User didn't specify. Previous logic was: select workers belonging to team.
// Let's stick to "select", don't uncheck others.
// Select workers belonging to the team
workerIds.forEach(function(id) {
const checkbox = document.querySelector(`input[name="workers"][value="${id}"]`);
if (checkbox) {
@ -176,6 +181,86 @@
var myModal = new bootstrap.Modal(conflictModalEl);
myModal.show();
}
// --- Cost Calculation Logic ---
const workerRates = {{ worker_rates_json|safe }};
const startDateInput = document.getElementById('{{ form.date.id_for_label }}');
const endDateInput = document.getElementById('{{ form.end_date.id_for_label }}');
const satCheckbox = document.getElementById('{{ form.include_saturday.id_for_label }}');
const sunCheckbox = document.getElementById('{{ form.include_sunday.id_for_label }}');
const workerCheckboxes = document.querySelectorAll('input[name="workers"]');
const totalDisplay = document.getElementById('estimatedTotal');
const detailsDisplay = document.getElementById('calculationDetails');
function calculateTotal() {
// 1. Calculate Days
let days = 0;
const start = startDateInput.value ? new Date(startDateInput.value) : null;
const end = endDateInput.value ? new Date(endDateInput.value) : null;
if (start) {
if (!end || end < start) {
days = 1;
} else {
// Iterate dates
let curr = new Date(start);
// Reset time components to avoid TZ issues
curr.setHours(0,0,0,0);
const last = new Date(end);
last.setHours(0,0,0,0);
while (curr <= last) {
const dayOfWeek = curr.getDay(); // 0 = Sun, 6 = Sat
let isWorkingDay = true;
if (dayOfWeek === 6 && !satCheckbox.checked) isWorkingDay = false;
if (dayOfWeek === 0 && !sunCheckbox.checked) isWorkingDay = false;
if (isWorkingDay) days++;
curr.setDate(curr.getDate() + 1);
}
}
}
// 2. Sum Worker Rates
let dailyRateSum = 0;
let workerCount = 0;
workerCheckboxes.forEach(cb => {
if (cb.checked) {
const rate = workerRates[cb.value] || 0;
dailyRateSum += rate;
workerCount++;
}
});
// 3. Update UI
const total = dailyRateSum * days;
totalDisplay.textContent = 'R ' + total.toLocaleString('en-ZA', {minimumFractionDigits: 2, maximumFractionDigits: 2});
detailsDisplay.textContent = `${workerCount} worker${workerCount !== 1 ? 's' : ''} × ${days} day${days !== 1 ? 's' : ''}`;
}
// Attach Listeners
if (startDateInput) startDateInput.addEventListener('change', calculateTotal);
if (endDateInput) endDateInput.addEventListener('change', calculateTotal);
if (satCheckbox) satCheckbox.addEventListener('change', calculateTotal);
if (sunCheckbox) sunCheckbox.addEventListener('change', calculateTotal);
workerCheckboxes.forEach(cb => {
cb.addEventListener('change', calculateTotal);
});
// Also update when team changes (since it selects workers programmatically)
if (teamSelect) {
teamSelect.addEventListener('change', function() {
// Give it a moment for the check logic to finish
setTimeout(calculateTotal, 100);
});
}
// Initial Run
calculateTotal();
});
function submitConflict(action) {

View File

@ -80,13 +80,24 @@
<button type="submit" class="btn btn-primary w-100">Filter</button>
</div>
</form>
{% if selected_worker or selected_team or selected_project or selected_payment_status and selected_payment_status != 'all' %}
<div class="mt-3">
<a href="{% url 'work_log_list' %}?view={{ view_mode }}" class="btn btn-sm btn-link text-decoration-none text-muted">
<i class="bi bi-x-circle"></i> Clear all filters
</a>
<hr class="my-4 opacity-10">
<div class="d-flex justify-content-between align-items-end">
<div>
{% if selected_worker or selected_team or selected_project or selected_payment_status and selected_payment_status != 'all' %}
<a href="{% url 'work_log_list' %}?view={{ view_mode }}" class="btn btn-sm btn-link text-decoration-none text-muted p-0">
<i class="bi bi-x-circle"></i> Clear all filters
</a>
{% else %}
<small class="text-muted">Showing all records</small>
{% endif %}
</div>
<div class="text-end">
<small class="text-uppercase text-muted fw-bold ls-1 d-block mb-1">Total Value</small>
<span class="h2 fw-bold text-primary mb-0">R {{ total_amount|floatformat:2 }}</span>
</div>
</div>
{% endif %}
</div>
</div>
</div>

View File

@ -24,6 +24,8 @@ def is_staff_or_supervisor(user):
"""Check if user is staff or manages at least one team/project."""
if user.is_staff or user.is_superuser:
return True
if user.has_perm('core.view_project'):
return True
return user.managed_teams.exists() or user.assigned_projects.exists()
@login_required
@ -141,11 +143,16 @@ def log_attendance(request):
conflicts.append(conflict_entry)
if conflicts:
# Prepare worker rates for JS calculation
worker_qs = form.fields['workers'].queryset
worker_rates = {w.id: float(w.day_rate) for w in worker_qs}
context = {
'form': form,
'team_workers_json': json.dumps(team_workers_map),
'conflicting_workers': conflicts,
'is_conflict': True,
'worker_rates_json': json.dumps(worker_rates),
}
return render(request, 'core/log_attendance.html', context)
@ -203,9 +210,14 @@ def log_attendance(request):
else:
form = WorkLogForm(user=request.user if request.user.is_authenticated else None)
# Pass worker rates for frontend total calculation
worker_qs = form.fields['workers'].queryset
worker_rates = {w.id: float(w.day_rate) for w in worker_qs}
context = {
'form': form,
'team_workers_json': json.dumps(team_workers_map)
'team_workers_json': json.dumps(team_workers_map),
'worker_rates_json': json.dumps(worker_rates)
}
return render(request, 'core/log_attendance.html', context)
@ -831,4 +843,4 @@ def create_receipt(request):
return render(request, 'core/create_receipt.html', {
'form': form,
'items': items
})
})