Ver 11.01 better permisions
This commit is contained in:
parent
9c365aa4e1
commit
0fbf30ddc5
Binary file not shown.
@ -33,17 +33,44 @@
|
|||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
<ul class="navbar-nav ms-auto align-items-center">
|
<ul class="navbar-nav ms-auto align-items-center">
|
||||||
{% if user.is_authenticated %}
|
{% 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>
|
<li class="nav-item"><a class="nav-link" href="{% url 'home' %}">Dashboard</a></li>
|
||||||
{% endif %}
|
{% 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>
|
<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>
|
<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>
|
<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>
|
<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>
|
<li class="nav-item"><a class="nav-link" href="{% url 'manage_resources' %}">Manage</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
@ -86,6 +86,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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">
|
<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>
|
<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>
|
<button type="submit" class="btn btn-primary px-5">Save Work Log</button>
|
||||||
@ -153,13 +164,7 @@
|
|||||||
const teamId = this.value;
|
const teamId = this.value;
|
||||||
if (teamId && teamWorkersMap[teamId]) {
|
if (teamId && teamWorkersMap[teamId]) {
|
||||||
const workerIds = teamWorkersMap[teamId];
|
const workerIds = teamWorkersMap[teamId];
|
||||||
// Uncheck all first? No, maybe append. Let's append as per common expectations unless explicit clear needed.
|
// Select workers belonging to the team
|
||||||
// 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.
|
|
||||||
|
|
||||||
workerIds.forEach(function(id) {
|
workerIds.forEach(function(id) {
|
||||||
const checkbox = document.querySelector(`input[name="workers"][value="${id}"]`);
|
const checkbox = document.querySelector(`input[name="workers"][value="${id}"]`);
|
||||||
if (checkbox) {
|
if (checkbox) {
|
||||||
@ -176,6 +181,86 @@
|
|||||||
var myModal = new bootstrap.Modal(conflictModalEl);
|
var myModal = new bootstrap.Modal(conflictModalEl);
|
||||||
myModal.show();
|
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) {
|
function submitConflict(action) {
|
||||||
|
|||||||
@ -80,13 +80,24 @@
|
|||||||
<button type="submit" class="btn btn-primary w-100">Filter</button>
|
<button type="submit" class="btn btn-primary w-100">Filter</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% if selected_worker or selected_team or selected_project or selected_payment_status and selected_payment_status != 'all' %}
|
|
||||||
<div class="mt-3">
|
<hr class="my-4 opacity-10">
|
||||||
<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
|
<div class="d-flex justify-content-between align-items-end">
|
||||||
</a>
|
<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>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -24,6 +24,8 @@ def is_staff_or_supervisor(user):
|
|||||||
"""Check if user is staff or manages at least one team/project."""
|
"""Check if user is staff or manages at least one team/project."""
|
||||||
if user.is_staff or user.is_superuser:
|
if user.is_staff or user.is_superuser:
|
||||||
return True
|
return True
|
||||||
|
if user.has_perm('core.view_project'):
|
||||||
|
return True
|
||||||
return user.managed_teams.exists() or user.assigned_projects.exists()
|
return user.managed_teams.exists() or user.assigned_projects.exists()
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -141,11 +143,16 @@ def log_attendance(request):
|
|||||||
conflicts.append(conflict_entry)
|
conflicts.append(conflict_entry)
|
||||||
|
|
||||||
if conflicts:
|
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 = {
|
context = {
|
||||||
'form': form,
|
'form': form,
|
||||||
'team_workers_json': json.dumps(team_workers_map),
|
'team_workers_json': json.dumps(team_workers_map),
|
||||||
'conflicting_workers': conflicts,
|
'conflicting_workers': conflicts,
|
||||||
'is_conflict': True,
|
'is_conflict': True,
|
||||||
|
'worker_rates_json': json.dumps(worker_rates),
|
||||||
}
|
}
|
||||||
return render(request, 'core/log_attendance.html', context)
|
return render(request, 'core/log_attendance.html', context)
|
||||||
|
|
||||||
@ -203,9 +210,14 @@ def log_attendance(request):
|
|||||||
else:
|
else:
|
||||||
form = WorkLogForm(user=request.user if request.user.is_authenticated else None)
|
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 = {
|
context = {
|
||||||
'form': form,
|
'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)
|
return render(request, 'core/log_attendance.html', context)
|
||||||
@ -831,4 +843,4 @@ def create_receipt(request):
|
|||||||
return render(request, 'core/create_receipt.html', {
|
return render(request, 'core/create_receipt.html', {
|
||||||
'form': form,
|
'form': form,
|
||||||
'items': items
|
'items': items
|
||||||
})
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user