Konrad confirmed the 36-commit bundle "all working well" on prod
(17 May 2026). Flip CLAUDE.md + parked-work.md production status from
"deploy pending" to "✅ fully caught up & verified at 80d96d7".
Also flags the in-progress (local-only) SiteReport removal.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Flip parked-work.md + CLAUDE.md from "paused, not pushed" to "pushed to
origin/ai-dev d7015b9..4c25011, Flatlogic VM deploy pending (migrate +
collectstatic + restart-last)". Prevents a fresh session reading stale
"not pushed" status.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Konrad's decision (15 May 2026): Phase A.2 (manual JournalEntry
UI) + Phase B (Letterly inbound webhook) are too complex to
interleave with normal app work — they'll be built and tested
offline on a separate track, not in ai-dev.
Verified there is NOTHING to remove or bypass: zero JournalEntry
model/views/urls/templates, zero Letterly/webhook/@csrf_exempt
code anywhere on ai-dev, latest migration is 0015. The working
app was already 100% clean of journal/voice code — these features
never left the design-doc stage.
Doc changes:
- parked-work.md: "Blocked on Konrad's input" section replaced
with "🧊 Backburner — separate offline track", with an explicit
"do NOT start in ai-dev" warning and the nothing-to-remove
verification recorded.
- CLAUDE.md breadcrumb: reframed from "parked pending Q5/Q7
answers" (implies ready-to-go once answered) to "deliberately
deferred to offline track — do not pick up as normal feature
work".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Production caught up — all 14 pending commits live after a
second service restart (the first restart ran before the code
reached the target commit; DEBUG=False's cached template loader
held the old templates until restarted again).
CLAUDE.md:
- 'What's mid-flight' breadcrumb: no longer says pending deploy;
now states production is at 1d224bc and fully live.
- Flatlogic Deployment section: new '⚠ DEPLOY ORDERING' bullet
documenting that production runs DEBUG=False → cached template
loader → restart MUST come after the pull, and template-only
changes still need a restart (unlike DEBUG=True local dev).
Includes the symptom ('git log shows right commit but page
looks old') and the fix (restart again).
- Bumped the {# #} bit-us count + added a grep sanity-check
one-liner (from the prior commit, retained).
parked-work.md: 'Pending pull-and-restart' section replaced with
'Production status — fully caught up'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md gotcha #1 strikes again — the dashboard audit pass added
7 multi-line {# ... #} comment blocks across index.html, report.html,
and pdf/report_pdf.html. All rendered as literal text on the live
pages (Konrad screenshotted them). Also caught an old one in
admin/base_site.html that was technically broken syntax but
non-rendering (outside any block). All 8 converted to
{% comment %}{% endcomment %}.
CLAUDE.md updated:
- Bumped the bit-us count (4 → confirmed 4 + 5 + 7 across three
features). Added a grep-one-liner sanity check that finds broken
multi-line {# blocks across all templates so future passes can
spot-check before committing.
Cryptic hero-card sublines on /report/ clarified (Konrad asked
what they mean):
- "as of 08:13" → "Live total at 08:13 today · for <scope>" with
hover tooltip explaining the snapshot semantics.
- "Company Avg / Working Day" / "/ Month" labels renamed to
"Avg Labour Cost / Working Day" / "/ Month". Sublines simplified
to "Lifetime average across all crews" / "Daily figure × 30.44
days". Both gain hover tooltips that explain the math and the
"current pay rates" basis.
Pure template + docs change. 173/173 tests still passing
(no test changes — these are cosmetic fixes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md breadcrumb: '6 subsequent commits' → '13 subsequent
commits' with a one-paragraph summary of what's pending pull +
restart on production. Points at parked-work.md for the full
commit table.
parked-work.md:
- 'Pending pull-and-restart' table: added 18c75b2 (calendar
month) and the 2e6b78d→c02edce audit-pass range as a single
collapsed entry.
- 'Recently shipped' grew a detailed entry for the 18-finding
audit pass at the top.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Documents two related foot-guns that bit recent audits:
- `Worker.daily_rate` is computed LIVE from `monthly_salary`; it's
never snapshotted onto a WorkLog row. So historical cost totals
inflate retroactively when a worker gets a raise. The new
"at current pay rates" subline on the report hero cards
(commit 4186603) is the visible half of this convention. Future
audits should read this note before deciding "this can't be right".
- Two code paths compute the same formula: Python property and a
SQL `Sum(F('workers__monthly_salary') / Decimal('20'))`. They
produce identical results in normal use but could drift by 1
cent on edge-case rounding. `CompanyCostVelocitySQLAggregateTests`
is the regression test that would catch a real divergence.
Findings 2 + 11.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md breadcrumb: 'subsequent UX polish' → '6 subsequent
commits' so the next session can see at a glance how much is
pending pull-and-restart on production.
parked-work.md:
- 'Small polish follow-ups' section: 7 items cleared on 15 May;
section header retained as a landing pad for future cleanups.
- 'Pending pull-and-restart' table: added rows for 70fa085
(day-name in modal) and d1d3e15 (polish pass) so the deploy
checklist is current.
- 'Recently shipped' grew two new entries at the top: the polish
pass and the day-name modal change.
CLAUDE.md changes:
- 'What's mid-flight' breadcrumb updated: SiteReport/Absences
migrations are LIVE on prod; only the 4 latest UX commits await
a pull-and-restart (no migration / collectstatic needed).
- URL Routes table entries for /workers/ and /history/ now document
the new ?team= filter (and team=none for unassigned / no-team cases).
- Worker Management UI inline description mentions the team filter
+ Absences tab on the worker detail page.
- Two new Coding Style gotchas captured: (1) Bootstrap dropdowns
inside .card elements get clipped by sibling cards — fix is to
lift the wrapping card with position:relative + z-index;
(2) JS reading from data-worker-id was unreliable on production —
read input[name="workers"][value] directly (the attendance-form
pattern that's been working for years).
parked-work.md changes:
- Pending-deploy section rewritten: the BIG deploy (migrations +
collectstatic) is DONE; only 4 small commits await a pull +
service restart.
- 'Recently shipped' grew two new entries: the team filters on
/workers/ + /history/, and the two absences UX polish fixes.
- Updated timestamp to 15 May 2026.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Worker Absences feature shipped (bf6f0a5..27fe05e), so it's no
longer the active queue item. Promoted the pending production
deploy (run /run-migrate/, collectstatic, restart service) to the
top of parked-work.md — production /history/ is 500ing until those
migrations run. CLAUDE.md breadcrumb updated to flag this as the
next operator action when a fresh session starts.
Also captured the small polish follow-ups from the absences code
reviews (AbsenceQuickForm dead code, N+1 in team_workers_map,
duplicated CSV filter block, etc.) so they don't get lost in a
future janitorial pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#5 from checkpoint feedback: /workers/<id>/ now has an Absences tab
showing YTD totals (chip row) + 50 most-recent absences (table).
Admin dashboard adds a conditional 'X absent in last 7 days' alert
card (only renders when count > 0; links to filtered /absences/).
CLAUDE.md gets a new Absence model entry + URL routes + dedicated
'Absence-to-PayrollAdjustment cascade' section. Reason-badge CSS
moved to static/css/custom.css as single source of truth. 4 new tests.
Captures the deferred work (Phase A.2 manual JournalEntry UI,
Phase B Letterly webhook) and queues the next brainstorm topic
(worker absence records). Adds an 8-line breadcrumb at the top
of CLAUDE.md so a fresh session sees it in the first 20 lines.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Companion to attendance: capture WHAT was done on site each day,
alongside WHO worked. Optional 1:1 with WorkLog. Mobile-first form
auto-redirected from /attendance/log/ on success (with a Skip link).
Why this design (vs. extending WorkLog or per-project templates):
- Hybrid schema. Stable + queryable fields are real columns
(`weather`, `temperature_min`, `temperature_max`, `notes`,
`created_by`, `created_at`, `updated_at`). The METRICS that change
per project / over time live in a single JSONField with shape
`{counts: {key: int}, checks: {key: bool}}` — driven by
`core/site_report_schema.py`. Adding a new metric is a one-line
edit to that file, NO migration required. Old reports without the
new key just render as 0 / unchecked.
- Two-step flow. Attendance form is unchanged; on successful POST
the supervisor lands on `/site-report/<work_log_id>/edit/` for the
most-recently-created log. They can fill in progress details
(~30 sec on a phone) or click "Skip" to home. WorkLogs without a
SiteReport are completely valid historic rows.
- Permission scope mirrors WorkLog access. Anyone who can see the
parent log (admin / log's supervisor / project's supervisors) can
see + edit its SiteReport. Wraps the existing pattern from
`work_history()` in a small helper `_can_access_site_report()`.
What ships:
Models:
- SiteReport (1:1 → WorkLog, weather choices, IntegerField temps,
JSONField metrics defaulting to {})
- Migration 0013_add_site_report (pure CreateModel, no schema
changes to existing tables)
Schema:
- core/site_report_schema.py (NEW) — single source of truth for
the metric list. Currently 7 counts + 4 checks per Konrad's
v1 spec. Helpers: get_count_keys, get_check_keys, label_for,
empty_metrics.
Form:
- SiteReportForm (in core/forms.py) — ModelForm with the four
stable fields PLUS dynamic IntegerField/BooleanField per
metric in __init__. save() serializes both halves into the
JSON blob. clean() validates min ≤ max temperature.
Views:
- site_report_edit — create-or-update; stamps created_by on
first save; preserves it on subsequent admin edits
- site_report_detail — read-only display; 404 when no report
- attendance_log redirect updated to two-step flow
- _can_access_site_report — shared permission helper
URLs:
- /site-report/<work_log_id>/edit/ (name: site_report_edit)
- /site-report/<work_log_id>/ (name: site_report_detail)
Templates:
- site_report_edit.html — mobile-first stack of inputs, weather
as a chunky icon-button row (☀️☁️🌧️⛈️🥵🥶💨), counts in a
2-col grid, checks as toggle switches, Notes textarea, Skip
+ Save buttons. Iterates pre-built (metric, bound_field)
pairs from the view to avoid needing a new template filter.
- site_report_detail.html — counts as accent-coloured value
cards, checks as a check-list, weather + temp + notes + edit
link.
- work_history.html — added a small clipboard icon next to
each row's date: filled (linked to detail) when a report
exists, muted outline (linked to edit) when not. Click is
event.stopPropagation()-ed so the row's payroll-modal
handler doesn't also fire.
Performance:
- work_history queryset adds .select_related('site_report') so
the new template indicator doesn't introduce an N+1.
Admin:
- SiteReport registered with raw_id_fields on work_log +
created_by, list filters on weather + project + date.
Tests (16 new, full suite 85/85):
- SiteReportModelTests — defaults, 1:1 reverse accessor,
arbitrary-key JSON round-trip
- SiteReportFormTests — dynamic field generation, save
serialisation, temp validation, instance pre-fill
- SiteReportEditViewTests — admin GET/POST, project
supervisor allowed, outsider supervisor 403, created_by
preserved on subsequent admin edits
- SiteReportDetailViewTests — 404 when absent, displays data
when present
- AttendanceLogRedirectsToSiteReportTests — confirms the
two-step flow
CLAUDE.md updates:
- SiteReport added to "Key Models" with shape + reverse-accessor note
- New "SiteReport metric schema" section near "UI-vs-DB
naming drift" — explains the JSON-column-with-Python-source
pattern, when it's safe, what NOT to do (rename a key with
data), and where the keys appear across the codebase
- URL Routes table gets the two new endpoints
What's NOT in this commit (deferred per the brainstorm plan):
- JournalEntry model + manual web-entry UI (Phase A.2 — depends
on Konrad's Q7 answer about Vi/recipient field)
- Letterly inbound webhook (Phase B — integrations branch only,
depends on Q5 sample payload)
- Photos on site reports (Q9, defaulted to "future")
- Per-project metric templates (Q4, defaulted to "same set for all v1")
Reference plan: ~/.claude/plans/prancy-painting-brook.md (local).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First integration: when a PayrollRecord is created, the app POSTs a
JSON summary to WEBHOOK_PAYSLIP_URL (env var) if set. Unset = feature
OFF; no network call, no behaviour change. Typical destination: a
Make.com / Zapier / n8n Catch-Hook URL that fans the event out to
Google Sheets / Airtable / Slack / etc. — no more Python required.
This is the highest-leverage starter integration per the research plan
at ~/.claude/plans/prancy-painting-brook.md (Section B). One webhook
sender unlocks 5000+ downstream destinations via visual workflow UIs,
and the pattern (post_save signal + env-var gate + try/except) becomes
the template for future event types.
Implementation:
- core/signals.py (new) — post_save receiver on PayrollRecord;
fires only on created=True; short-circuits when env var empty;
swallows all network errors with a WARNING log so payslip save
is never blocked
- core/apps.py — ready() imports signals for dispatcher registration
- config/settings.py — reads WEBHOOK_PAYSLIP_URL env var (default "")
- requirements.txt — adds requests>=2.32.0 (de facto Python HTTP lib,
no prior outbound-HTTP code in the codebase)
- CLAUDE.md — documents the env var + the non-fatal failure contract
+ points at PayslipWebhookTests for the behavioural spec
Payload shape: event, payslip_id, worker_id, worker_name, amount_paid
(as string for Decimal safety), payslip_date (ISO), work_log_count,
adjustment_count, admin_url. No unbounded text fields; no secrets.
Tests (4 new, PayslipWebhookTests):
- fires when configured with right payload
- no-op when env var unset
- swallows ConnectionError without breaking PayrollRecord.save()
- does NOT refire on subsequent .save() of an existing record
Full suite: 73/73.
Risks + rollback: trivial. Revert the commit, no data impact. Make.com
handles its own retries; if the webhook is down we just miss events
until it comes back.
Out of scope for v1 (deferred): other event types (adjustment.created,
loan.issued), HMAC signing, in-app retry queue, inbound webhooks, AI
integrations, public read-only API. All are on the roadmap in the plan
doc; each follows the same signal-based pattern and is cheap to add.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a new CLAUDE.md section documenting the display/DB gap that
Path A of the UX Polish Pass creates: user sees 'Loan' / 'Advance'
/ 'Advance Repaid' while DB stores 'New Loan' / 'Advance Payment'
/ 'Advance Repayment'. Includes a lookup table, the rule for when
to use which (DB for logic, display for templates), and the failure
symptom so future Claude sessions don't chase ghost filters.
Ships BEFORE the rename so the doc is searchable from minute one.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three additions from this session's work:
1. Django ORM gotcha — PayrollAdjustment project double-attribution.
Documents the Coalesce pattern that solved the Apr 2026 perf-pass
double-count bug on Overtime adjustments.
2. Payroll dashboard query-count baselines — target ranges for /
and the four /payroll/ tabs after the perf pass, plus the
"spotting a regression" heuristic (>50% jump = N+1 reintroduced).
3. Profiling locally — Django Debug Toolbar — what it is, how it's
triple-gated, how to use it for N+1 hunting. Flags that the
package is already in requirements.txt so future sessions don't
need to install it.
Net: +35 lines, three new sections, no deletions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final whole-impl review catch on the Perf Quick-Wins Pass. Step 3
said "the mtime of the collected copy under staticfiles/ stays the
same, so the token doesn't bump." That's backwards — the token is
read from static/css/custom.css (the SOURCE file), so editing the
source DOES bump the token and Cloudflare correctly misses on the
next request. What actually breaks is the VM's response to the miss:
Apache serves stale bytes from staticfiles/ because collectstatic
hasn't refreshed the collected copy. New URL, old bytes behind it.
Rewording makes the causal chain correct so future Gemini/Claude
debugging "CSS change deployed but old file still shows" reaches
the right conclusion (run collectstatic on the VM) via the right
reasoning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code-review followups on 16d4399:
- CLAUDE.md's "When CSS changes don't appear" diagnostic steps
were written for the old per-request token. Under mtime-based
caching, a stable ?v= number is the healthy expected state,
not a broken one. Rewrote steps 1 + 3 so someone debugging
a real production CSS issue gets the right advice.
- Dropped unused `original = cp._compute_cache_bust_token` line
in test_token_falls_back_if_file_missing - it misled readers
into thinking the function itself was patched. Added a one-
line comment clarifying the monkey-patch is path-only.
Tests: still 68/68.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
deployment_timestamp was int(time.time()) per-request, giving every
page load a new ?v=... query string on custom.css. Cloudflare treats
each unique URL as a new resource, so the CSS was fetched from the VM
on every page load — 64 KB over the wire per navigation.
Token now tied to static/css/custom.css mtime. The URL only changes
when the CSS actually changes, so Cloudflare can hold the file for
its full 4h TTL. Degraded-mode fallback preserves today's behaviour
if the file isn't on disk.
3 new CacheBustTokenTests; all 68 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit revealed several stale / missing items:
1. Wrong CSS selector for light theme — said `:root.light`, actual is
`[data-theme="light"]`. Task 2 of Adjustments caught this in the
implementer's self-review; the doc didn't get updated. Now correct.
2. `_report_config_modal (partial)` removed from templates list — the
file was deleted in commit 1d00a3a (retire modal).
3. `_adjustment_row.html` added to templates list — new partial, shared
by flat + grouped views on the Adjustments tab.
4. `format_tags.py` now lists all 5 filters: money, money_abs, type_slug,
url_replace, dictlookup (was just 'money').
5. New narrative paragraphs for:
- Inline Filters on /report/ (pill popovers, cross-filter, JSON gotcha)
- Adjustments tab (filter pills, badge palette, group-by, bulk delete)
- _delete_adjustment_with_cascade helper (shared by single+bulk)
- Pill-popover filter pattern (.adj-hidden-inputs + OK-rewrites-inputs)
6. Two new schema name-drifts: PayrollRecord.amount_paid (not total_amount
/ days_worked); Loan.principal_amount (not principal). Both bit an
implementer this session when writing test fixtures.
7. Two new Coding Style rules in the top section:
- Multi-line {# #} template comments are INVALID — use {% comment %}
(bit us 4× in this session). With caveat that literal {# or #} can't
appear inside a {% comment %} block either.
- Duplicate id= attributes silently steal event handlers — grep before
assigning (caught adjSelectAll collision between table header + modal).
Now 707 lines, 24 sections. Future sessions should have the context to
avoid the mistakes this session made.
Captures the 11-task implementation, 5 deviations (biggest: the
CP1 pivot from Choices.js chip-multiselect to popover-checkbox
filter UX after Konrad flagged the chip pattern as intrusive),
14 new adjustments-tab tests, and total code churn (~+1400 lines).
CLAUDE.md URL Routes table gains two rows so future sessions
surface /payroll/?status=adjustments and the bulk-delete endpoint.
Feature ready for final whole-feature code review + batched push.
Five focused updates from the Apr 22-23 bug-fix + gitignore session:
1. Fix stale supervisor-picker queryset doc: it was showing the pre-fix
Q(is_staff)|Q(is_superuser)|Q(groups__name='Work Logger') filter.
Since commit 0ceceeb the queryset is just User.objects.filter(is_active=True).
2. Update "How to add a new supervisor" step 2: Work Logger group
membership is no longer required for picker visibility — optional now.
3. Add "Schema name-drifts to remember" block near Key Models. Three
recurring gotchas that burned four subagent tasks across two sessions:
- PayrollAdjustment.description (not reason)
- log.adjustments_by_work_log (not payrolladjustment_set)
- log.overtime_amount (not log.overtime)
4. Add canonical test-command one-liner to the Commands section:
USE_SQLITE=true DJANGO_DEBUG=true python manage.py test core.tests -v 2
5. Add "Django ORM gotcha" subsection documenting the M2M filter +
values().annotate(Sum()) inflation bug and the id__in subquery fix
pattern (refs commit f1e246c, ReportContextFilterInflationTests).
No code changes; no test impact.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Problem: every time collectstatic ran on the VM, Flatlogic's web UI
detected the modified files in staticfiles/ and auto-committed them
with a generic "Ver XX.YY" message (e.g. "Ver 30.04 Fix reports and
add Supervisor"), pushing the result to gitea but not GitHub. Every
push of CSS/JS changes triggered a reconciliation dance. See the
"Ver 30.04" divergence resolved by commit e0d2c74 for the most recent
example — that was the 3rd or 4th recurrence of this exact pattern.
Fix:
1. Add staticfiles/ to .gitignore
2. Untrack all 627 currently-tracked files via `git rm -r --cached`
3. Document the change in CLAUDE.md (Project Structure, Static Assets,
and a new "NOT tracked in git" subsection)
Deploy consequence: the NEXT pull on the VM will delete
staticfiles/ from the working tree (because git sees those files
removed from the tree). Gemini MUST run `collectstatic --noinput`
IMMEDIATELY after `git pull` to repopulate from source, then
restart the service. Brief window of 404s on static assets is
acceptable at this scale (seconds).
After this change: collectstatic output lives on the VM's filesystem
but outside git's view, so Flatlogic's UI has nothing to auto-commit.
The recurring divergence pattern is permanently eliminated.
No runtime code changes — all 28 tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
- 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>
- 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>
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>
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>
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>