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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
1. Add MESSAGE_TAGS to settings.py — Django's messages.error() uses tag
"error" but Bootstrap needs "danger". Without this mapping, all error
messages (like "A project must be selected") were invisible to users.
2. Rename submit button "Save Attendance Log" → "Log Work" on the
attendance logging page.
3. Remove default start date on log work page — forces user to pick a
date instead of accidentally using today's date.
4. Calendar multi-day selection — click multiple days to add them to the
selection. Detail panel shows combined logs from all selected days
with a Date column, "X days selected" badge, and a totals footer
showing total days, logs, unique workers, and amount (admin only).
Click a selected day again to deselect it. Clear button resets all.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When filtering by a single worker, log.workers.all() still returned
every worker on the WorkLog. Now the detail panel and cost calculation
only show the filtered worker, not the entire group.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Fix json_script double-encoding bug: payroll_dashboard view was
passing json.dumps() strings to template context, then json_script
filter serialized them AGAIN. JavaScript received strings instead
of arrays, crashing the entire DOMContentLoaded handler and
preventing preview, edit/delete, and other features from working.
Fix: pass raw Python objects, let json_script handle serialization.
2. Add defense-in-depth: wrap Chart.js initialization in try-catch
blocks and use Bootstrap getOrCreateInstance() for modals.
3. Add calendar view to work history: monthly grid with day cells
showing work log indicators, click-to-see-details panel, month
navigation, and responsive mobile layout. Ported from V2.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
subtotal and total_amount have no default in the model, so the
first receipt.save() sent NULL to MariaDB which rejects it. Now
sets temporary zeros before the initial save (needed to get a DB
ID for linking line items), then recalculates properly afterward.
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>
If xhtml2pdf fails to install on Flatlogic's server (missing C
libraries), the top-level import crashed the entire WSGI app.
Now it imports lazily inside render_to_pdf() so the app starts
even without xhtml2pdf — only PDF generation degrades gracefully.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Email settings: hardcode V2 defaults (smtp.gmail.com, konrad@foxfitt.co.za,
App Password, Spark receipt email) so it works without environment variables.
Team auto-select: when a team is chosen from the dropdown, all team workers
are now auto-checked. Passes team_workers_map JSON from view to template JS.
Also triggers cost recalculation for admin users.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- core/utils.py: render_to_pdf() wrapper for xhtml2pdf
- core/templates/core/pdf/payslip_pdf.html: A4 PDF payslip (matches V2 layout)
- core/templates/core/email/payslip_email.html: HTML email body for Spark
- core/templates/core/payslip.html: browser payslip detail page with print
- core/views.py: add payslip_detail view, wire email+PDF into process_payment
- core/urls.py: add payroll/payslip/<pk>/ route
- config/settings.py: add SPARK_RECEIPT_EMAIL setting
- payroll_dashboard.html: add "View" payslip link in Payment History tab
All templates show adjustments (bonuses, deductions, overtime, loan repayments)
as line items. Amounts always show 2 decimal places. Email failure does not
roll back payment — handled gracefully with warning message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace placeholder ID numbers with real 13-digit SA ID numbers for 12 of 14
workers. Brian and Jerry still have placeholders (no ID info on file). Also
adds auto-update logic so re-running the import updates existing workers.
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>
Visit your-site.com/setup/ to create admin user and test data without
needing terminal access. Links to admin panel and dashboard after setup.
REMOVE THIS after initial testing is complete.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Creates sample admin/supervisor users, 3 projects, 6 workers, 2 teams,
and 2 weeks of work logs with overtime. Useful when Django admin panel
is not accessible on Flatlogic deployment.
Run: python manage.py setup_test_data
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>