73 Commits

Author SHA1 Message Date
Konrad du Plessis
a8ef7bb341 Update CLAUDE.md with cache-busting, email fallback, and deploy context
Documents three things that came out of today's Phase 2 deploy session
and weren't previously written down:

1. Static Assets & Cache-Busting (new section): explains that production
   traffic goes through Cloudflare with 4h edge cache; the
   `deployment_timestamp` template variable is what breaks stale caches;
   and why `request.timestamp` must never be used (the silent-default-to-1.0
   bug that ate a couple of hours).

2. Environment Variables: inline notes for each var. Most important new
   fact is that DEFAULT_FROM_EMAIL is now optional — falls back to
   EMAIL_HOST_USER if unset (prevents the "Invalid address ''" failure
   mode on outbound mail). Also documents that .env lives at BASE_DIR.parent
   on Flatlogic and can only be edited via Gemini/shell.

3. Flatlogic Deployment: collectstatic isn't auto-run, django-dev.service
   runs manage.py runserver (dev server in prod — known but works at this
   scale), Cloudflare sits in front, VM has two git remotes (github +
   gitea) that must stay in sync, VM-local safety branches for rollback,
   and the "pick one write path" workflow rule to avoid divergence.

No code changes — documentation only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 04:42:32 +02:00
Konrad du Plessis
5d6446ae75 Fix empty DEFAULT_FROM_EMAIL causing 'Invalid address' on outbound mail
When DEFAULT_FROM_EMAIL env var isn't set, it defaulted to an empty
string, causing every outbound email (receipts, payslips) to fail
with: Invalid address "".

Phase 1 removed the hardcoded Gmail fallback for security. The
cleanest restore — without reintroducing a secret default — is to
fall back to EMAIL_HOST_USER, which is already the authenticated
Gmail address we send AS. That address is always valid when SMTP
auth works, and it's already set on the VM (otherwise sending
would fail with an auth error instead).

Now:
  DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "") or EMAIL_HOST_USER

