Three small fixes from the final review:
- AbsenceAdmin.save_model() now runs _sync_absence_payroll_adjustment
so toggling is_paid via /admin/ updates the linked Bonus consistently
with the friendly UI.
- _delete_adjustment_with_cascade clears absence.is_paid when deleting
a Bonus linked to an Absence — closes the state-drift window after
bulk-delete from /payroll/?status=adjustments.
- base.html — Resources dropdown 'Absences' entry now shows for
supervisors as well as staff (was staff-only). View-layer permission
helpers (_absence_user_queryset, _user_can_log_absences) already
enforce the real access boundary; this just makes the menu honest.
2 regression tests.
CLAUDE.md gotcha #5: multi-line {# ... #} blocks render as literal text
in Django templates. Converted to {% comment %} blocks in edit.html
and list.html (also scanned log.html / log_confirm.html for safety).
Adds an 'Absences' entry to the Resources dropdown in base.html so the
feature is discoverable from the topbar.
Closes the Task 3 design-goal gap: two user-facing modals (work-log
payroll preview in base.html, split-payslip preview in
payroll_dashboard.html) render adjustment types via JS reading AJAX
JSON. After Task 3's TYPE_CHOICES rename they were still showing
the old long labels because the backend endpoints
(work_log_payroll_ajax, preview_payslip) only emitted adj.type (DB
value), not the display label.
Added a 'type_label' field to the JSON payloads alongside the
existing 'type' field. JS at both render sites now reads
`adj.type_label || adj.type` — with the fallback so any stale
client-side JSON degrades gracefully to the DB value rather than
rendering blank.
Path A still holds: adj.type in JSON stays the DB value for any
identifier purposes; the new type_label is additive.
Tests: 69/69.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues caught by code quality review on commit 2e60124:
1. C1 (critical): the <script> at line ~398 runs during HTML parsing,
BEFORE the modal markup at line ~627 has been parsed. getElementById
returned null, the `if (!modalEl) return;` guard silently exited the
IIFE, and the delegated click listener was never attached — so the
modal was completely dormant. Wrapped the IIFE body in a
DOMContentLoaded handler so the DOM is fully parsed before lookups.
2. I1 (a11y): added aria-labelledby on the modal root + a matching id on
the modal-title h5 so screen readers announce the title correctly
(Bootstrap 5 a11y convention).
No behavioural changes to the JS logic itself — only the wrapping and
two aria attributes on the markup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Modal shell + JS click handler live in base.html so any page opts in
by adding data-log-id to a row. JS uses createElement + textContent
(matches worker_lookup_ajax pattern) to build the modal body from
JSON — no innerHTML. Supervisors never receive the markup.
Footer 'Open full page' links to /history/<id>/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
My previous commit (fb1a8a2) added a multi-line explanatory comment
using Django's {# ... #} syntax, which is single-line only. The comment
therefore rendered as literal text at the top of every page.
This is the second time this session I've made this exact mistake —
lesson for next time: always render a page on the dev server and grep
the response body for '{#' after template changes, even one-liners.
Verified locally this time: leak count = 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The static asset cache-buster in base.html was using
{{ request.timestamp|default:'1.0' }} — but `request.timestamp` is
not a Django request attribute, so the template always fell back to
the literal '1.0'. Every deploy's CSS URL resolved to the same
`custom.css?v=1.0`, so any CDN or browser cache in front of the app
held onto the pre-redesign CSS forever — even hard refreshes in
incognito couldn't bust it.
Symptom: after deploying the redesigned app, the browser continued
to receive a 1,734-byte pre-redesign custom.css while the VM's
/static/css/custom.css was the full 39,078-byte Premium Orange Theme.
.topbar-nav rules were missing, so the topbar rendered as stacked
block links.
Fix: use `deployment_timestamp` (already provided by
core.context_processors.project_context as int(time.time()) at
render time). Every restart gets a fresh URL, CDNs refetch from
origin, stale caches break.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move decorative gradient glows from ::before/::after pseudo-elements on
.app-main to a separate .app-glow div. The pseudo-elements were creating
a stacking context that trapped Bootstrap modals (z-index 1055) inside
.app-main, while the backdrop (z-index 1050) was appended to <body> —
causing the backdrop to render on top of the modal content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the green accent with a warm orange/amber palette and switch to a
dark-first design. Add a fixed sidebar for desktop navigation and a bottom
tab bar for mobile, replacing the top navbar. Cards now use glass-morphism
with left accent bars, buttons use orange gradients, and decorative glow
effects add depth. All 8 page templates updated, both light and dark modes
tested across desktop and mobile viewports.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Redesign Advance Payments to work like loans with tracked balances:
- Add loan_type field to Loan model ('loan' or 'advance')
- Move Advance Payment from DEDUCTIVE to ADDITIVE types (worker receives money)
- Add new Advance Repayment type for deducting from future salary
- Create/edit/delete handlers mirror New Loan behavior for advances
- Loans & Advances tab with type badges and filter buttons
Enhance Payslip Preview modal into "Worker Payment Hub":
- Show outstanding loans & advances with balances in preview
- Inline repayment form per loan (amount pre-filled, note, Deduct button)
- AJAX add_repayment_ajax endpoint creates adjustment without page reload
- Modal auto-refreshes after repayment showing updated net pay
- New refreshPreview() JS function enables re-fetching after AJAX
Other changes:
- Rename History to Work History in navbar
- Advance-specific payslip layout for pure advance payments
- Fix JS noProjectTypes to hide Project field for advance types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The integrity hash for bootstrap.bundle.min.js had a lowercase 'x' where
the correct hash has uppercase 'X' (position 27: NNkmXc5s not NNkmxc5s).
This caused the browser to silently block Bootstrap JS execution entirely,
breaking ALL modals (Add Adjustment, Edit, Delete, Price Overtime, Payslip
Preview), dropdowns (navbar), and mobile navbar toggle across the whole app.
Verified by computing sha384 of the actual CDN file:
curl -sL .../bootstrap.bundle.min.js | openssl dgst -sha384 -binary | base64
→ YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Straight port from V2 adapted for V5 field names. Creates expense
receipts with dynamic line items, VAT calculation (Included/Excluded/
None at 15%), and emails HTML + PDF to Spark Receipt. Uses lazy
xhtml2pdf import to avoid crashing if not installed on server.
Files: forms.py (ExpenseReceiptForm + FormSet), views.py (create_receipt),
create_receipt.html, receipt_email.html, receipt_pdf.html, urls.py, base.html
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Admin Panel link inside the Bootstrap dropdown wasn't responding to
clicks (cursor changed but navigation didn't fire). Moved it to a direct
navbar link alongside Dashboard, Payroll, etc. Simplified logout to a
simple button next to username instead of dropdown.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Attendance form: date range (start+end), Sat/Sun checkboxes, conflict
detection with Skip/Overwrite, supervisor auto-set, estimated cost card
- Work history: filter by worker/project/payment status, CSV export,
payment status badges (Paid/Unpaid)
- Supervisor dashboard: stat cards for projects, teams, workers count
- Forms: supervisor filtering (non-admins only see their projects/workers)
- Navbar: History link now works, cleaned up inline styles in base.html
- Management command: setup_groups creates Admin + Work Logger groups
- No model/migration changes — database is untouched
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>