Ver 9 Bulk add
This commit is contained in:
parent
dbb09ad772
commit
af006bef5f
Binary file not shown.
Binary file not shown.
@ -2,6 +2,23 @@ from django import forms
|
|||||||
from .models import WorkLog, Project, Worker, Team
|
from .models import WorkLog, Project, Worker, Team
|
||||||
|
|
||||||
class WorkLogForm(forms.ModelForm):
|
class WorkLogForm(forms.ModelForm):
|
||||||
|
end_date = forms.DateField(
|
||||||
|
required=False,
|
||||||
|
widget=forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||||||
|
label="End Date (Optional)"
|
||||||
|
)
|
||||||
|
include_saturday = forms.BooleanField(
|
||||||
|
required=False,
|
||||||
|
label="Include Saturday",
|
||||||
|
initial=False,
|
||||||
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||||
|
)
|
||||||
|
include_sunday = forms.BooleanField(
|
||||||
|
required=False,
|
||||||
|
label="Include Sunday",
|
||||||
|
initial=False,
|
||||||
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
||||||
|
)
|
||||||
team = forms.ModelChoiceField(queryset=Team.objects.none(), required=False, empty_label="Select Team", widget=forms.Select(attrs={'class': 'form-control'}))
|
team = forms.ModelChoiceField(queryset=Team.objects.none(), required=False, empty_label="Select Team", widget=forms.Select(attrs={'class': 'form-control'}))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
@ -13,27 +13,41 @@
|
|||||||
|
|
||||||
<div class="container mb-5 mt-n4">
|
<div class="container mb-5 mt-n4">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-10">
|
||||||
<div class="card p-4 shadow-sm">
|
<div class="card p-4 shadow-sm">
|
||||||
<form method="post" id="workLogForm">
|
<form method="post" id="workLogForm">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-4">
|
<div class="col-md-3">
|
||||||
<label class="form-label fw-bold">Date</label>
|
<label class="form-label fw-bold">Start Date</label>
|
||||||
{{ form.date }}
|
{{ form.date }}
|
||||||
{% if form.date.errors %}
|
{% if form.date.errors %}
|
||||||
<div class="text-danger mt-1 small">{{ form.date.errors }}</div>
|
<div class="text-danger mt-1 small">{{ form.date.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-3">
|
||||||
|
<label class="form-label fw-bold">End Date (Optional)</label>
|
||||||
|
{{ form.end_date }}
|
||||||
|
<div class="mt-2">
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
{{ form.include_saturday }}
|
||||||
|
<label class="form-check-label small" for="{{ form.include_saturday.id_for_label }}">Sat</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
{{ form.include_sunday }}
|
||||||
|
<label class="form-check-label small" for="{{ form.include_sunday.id_for_label }}">Sun</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
<label class="form-label fw-bold">Project</label>
|
<label class="form-label fw-bold">Project</label>
|
||||||
{{ form.project }}
|
{{ form.project }}
|
||||||
{% if form.project.errors %}
|
{% if form.project.errors %}
|
||||||
<div class="text-danger mt-1 small">{{ form.project.errors }}</div>
|
<div class="text-danger mt-1 small">{{ form.project.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-3">
|
||||||
<label class="form-label fw-bold">Team (Optional)</label>
|
<label class="form-label fw-bold">Team (Optional)</label>
|
||||||
{{ form.team }}
|
{{ form.team }}
|
||||||
{% if form.team.errors %}
|
{% if form.team.errors %}
|
||||||
@ -93,22 +107,22 @@
|
|||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p class="fw-bold">The following workers have already been logged for {{ conflict_date }}:</p>
|
<p class="fw-bold">The following duplicate entries were found:</p>
|
||||||
<div class="card bg-light mb-3">
|
<div class="card bg-light mb-3" style="max-height: 200px; overflow-y: auto;">
|
||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
{% for worker in conflicting_workers %}
|
{% for conflict in conflicting_workers %}
|
||||||
<li class="list-group-item bg-transparent">{{ worker.name }}</li>
|
<li class="list-group-item bg-transparent">{{ conflict.name }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<p>How would you like to proceed?</p>
|
<p>How would you like to proceed?</p>
|
||||||
<div class="alert alert-info small mb-0">
|
<div class="alert alert-info small mb-0">
|
||||||
<strong>Skip:</strong> Log only the new workers. Keep existing logs as they are.<br>
|
<strong>Skip:</strong> Log only the new entries. Existing logs remain unchanged.<br>
|
||||||
<strong>Overwrite:</strong> Remove these workers from previous logs for this date and add them here.
|
<strong>Overwrite:</strong> Update existing logs for these dates with the new project/team selection.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" onclick="submitConflict('skip')">Skip Duplicate Workers</button>
|
<button type="button" class="btn btn-secondary" onclick="submitConflict('skip')">Skip Duplicates</button>
|
||||||
<button type="button" class="btn btn-primary" onclick="submitConflict('overwrite')">Overwrite Existing</button>
|
<button type="button" class="btn btn-primary" onclick="submitConflict('overwrite')">Overwrite Existing</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -139,9 +153,14 @@
|
|||||||
const teamId = this.value;
|
const teamId = this.value;
|
||||||
if (teamId && teamWorkersMap[teamId]) {
|
if (teamId && teamWorkersMap[teamId]) {
|
||||||
const workerIds = teamWorkersMap[teamId];
|
const workerIds = teamWorkersMap[teamId];
|
||||||
// Select workers belonging to the team
|
// 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.
|
||||||
|
|
||||||
workerIds.forEach(function(id) {
|
workerIds.forEach(function(id) {
|
||||||
// Find the checkbox for this worker ID
|
|
||||||
const checkbox = document.querySelector(`input[name="workers"][value="${id}"]`);
|
const checkbox = document.querySelector(`input[name="workers"][value="${id}"]`);
|
||||||
if (checkbox) {
|
if (checkbox) {
|
||||||
checkbox.checked = true;
|
checkbox.checked = true;
|
||||||
|
|||||||
111
core/views.py
111
core/views.py
@ -79,65 +79,108 @@ def log_attendance(request):
|
|||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = WorkLogForm(request.POST, user=request.user)
|
form = WorkLogForm(request.POST, user=request.user)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
date = form.cleaned_data['date']
|
start_date = form.cleaned_data['date']
|
||||||
|
end_date = form.cleaned_data.get('end_date')
|
||||||
|
include_sat = form.cleaned_data.get('include_saturday')
|
||||||
|
include_sun = form.cleaned_data.get('include_sunday')
|
||||||
selected_workers = form.cleaned_data['workers']
|
selected_workers = form.cleaned_data['workers']
|
||||||
|
project = form.cleaned_data['project']
|
||||||
|
notes = form.cleaned_data['notes']
|
||||||
conflict_action = request.POST.get('conflict_action')
|
conflict_action = request.POST.get('conflict_action')
|
||||||
|
|
||||||
# Check for existing logs for these workers on this date
|
# Generate Target Dates
|
||||||
# We want to find workers who ARE in selected_workers AND have a WorkLog on 'date'
|
target_dates = []
|
||||||
conflicting_workers = Worker.objects.filter(
|
if end_date and end_date >= start_date:
|
||||||
work_logs__date=date,
|
curr = start_date
|
||||||
id__in=selected_workers.values_list('id', flat=True)
|
while curr <= end_date:
|
||||||
).distinct()
|
# 5 = Saturday, 6 = Sunday
|
||||||
|
if (curr.weekday() == 5 and not include_sat) or (curr.weekday() == 6 and not include_sun):
|
||||||
|
curr += timedelta(days=1)
|
||||||
|
continue
|
||||||
|
target_dates.append(curr)
|
||||||
|
curr += timedelta(days=1)
|
||||||
|
else:
|
||||||
|
target_dates = [start_date]
|
||||||
|
|
||||||
if conflicting_workers.exists() and not conflict_action:
|
if not target_dates:
|
||||||
|
messages.warning(request, "No valid dates selected (check weekends).")
|
||||||
|
return render(request, 'core/log_attendance.html', {
|
||||||
|
'form': form, 'team_workers_json': json.dumps(team_workers_map)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Check Conflicts - Scan all target dates
|
||||||
|
if not conflict_action:
|
||||||
|
conflicts = []
|
||||||
|
for d in target_dates:
|
||||||
|
# Find workers who already have a log on this date
|
||||||
|
existing_logs = WorkLog.objects.filter(date=d, workers__in=selected_workers).distinct()
|
||||||
|
for log in existing_logs:
|
||||||
|
# Which of the selected workers are in this log?
|
||||||
|
for w in log.workers.all():
|
||||||
|
if w in selected_workers:
|
||||||
|
# Avoid adding duplicates if multiple logs exist for same worker/day (rare but possible)
|
||||||
|
conflict_entry = {'name': f"{w.name} ({d.strftime('%Y-%m-%d')})"}
|
||||||
|
if conflict_entry not in conflicts:
|
||||||
|
conflicts.append(conflict_entry)
|
||||||
|
|
||||||
|
if conflicts:
|
||||||
context = {
|
context = {
|
||||||
'form': form,
|
'form': form,
|
||||||
'team_workers_json': json.dumps(team_workers_map),
|
'team_workers_json': json.dumps(team_workers_map),
|
||||||
'conflicting_workers': conflicting_workers,
|
'conflicting_workers': conflicts,
|
||||||
'is_conflict': True,
|
'is_conflict': True,
|
||||||
'conflict_date': date,
|
|
||||||
}
|
}
|
||||||
return render(request, 'core/log_attendance.html', context)
|
return render(request, 'core/log_attendance.html', context)
|
||||||
|
|
||||||
# If we are here, either no conflicts or action is chosen
|
# Execution Phase
|
||||||
|
created_count = 0
|
||||||
|
skipped_count = 0
|
||||||
|
overwritten_count = 0
|
||||||
|
|
||||||
|
for d in target_dates:
|
||||||
|
# Find conflicts for this specific day
|
||||||
|
day_conflicts = Worker.objects.filter(
|
||||||
|
work_logs__date=d,
|
||||||
|
id__in=selected_workers.values_list('id', flat=True)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
workers_to_save = list(selected_workers)
|
workers_to_save = list(selected_workers)
|
||||||
|
|
||||||
|
if day_conflicts.exists():
|
||||||
if conflict_action == 'skip':
|
if conflict_action == 'skip':
|
||||||
# Exclude conflicting workers
|
conflicting_ids = day_conflicts.values_list('id', flat=True)
|
||||||
conflicting_ids = conflicting_workers.values_list('id', flat=True)
|
|
||||||
workers_to_save = [w for w in selected_workers if w.id not in conflicting_ids]
|
workers_to_save = [w for w in selected_workers if w.id not in conflicting_ids]
|
||||||
|
skipped_count += day_conflicts.count()
|
||||||
if not workers_to_save:
|
|
||||||
messages.warning(request, "No new workers to log (all skipped).")
|
|
||||||
return redirect('home')
|
|
||||||
|
|
||||||
messages.success(request, f"Logged {len(workers_to_save)} workers (skipped {conflicting_workers.count()} duplicates).")
|
|
||||||
|
|
||||||
elif conflict_action == 'overwrite':
|
elif conflict_action == 'overwrite':
|
||||||
# Remove conflicting workers from their OLD logs
|
# Remove conflicting workers from their OLD logs
|
||||||
for worker in conflicting_workers:
|
for worker in day_conflicts:
|
||||||
old_logs = WorkLog.objects.filter(date=date, workers=worker)
|
old_logs = WorkLog.objects.filter(date=d, workers=worker)
|
||||||
for log in old_logs:
|
for log in old_logs:
|
||||||
log.workers.remove(worker)
|
log.workers.remove(worker)
|
||||||
# Cleanup empty logs
|
|
||||||
if log.workers.count() == 0:
|
if log.workers.count() == 0:
|
||||||
log.delete()
|
log.delete()
|
||||||
messages.success(request, f"Logged {len(workers_to_save)} workers (overwrote {conflicting_workers.count()} previous entries).")
|
overwritten_count += day_conflicts.count()
|
||||||
|
# workers_to_save remains full list
|
||||||
|
|
||||||
else:
|
if workers_to_save:
|
||||||
# No conflicts initially
|
# Create Log
|
||||||
messages.success(request, "Work log saved successfully.")
|
log = WorkLog.objects.create(
|
||||||
|
date=d,
|
||||||
|
project=project,
|
||||||
|
notes=notes,
|
||||||
|
supervisor=request.user if request.user.is_authenticated else None
|
||||||
|
)
|
||||||
|
log.workers.set(workers_to_save)
|
||||||
|
created_count += len(workers_to_save)
|
||||||
|
|
||||||
# Save the new log
|
msg = f"Logged {created_count} entries."
|
||||||
work_log = form.save(commit=False)
|
if skipped_count:
|
||||||
if request.user.is_authenticated:
|
msg += f" Skipped {skipped_count} duplicates."
|
||||||
work_log.supervisor = request.user
|
if overwritten_count:
|
||||||
work_log.save()
|
msg += f" Overwrote {overwritten_count} previous entries."
|
||||||
|
|
||||||
# Manually set workers
|
|
||||||
work_log.workers.set(workers_to_save)
|
|
||||||
|
|
||||||
|
messages.success(request, msg)
|
||||||
return redirect('home')
|
return redirect('home')
|
||||||
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)
|
||||||
|
|||||||
@ -23,6 +23,12 @@ h1, h2, h3, .heading-font {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navbar {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard-header {
|
.dashboard-header {
|
||||||
background: linear-gradient(135deg, var(--primary-color) 0%, #334155 100%);
|
background: linear-gradient(135deg, var(--primary-color) 0%, #334155 100%);
|
||||||
color: var(--white);
|
color: var(--white);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user