Verified locally: when DEFAULT_FROM_EMAIL is unset and EMAIL_HOST_USER
is 'test@example.com', DEFAULT_FROM_EMAIL resolves to the same address.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 04:33:15 +02:00
Konrad du Plessis
2e83afb28b Fix: replace multi-line {# #} comment with single-line form
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>
2026-04-22 04:24:57 +02:00
Konrad du Plessis
fb1a8a2475 Fix CSS cache-bust: use deployment_timestamp not request.timestamp
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>
2026-04-22 04:14:39 +02:00
Flatlogic Bot
55a995a9d7 Ver 30.16 screeeewup 2026-04-22 01:50:25 +00:00
Konrad du Plessis
3c28387dd3 WIP: 2026-04-22 session checkpoint
Complete working state of the session. Will be split into two deploy
phases (safety scaffolding then feature release) before merging to ai-dev.

Includes:
- Security fixes (email creds / SECRET_KEY / DEBUG / CSRF)
- Backup + restore management commands and browser endpoints
- WeasyPrint migration (replaces xhtml2pdf)
- New Worker fields + WorkerCertificate + WorkerWarning models
- Worker / Team / Project friendly management UIs
- Dashboard cert-expiry card + Manage All buttons
- Bootstrap tooltips (global init + theme-aware CSS)
- Django admin template override (taller M2M pickers)
- Money filter for ZAR currency formatting
- Resources dropdown nav
- Massive CLAUDE.md expansion + deploy plan docs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 00:19:15 +02:00
Konrad du Plessis
deef851e52 Fix dark mode contrast: table text, loan badges, modals, disabled inputs
Override Bootstrap's --bs-table-color to use theme text color so table
numbers (days, amounts, totals) are readable on dark backgrounds. Fix
Loan badge by removing text-dark class and using CSS to force black text
on bg-warning. Add dark mode overrides for disabled form controls, select
option dropdowns, btn-close filter, btn-secondary colors, and Bootstrap
text utility classes (.text-dark, .text-primary, .text-muted, etc.).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 18:57:44 +02:00
Konrad du Plessis
16d03421e8 Fix modal z-index stacking issue caused by sidebar layout
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>
2026-04-20 18:49:43 +02:00
Konrad du Plessis
82c1906607 Redesign UI with premium orange theme, sidebar nav, and bottom tab bar
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>
2026-04-20 18:40:00 +02:00
Konrad du Plessis
a1ac8540ab Add comprehensive features guide and update CLAUDE.md
New docs/FEATURES.md covers all 15 feature areas: dashboard, attendance,
work history, payroll, payments, adjustments, loans, worker lookup, worker
management, team schedules, receipts, emails/PDFs, auth, exports, and
deployment tools. CLAUDE.md updated with accurate line count.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 15:15:24 +02:00
Konrad du Plessis
60ee21dd61 Add Worker Lookup modal to payroll dashboard
New AJAX endpoint (worker_lookup_ajax) returns a comprehensive financial
report card for any active worker. Modal shows: amount payable, outstanding
loans, paid this month/year, loans this year, recent activity, active loans
table, current project + days, PPE sizing, drivers license, and notes.
Worker names across all dashboard tabs are now clickable links that open
the modal. Header button with searchable dropdown for quick access.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 14:15:04 +02:00
Konrad du Plessis
81009be0c6 Add PPE sizing and drivers license fields to Worker model
New fields: shoe_size, overall_top_size, pants_size, tshirt_size,
has_drivers_license (boolean), drivers_license (file upload).
Admin organised into 3 fieldsets. CSV export updated with new columns.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 13:10:46 +02:00
Konrad du Plessis
803f8696e7 Update CLAUDE.md: accurate function count, quick adjust docs
- views.py now has 27 functions (~2470 lines)
- Document the Quick Adjust button on pending payments rows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 12:25:22 +02:00
Konrad du Plessis
cfed13c9f5 Add quick 'Adjust' button to pending payments table rows
Each worker row now has an Adjust button (slider icon) that opens the
Add Adjustment modal with that worker pre-checked and their most recent
project pre-selected. Header Add Adjustment button resets the modal
to a clean state (no workers pre-checked).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 10:25:10 +02:00
Konrad du Plessis
c3bbffe9c0 Update CLAUDE.md with Pay Immediately loan documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 10:00:39 +02:00
Konrad du Plessis
66fab12b90 Add 'Pay Immediately' option for New Loan adjustments
When creating a New Loan, a "Pay Immediately" checkbox (checked by
default) processes the loan right away — creates PayrollRecord, sends
payslip to Spark, and records the loan as paid. Unchecking it keeps
the old behavior where the loan sits in Pending Payments.

Also adds loan-only payslip detection (like advance-only) across all
payslip views: email template, PDF template, and browser detail page
show a clean "Loan Payslip" layout instead of "0 days worked".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 09:59:42 +02:00
Konrad du Plessis
72d40971f1 Update batch pay modal: 3-option loan filter + radio button fix
- Replace "Exclude workers with loans" checkbox with dropdown
  (All Workers / With loans only / Without loans) in batch pay modal,
  matching the pending payments table filter style
- Fix radio button visual state when switching between
  "Until Last Paydate" and "Pay All" modes (set checked after DOM append)
- Update CLAUDE.md with pending table filter and overdue badge docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 09:23:01 +02:00
Konrad du Plessis
3bb75c5615 Replace loan checkbox with 3-option dropdown on pending table
Loans filter now offers: All Workers / With loans only / Without loans.
Replaces the simpler exclude-only checkbox for more flexibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:57:14 +02:00
Konrad du Plessis
1b6ade87af Add overdue badges and filters to pending payments table
- Red 'Overdue' badge on workers with unpaid work from completed pay periods
- Yellow 'Loan' badge on workers with active loans/advances
- Filter bar above table: team dropdown, overdue-only toggle, exclude loans
- All three filters combine (team + overdue + loan) for flexible views
- Overdue detection uses team pay schedule cutoff from get_pay_period()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:53:47 +02:00
Konrad du Plessis
695b7cb3f1 Add 'Exclude workers with loans' checkbox to batch pay modal
Backend adds has_loan flag per worker (checks active Loans).
Frontend shows checkbox only when any eligible worker has a loan.
Combined with team filter in a shared applyBatchFilters() function
that shows/hides rows based on both filters simultaneously.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:45:49 +02:00
Konrad du Plessis
00f16df8b1 Add team filter dropdown to batch pay modal
Client-side filter lets admin narrow batch payment list by team.
Selecting a team hides other workers, unchecks them (so they won't
be paid), and updates the summary total. Select All respects the
filter — only toggles visible rows. Filter resets when switching
between schedule/pay-all modes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:37:48 +02:00
Konrad du Plessis
2c3410e7c7 Update CLAUDE.md with batch pay feature documentation
- Add batch pay workflow docs (schedule vs pay-all modes, shared helper)
- Add batch-pay preview and process endpoints to URL routes table
- Update view count to 19 (~2000 lines)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:32:05 +02:00
Konrad du Plessis
9ebaae1b0c Fix batch pay radio toggle: use persistent JS reference for radio group
The radio group was being removed from DOM then accessed via getElementById
which returned null for detached elements, silently breaking the toggle.
Now uses a persistent JS variable reference that survives DOM removal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:30:11 +02:00
Konrad du Plessis
8d13c552aa Add batch pay mode toggle: Until Last Paydate / Pay All
Radio buttons in the Batch Pay modal let admin choose between:
- "Until Last Paydate" (default): splits at last completed pay period
- "Pay All": includes all unpaid work regardless of pay schedule

Preview re-fetches when mode changes. Workers without teams are
included in Pay All mode (skipped in schedule mode as before).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:26:33 +02:00
Konrad du Plessis
2e6881b7a4 Add batch pay feature and fix pay period cutoff logic
Batch Pay: new button on payroll dashboard lets admins pay multiple
workers at once using team pay schedules. Shows preview modal with
eligible workers, then processes all payments in one click.

Fix: "Split at Pay Date" now uses cutoff_date (end of last completed
period) instead of current period end. This includes ALL overdue work
across completed periods, not just one period.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 22:16:21 +02:00
Konrad du Plessis
79b6345cb9 Document /run-migrate/ endpoint and unreliable auto-migrations
Flatlogic doesn't always run migrations on Pull Latest. Added note
about using /run-migrate/ to fix "Unknown column" errors after deploy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 21:22:42 +02:00
Konrad du Plessis
2c8d80e4a1 Add /run-migrate/ endpoint for browser-based migration
Flatlogic's "Pull Latest" doesn't always run migrations automatically.
This endpoint lets you visit /run-migrate/ to apply pending migrations
to the production MySQL database from the browser.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 21:19:55 +02:00
Konrad du Plessis
394f9bdfe4 Update CLAUDE.md with split payslip and team pay schedule docs
Document the new split payslip feature, team pay schedule fields,
pay period calculation helpers, and backward-compatible process_payment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 21:08:32 +02:00
Konrad du Plessis
409e7bfd57 Add split payslip feature with team pay schedules
Enable selective payment of work logs and adjustments instead of
all-or-nothing. The preview modal now shows checkboxes on every item
(all checked by default) with dynamic net pay recalculation.

Teams can be configured with a pay frequency (weekly/fortnightly/monthly)
and anchor start date. When set, a "Split at Pay Date" button appears
that auto-unchecks items outside the current pay period.

Key changes:
- Team model: add pay_frequency and pay_start_date fields
- preview_payslip: return IDs, dates, and pay period info in JSON
- process_payment: accept optional selected_log_ids/selected_adj_ids
- Preview modal JS: checkboxes, recalcNetPay(), Split button, Pay Selected
- Backward compatible: existing Pay button still processes everything

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 21:07:28 +02:00
Konrad du Plessis
44a0030c46 Show monthly total in project chart tooltip
When hovering over a bar in the Cost by Project chart, the tooltip
now shows the total for that month across all projects at the bottom.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:32:14 +02:00
Konrad du Plessis
ec5c4198d6 Add outstanding breakdown to payroll dashboard too
Same wages/additions/deductions breakdown as the home dashboard,
now also shown on the Payroll Dashboard stat card.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:09:18 +02:00
Konrad du Plessis
d33d5943f9 Add outstanding payments breakdown on dashboard
Split the single outstanding total into unpaid wages, additions, and
deductions so the card shows where the number comes from. Rename the
'General' project bucket to 'No Project' so per-project totals now
visibly sum to the overall total.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:56:40 +02:00
Konrad du Plessis
d51d06d28d Redesign advance payments: auto-process immediately with auto-repayment
Advances are now treated as immediate payments (not pending salary items):
- Auto-creates PayrollRecord + sends payslip email at creation time
- Auto-creates Advance Repayment adjustment for next salary cycle
- Validates worker has unpaid work logs (otherwise use New Loan)
- Requires project selection for cost tracking
- Partial repayment converts advance to regular loan
- Admin can edit auto-repayment amount before payday
- Negative net pay warning in preview modal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 14:23:03 +02:00
Konrad du Plessis
0257b454af Add Advance Payment system + enhanced preview modal with inline repayments
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>
2026-03-05 10:46:58 +02:00
Konrad du Plessis
19c662ec7d Fix 3 critical bugs in dashboard + attendance logging
- Fix outstanding payments: check per-worker (not per-log) to handle partially-paid WorkLogs
- Fix adjustment math: deductions now subtract from outstanding instead of adding
- Fix conflict resolution: use explicit worker ID list (QueryDict.getlist) instead of broken form.data.workers iteration
- Add missing migration 0003 for Project start_date/end_date fields
- Add CLAUDE.md project documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:28:11 +02:00
Konrad du Plessis
b7baf88cfc Add worker name pills on history page + per-worker payroll chart
Work History:
- Worker names now display as rounded pill badges instead of comma-
  separated text, making them easier to scan (both server-rendered
  list view and JS calendar detail view)

Payroll Dashboard:
- New "By Worker" toggle on the Monthly Payroll chart card
- Dropdown to select an active worker with payment history
- Stacked bar chart shows monthly breakdown: base pay, overtime,
  bonuses (positive), deductions, loan repayments, advances (negative)
- All data pre-computed server-side with 2 aggregate queries and
  embedded as JSON — switching workers is instant, no AJAX needed
- Only workers with actual payment history appear in the dropdown
- Legend items auto-hide when a component has no data for that worker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 19:22:20 +02:00
Flatlogic Bot
4791ef8192 Ver 3.1 Payroll link en dasboard design 2026-02-24 14:12:50 +00:00
Konrad du Plessis
81573ba814 Fix broken Run Payroll link + redesign dashboard stat cards
- index.html: Fix Run Payroll shortcut (href="#" → payroll_dashboard URL)
- index.html: Remove max-height scroll on Outstanding by Project card
- payroll_dashboard.html: Redesign analytics cards — 7/5 split layout with
  3 stat cards stacked left, project breakdown card right (no scroll)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:08:32 +02:00
Konrad du Plessis
f486bd532b Fix resource filter — Bootstrap d-flex !important was overriding inline display:none
The Active/Inactive/All filter buttons weren't actually hiding rows because
Bootstrap's d-flex class uses display:flex !important, which beats inline
display:none. Switched to V2's approach: a .resource-hidden CSS class with
display:none !important that properly overrides d-flex.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 01:45:43 +02:00
Konrad du Plessis
97866f1e74 Replace resource filter with V2's Active/Inactive/All button bar
Ported from V2: three-button filter bar (Active | Inactive | All)
that shows/hides resource rows via JS data-active attribute.
Defaults to Active so inactive workers/projects/teams are hidden.
Toggle switch updates data-active instantly and re-applies filter.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 01:25:29 +02:00
Konrad du Plessis
ef77c97719 Revert project dates — migrations not running on Flatlogic
Removed start_date and end_date from Project model. Flatlogic doesn't
run migrations during rebuild, so the DB columns never got created,
crashing the site. Active/inactive resource split is kept.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 01:10:40 +02:00
Konrad du Plessis
47de74bde4 Show active resources by default, collapse inactive + add project dates
Dashboard Manage Resources now shows only active workers/projects/teams
by default. Inactive items are hidden behind a collapsible "Show X
Inactive" button — faded at 50% opacity. Tab badges show active counts.

Also adds start_date and end_date fields to Project model (optional).
Dates display under the project name in the resource list.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:49:57 +02:00
Konrad du Plessis
2aad9ac623 Add Export Workers CSV — downloads all worker data as spreadsheet
Admin-only CSV export with name, ID number, phone, salary, daily rate,
employment date, active status, and notes. Button on dashboard next to
Manage Resources header.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:36:57 +02:00
Konrad du Plessis
3199e52e72 Add data migration to set real SA ID numbers for all workers
Reads ID numbers from the workers_list.xlsx data and matches workers
by first name + surname (case-insensitive). Handles name variations
like "Soldier Aphiwe Dobe" matching "Aphiwe" + "Dobe".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:24:15 +02:00
Konrad du Plessis
b9c0a985c3 Fix template comments rendering as visible text on Work History page
Django {# #} comments can't span multiple lines — they were showing
as raw text in the Workers and Amount columns. Collapsed to single lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:19:35 +02:00
Konrad du Plessis
b6fca98c17 Fix attendance start date, history worker filter, and add Amount column
1. Attendance form: Force start date to blank by clearing Django 5.x auto-fill
   from model default (default=timezone.now). Added self.fields['date'].initial=None
   in AttendanceLogForm.__init__().

2. History list view: When filtering by a specific worker, show only that
   worker's name in the Workers column (not all workers on the log). Uses
   filtered_worker_obj passed from the view.

3. History list view: Added Amount column (admin-only) showing daily cost.
   When filtering by worker, shows that worker's daily_rate. When unfiltered,
   shows total via new WorkLog.display_amount property (sum of all workers'
   daily_rate, uses prefetch cache for efficiency).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 00:13:46 +02:00
Konrad du Plessis
7fd32a0aee Fix Bootstrap JS blocked by wrong SRI hash — single char (x→X)
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>
2026-02-22 23:57:09 +02:00
Konrad du Plessis
0b3ef5395f Fix work history filter — add validation, explicit form action, and visual feedback
- Add explicit action="{% url 'work_history' %}" to filter form (prevents
  potential URL mismatch on Flatlogic proxy)
- Add numeric validation for worker/project GET params (prevents 500 errors)
- Add results counter: "Showing X of Y work logs" when filters are active
- Add active filter badges showing worker name, project name, and status
- Add green left border indicator on filter card when filters are active
- Make Clear button conditional (red, only appears with active filters)
- Add SQLite dev toggle in settings.py for local testing without MariaDB

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 23:53:21 +02:00
Konrad du Plessis
b837932bb4 Fix Add Adjustment form silently failing — add validation + required fields
Root cause: V5 was missing required attributes that V2 had on the Add
Adjustment form. When a user submitted without selecting a project (for
types that require one), the server rejected it with messages.error()
but the error was invisible before the MESSAGE_TAGS fix. Combined with
no client-side validation for workers, the form would silently create
0 adjustments or redirect with no visible feedback.

Fixes:
- Add required attribute to Project select (toggles off for Loan types)
- Add client-side validation: blocks submit if no workers selected
- Add backend validation: returns error if no workers in POST data
- Add "Select All" / "Clear" links for worker checkboxes (matches V2)
- Add "X worker(s) selected" counter for visual feedback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 23:22:20 +02:00
Konrad du Plessis
0fa25e1538 Prevent duplicate payslip emails from double-click on Pay button
Problem: Supervisors on slow mobile connections sometimes double-click
the "Pay" button, causing two PayrollRecords + two payslip emails to
be sent to Spark Receipt for the same worker.

Backend fix (the critical part):
- Moved unpaid_logs and pending_adjs queries INSIDE transaction.atomic()
- Added select_for_update() on Worker row — this database-level lock
  forces the second concurrent request to WAIT until the first commits
- After the lock is acquired, the second request re-queries and finds
  no unpaid logs (already paid by first request), so it bails out

Frontend fix (defence-in-depth):
- Pay button now shows a Bootstrap spinner + "Processing..." text
- Second click is blocked with e.preventDefault() if button is
  already disabled (handles edge case where form resubmits)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 23:06:31 +02:00