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
|
||||
|
||||
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'}))
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -13,27 +13,41 @@
|
||||
|
||||
<div class="container mb-5 mt-n4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="col-lg-10">
|
||||
<div class="card p-4 shadow-sm">
|
||||
<form method="post" id="workLogForm">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label fw-bold">Date</label>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-bold">Start Date</label>
|
||||
{{ form.date }}
|
||||
{% if form.date.errors %}
|
||||
<div class="text-danger mt-1 small">{{ form.date.errors }}</div>
|
||||
{% endif %}
|
||||
</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>
|
||||
{{ form.project }}
|
||||
{% if form.project.errors %}
|
||||
<div class="text-danger mt-1 small">{{ form.project.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-bold">Team (Optional)</label>
|
||||
{{ form.team }}
|
||||
{% if form.team.errors %}
|
||||
@ -93,22 +107,22 @@
|
||||
</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="fw-bold">The following workers have already been logged for {{ conflict_date }}:</p>
|
||||
<div class="card bg-light mb-3">
|
||||
<p class="fw-bold">The following duplicate entries were found:</p>
|
||||
<div class="card bg-light mb-3" style="max-height: 200px; overflow-y: auto;">
|
||||
<ul class="list-group list-group-flush">
|
||||
{% for worker in conflicting_workers %}
|
||||
<li class="list-group-item bg-transparent">{{ worker.name }}</li>
|
||||
{% for conflict in conflicting_workers %}
|
||||
<li class="list-group-item bg-transparent">{{ conflict.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<p>How would you like to proceed?</p>
|
||||
<div class="alert alert-info small mb-0">
|
||||
<strong>Skip:</strong> Log only the new workers. Keep existing logs as they are.<br>
|
||||
<strong>Overwrite:</strong> Remove these workers from previous logs for this date and add them here.
|
||||
<strong>Skip:</strong> Log only the new entries. Existing logs remain unchanged.<br>
|
||||
<strong>Overwrite:</strong> Update existing logs for these dates with the new project/team selection.
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -139,9 +153,14 @@
|
||||
const teamId = this.value;
|
||||
if (teamId && 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) {
|
||||
// Find the checkbox for this worker ID
|
||||
const checkbox = document.querySelector(`input[name="workers"][value="${id}"]`);
|
||||
if (checkbox) {
|
||||
checkbox.checked = true;
|
||||
|
||||
111
core/views.py
111
core/views.py
@ -79,65 +79,108 @@ def log_attendance(request):
|
||||
if request.method == 'POST':
|
||||
form = WorkLogForm(request.POST, user=request.user)
|
||||
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']
|
||||
project = form.cleaned_data['project']
|
||||
notes = form.cleaned_data['notes']
|
||||
conflict_action = request.POST.get('conflict_action')
|
||||
|
||||
# Check for existing logs for these workers on this date
|
||||
# We want to find workers who ARE in selected_workers AND have a WorkLog on 'date'
|
||||
conflicting_workers = Worker.objects.filter(
|
||||
work_logs__date=date,
|
||||
id__in=selected_workers.values_list('id', flat=True)
|
||||
).distinct()
|
||||
# Generate Target Dates
|
||||
target_dates = []
|
||||
if end_date and end_date >= start_date:
|
||||
curr = start_date
|
||||
while curr <= end_date:
|
||||
# 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 = {
|
||||
'form': form,
|
||||
'team_workers_json': json.dumps(team_workers_map),
|
||||
'conflicting_workers': conflicting_workers,
|
||||
'conflicting_workers': conflicts,
|
||||
'is_conflict': True,
|
||||
'conflict_date': date,
|
||||
}
|
||||
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)
|
||||
|
||||
if day_conflicts.exists():
|
||||
if conflict_action == 'skip':
|
||||
# Exclude conflicting workers
|
||||
conflicting_ids = conflicting_workers.values_list('id', flat=True)
|
||||
conflicting_ids = day_conflicts.values_list('id', flat=True)
|
||||
workers_to_save = [w for w in selected_workers if w.id not in conflicting_ids]
|
||||
|
||||
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).")
|
||||
skipped_count += day_conflicts.count()
|
||||
|
||||
elif conflict_action == 'overwrite':
|
||||
# Remove conflicting workers from their OLD logs
|
||||
for worker in conflicting_workers:
|
||||
old_logs = WorkLog.objects.filter(date=date, workers=worker)
|
||||
for worker in day_conflicts:
|
||||
old_logs = WorkLog.objects.filter(date=d, workers=worker)
|
||||
for log in old_logs:
|
||||
log.workers.remove(worker)
|
||||
# Cleanup empty logs
|
||||
if log.workers.count() == 0:
|
||||
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:
|
||||
# No conflicts initially
|
||||
messages.success(request, "Work log saved successfully.")
|
||||
if workers_to_save:
|
||||
# Create Log
|
||||
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
|
||||
work_log = form.save(commit=False)
|
||||
if request.user.is_authenticated:
|
||||
work_log.supervisor = request.user
|
||||
work_log.save()
|
||||
|
||||
# Manually set workers
|
||||
work_log.workers.set(workers_to_save)
|
||||
msg = f"Logged {created_count} entries."
|
||||
if skipped_count:
|
||||
msg += f" Skipped {skipped_count} duplicates."
|
||||
if overwritten_count:
|
||||
msg += f" Overwrote {overwritten_count} previous entries."
|
||||
|
||||
messages.success(request, msg)
|
||||
return redirect('home')
|
||||
else:
|
||||
form = WorkLogForm(user=request.user if request.user.is_authenticated else None)
|
||||
|
||||
@ -23,6 +23,12 @@ h1, h2, h3, .heading-font {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
background: linear-gradient(135deg, var(--primary-color) 0%, #334155 100%);
|
||||
color: var(--white);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user