Konrad du Plessis 32972276b5 feat(absences): add optional project FK on Absence
Migration 0015 adds Project FK (SET_NULL, nullable) to Absence.
When is_paid=True, the auto-Bonus PayrollAdjustment inherits the
project for cost-attribution. Form + admin + list + edit + log
templates expose the field. List view filter now uses
absence.project_id directly (was indirect via worker__work_logs).
5 new tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 22:11:22 +02:00

99 lines
4.1 KiB
HTML

{% extends 'base.html' %}
{% block title %}Edit Absence | FoxFitt{% endblock %}
{% block content %}
{% comment %}
=== EDIT ABSENCE PAGE ===
Single form for editing one Absence row. The is_paid checkbox is
the magic field — toggling it on creates a Bonus PayrollAdjustment
at the worker's daily rate; toggling it off deletes the adjustment
(UNLESS it's already been paid, in which case the view surfaces an
error).
Why two forms? HTML doesn't allow nested <form> tags. The delete
action needs its own form to POST to /absences/<id>/delete/. So
the delete form lives OUTSIDE the edit form (hidden), and the
Delete button inside the edit form uses the HTML5 `form="..."`
attribute to submit the delete form instead of its parent edit
form.
{% endcomment %}
<div class="container py-4">
<h1 class="page-title mb-3"><i class="fas fa-pen me-2"></i>Edit Absence</h1>
{% if messages %}
{% for m in messages %}<div class="alert alert-{{ m.tags }}">{{ m }}</div>{% endfor %}
{% endif %}
{% comment %}
Hidden sibling form so the Delete button can submit to its own URL.
Hidden via inline style — only the button inside the edit form is visible.
{% endcomment %}
<form method="post" action="{% url 'absence_delete' absence.id %}"
onsubmit="return confirm('Delete this absence?');"
id="absence-delete-form" style="display: none;">
{% csrf_token %}
</form>
<form method="post" class="card">
{% csrf_token %}
<div class="card-body p-3 p-md-4">
<div class="row g-3">
<div class="col-12 col-md-6">
<label class="form-label" for="{{ form.worker.id_for_label }}">Worker</label>
{{ form.worker }}
{{ form.worker.errors }}
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="{{ form.date.id_for_label }}">Date</label>
{{ form.date }}
{{ form.date.errors }}
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="{{ form.project.id_for_label }}">Project (optional)</label>
{{ form.project }}
{{ form.project.errors }}
<small class="text-muted">Which project was the worker absent from?</small>
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="{{ form.reason.id_for_label }}">Reason</label>
{{ form.reason }}
{{ form.reason.errors }}
</div>
<div class="col-12 col-md-6 d-flex align-items-end">
<div class="form-check">
{{ form.is_paid }}
<label class="form-check-label" for="{{ form.is_paid.id_for_label }}">
Paid at daily rate
</label>
</div>
</div>
<div class="col-12">
<label class="form-label" for="{{ form.notes.id_for_label }}">Notes</label>
{{ form.notes }}
{{ form.notes.errors }}
</div>
</div>
{% if form.non_field_errors %}
<div class="alert alert-danger mt-3">{{ form.non_field_errors }}</div>
{% endif %}
<div class="d-flex justify-content-between mt-3">
{% comment %}
`form="absence-delete-form"` makes this button submit the
hidden delete form rather than its enclosing edit form.
{% endcomment %}
<button type="submit" form="absence-delete-form" class="btn btn-outline-danger">
<i class="fas fa-trash me-1"></i>Delete
</button>
<div>
<a href="{% url 'absence_list' %}" class="btn btn-outline-secondary">Cancel</a>
<button type="submit" class="btn btn-accent">Save</button>
</div>
</div>
</div>
</form>
</div>
{% endblock %}