Skip to content

KAN-326: leave reconciliation without the impossible pay-run delete - #518

Merged
corrin merged 6 commits into
mainfrom
kan-326-payroll-deleted-payrun-status
Aug 2, 2026
Merged

KAN-326: leave reconciliation without the impossible pay-run delete#518
corrin merged 6 commits into
mainfrom
kan-326-payroll-deleted-payrun-status

Conversation

@corrin

@corrin corrin commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Prod incident

Posting payroll crashed every run with Invalid value for pay_run_status (Deleted), must be one of ['Draft', 'Posted', 'None'] once one employee's leave stopped matching (KAN-326).

Three-link chain, all fixed here:

  1. Differ churn — leave was keyed on a uniform hours-per-day, but Xero stores leave as one lumped period per pay week carrying only the total (verified live: per-day units sent to the API are silently discarded and recomputed from the working pattern). Mixed-hours leave (4.5/8/8/8 = 28.5h) could never match → deleted-and-recreated every run.
  2. Xero blocks leave deletion while the employee is in a draft pay run (400, string-only error).
  3. The self-heal was impossibledelete_same_week_draft_pay_run PUT PayRun(status="Deleted"), rejected client-side by the SDK; and /PayRuns/{PayRunID} is GET-only in the NZ Payroll API (per Xero's OpenAPI spec) — draft pay run deletion is Xero-UI-only in every region. Born broken in 72c47f3; its test mocked out the crashing function.

The fix

  • Key leave on (type, span, total units) — the only representation Xero round-trips. Builder emits one request per contiguous run of days, summing same-day entries.
  • Stale leave with an overlapping same-type desired request is updated in place — verified live that Xero allows leave updates during a draft pay run, unlike deletes. Payload is the one shape Xero honours: a single pay-week period with total units.
  • Only counterpart-less leave is deleted; when Xero blocks that, DraftPayRunBlocksLeaveChange surfaces an actionable Payroll Error naming the staff member and the draft pay run(s) to delete in the Xero UI (enumerating all mirrored drafts — the block is per employee-in-any-draft, not same-week).
  • The impossible auto-delete path (delete_same_week_draft_pay_run, _update_pay_run, _find_same_week_draft_pay_run, _pay_run_payload_from_object, the retry recursion) and dead code (_post_leave_entries, _delete_existing_leave_for_week) are removed outright (ADR 0017).

Verification

  • scripts/verify_kan326_leave_contracts.py pinned the undocumented API contracts against the dev demo tenant before the fix was built (create-discards-per-day-units, single-period create honoured, update-during-draft works, delete-during-draft blocked with the exact prod string). It stays until the demo tenant is cleaned up (draft pay run 54039178 needs UI deletion first), then gets removed before merge.
  • Live smoke on the demo tenant with a real draft pay run: keep (no churn on the prod scenario), update-in-place during draft, and blocked-delete → actionable message — all behaved as designed.
  • Unit tests rebuilt around real xero_python models so client-side SDK validation is actually exercised (the old suite mocked out the very function that crashed): differ/builder edge cases, the prod-shape regression case, update-in-place payload assertions, actionable-error content, orchestrator enrichment without retry.
  • E2E: none owed — UI unchanged; the failure path requires a live Xero draft pay run Playwright cannot fabricate; the verifier is the live-API evidence (ADR 0026 note).
  • mypy baseline shrinks by 9 lines; new code fully typed.

Prod unblock (manual, separate)

Delete draft pay run 17d7ca66 (week of 27 Jul) in the prod Xero UI, then re-run payroll posting.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PnuHL7w3UisSk6KQ81q3Qf

Summary by CodeRabbit

  • New Features

    • Xero leave requests are now consolidated by payroll week and can be updated without recreating them.
    • Draft pay-run conflicts now provide guidance for resolving them in Xero.
    • Leave and overtime entry commands validate data before saving and support safer dry runs.
  • Bug Fixes

    • Improved leave reconciliation for overlapping, split, and same-day leave.
    • Overtime entries now retain labour subtype details when reclassified.
    • Clearer validation errors identify missing staff configuration.

Addendum: manual time-entry commands crashed on the labour_subtype requirement

Folded into KAN-326 (same payroll surface). KAN-230 (db858444) made CostLine.clean() require labour_subtype on time lines but missed all three timesheet backfill commands: create_leave_entries and create_overtime_entries hand-rolled CostLines without it (crash on the first save), and reclassify_overtime_entries dropped it when splitting a line into an OT copy. create_leave_entries --dry-run reported success for input that would crash — it returned before any CostLine was constructed.

  • Both create commands now build lines via a module-level builder that sets labour_subtype from the staff default, failing early with a named-staff CommandError when unset.
  • create_leave_entries --dry-run performs the real saves inside a rolled-back transaction — it exercises every model rule, entry_seq assignment, and DB constraint, so it can never again report success for entries a live run would reject. (Pre-save validation is impossible by design: CostLine.clean() requires entry_seq, which only save() assigns.)
  • reclassify_overtime_entries copies the source line's subtype (all 18k existing time lines carry one — KAN-230 backfilled).
  • New builder tests; CostLine.save() override chain got its real keyword-only typed signature (mirrors django-stubs), and the overtime command's validated rows are a TypedDict. mypy baseline shrinks 39 lines across the PR.

Prod (KAN-326): posting payroll crashed with the xero-python error
"Invalid value for pay_run_status (Deleted)" every run once a week's
leave stopped matching. Three defects, all fixed here:

- The differ keyed leave on a uniform hours-per-day, so mixed-hours
  leave (e.g. 4.5/8/8/8) could never match and was deleted-and-recreated
  every run. Verified live: Xero only preserves span + total units (one
  lumped period per pay week), so the key is now (type, span, total) and
  the builder emits one request per contiguous run of days, summing
  same-day entries.
- delete_same_week_draft_pay_run built PayRun(pay_run_status="Deleted"),
  which the SDK rejects client-side — and the NZ Payroll API has no
  pay-run delete/update endpoint at all (GET-only; UI-only deletion), so
  the self-heal could never work. Removed it and its helpers outright.
- When a stale leave overlaps a desired request of the same type it is
  now updated in place — verified live that Xero permits leave updates
  during a draft pay run, unlike deletes. Only counterpart-less leave is
  deleted; if Xero blocks that, DraftPayRunBlocksLeaveChange tells the
  operator which draft pay run to remove in the Xero UI, with the staff
  member's name attached by the orchestrator.

create_employee_leave now takes the verified payload shape (single
pay-week period with total units — per-day units are silently discarded
by Xero). Tests rebuilt around real xero_python models so client-side
SDK validation is actually exercised; the old suite mocked out the very
function that crashed. scripts/verify_kan326_leave_contracts.py captured
the live API contracts and stays until the demo tenant is cleaned up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnuHL7w3UisSk6KQ81q3Qf
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Xero leave reconciliation

Layer / File(s) Summary
Payroll-week leave contracts
apps/workflow/api/xero/payroll.py, apps/workflow/api/xero/__init__.py, apps/workflow/tests/test_xero_payroll_leave.py
Leave payloads use one payroll-week period with total units. Existing leave can be updated in place. Public exports and the draft-pay-run exception were updated.
Leave reconciliation and draft-pay-run handling
apps/workflow/api/xero/payroll.py, apps/workflow/tests/test_xero_payroll_leave.py, mypy-baseline.txt
Reconciliation aggregates contiguous leave, updates overlapping records, deletes unmatched records, creates missing records, and reports draft-pay-run instructions without automatic deletion or retry. Tests cover these paths.

Validated manual time-entry commands

Layer / File(s) Summary
Typed CostLine persistence
apps/job/models/costing.py, mypy-baseline.txt
CostLine save-related methods now use explicit Django-compatible type signatures and named argument forwarding.
Validated leave and overtime entry creation
apps/timesheet/management/commands/*entries.py, apps/timesheet/management/commands/reclassify_overtime_entries.py, apps/timesheet/tests/test_manual_time_entry_commands.py
Leave and overtime commands build validated CostLine objects before atomic saves. Dry runs roll back after validation. Split overtime entries preserve labour_subtype.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StaffReconciliation
  participant XeroAPI
  participant LeavePayload
  participant LeaveUpdate
  StaffReconciliation->>XeroAPI: read existing employee leave
  StaffReconciliation->>LeavePayload: aggregate payroll-week leave
  LeavePayload-->>StaffReconciliation: total-unit leave requests
  StaffReconciliation->>LeaveUpdate: update overlapping leave
  LeaveUpdate->>XeroAPI: persist leave change
  StaffReconciliation->>XeroAPI: delete unmatched leave
  StaffReconciliation->>XeroAPI: create missing leave
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the Jira work item and the primary leave-reconciliation change that removes the unsupported pay-run deletion flow.
Description check ✅ Passed The description is detailed and covers the incident, fix, verification, related Jira work item, manual unblock, and additional timesheet changes.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kan-326-payroll-deleted-payrun-status

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/workflow/api/xero/payroll.py (1)

2502-2518: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Persist the delete failure before converting it.

The except block at line 2508 handles the exception from delete_employee_leave but never calls persist_app_error(exc). The update path is covered because update_employee_leave_in_place persists internally. The delete path is not covered, so blocked and unrelated delete failures produce no AppError row. persist_app_error is idempotent, so calling it here is safe.

🛠️ Proposed fix
         except Exception as exc:
+            persist_app_error(
+                exc,
+                additional_context={
+                    "operation": "delete_employee_leave",
+                    "employee_id": str(employee_id),
+                    "leave_id": str(leave.leave_id),
+                },
+            )
             if _is_draft_pay_run_leave_block(exc):
                 raise DraftPayRunBlocksLeaveChange(
                     _draft_block_message(
                         "removed",
                         str(leave.leave_id),
                         leave_start_date,
                         leave_end_date,
                     )
                 ) from exc
             raise
Based on learnings, `persist_app_error` is idempotent and resolves prior persistence through the `__cause__` chain, so this does not create duplicate rows.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/workflow/api/xero/payroll.py` around lines 2502 - 2518, Update the
exception handler around delete_employee_leave to call persist_app_error(exc)
before converting draft pay-run leave errors into DraftPayRunBlocksLeaveChange
or re-raising unrelated failures. Keep the existing exception conversion and
propagation behavior unchanged.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/workflow/api/xero/payroll.py`:
- Around line 1629-1639: In the validation errors within the leave date checks,
replace the EN DASH characters in the messages with ASCII hyphens or “to” while
preserving the existing error content and validation behavior.
- Around line 2247-2261: Update the leave-period total calculation to use only
period.number_of_units; remove the number_of_units_taken fallback and retain the
existing ValueError when number_of_units is absent. Keep the Decimal
accumulation and quantization behavior unchanged.

In `@scripts/verify_kan326_leave_contracts.py`:
- Around line 274-283: Update the ApiException handler around
payroll_api.create_employee_leave to raise SystemExit with the caught exception
as its explicit cause, preserving the existing error output and exit behavior.

---

Outside diff comments:
In `@apps/workflow/api/xero/payroll.py`:
- Around line 2502-2518: Update the exception handler around
delete_employee_leave to call persist_app_error(exc) before converting draft
pay-run leave errors into DraftPayRunBlocksLeaveChange or re-raising unrelated
failures. Keep the existing exception conversion and propagation behavior
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a0845029-eff2-4e03-bf7f-79dfbf360359

📥 Commits

Reviewing files that changed from the base of the PR and between df3c0cc and 1717429.

📒 Files selected for processing (5)
  • apps/workflow/api/xero/__init__.py
  • apps/workflow/api/xero/payroll.py
  • apps/workflow/tests/test_xero_payroll_leave.py
  • mypy-baseline.txt
  • scripts/verify_kan326_leave_contracts.py
💤 Files with no reviewable changes (1)
  • mypy-baseline.txt

Comment thread apps/workflow/api/xero/payroll.py
Comment thread apps/workflow/api/xero/payroll.py
Comment thread scripts/verify_kan326_leave_contracts.py Outdated
corrin and others added 4 commits August 2, 2026 19:25
Its captured API contracts are recorded in PR #518 and on the Jira
ticket; the test leave and draft pay run it created on the demo tenant
have been removed (draft deleted in the Xero UI, leave via --cleanup,
stale mirror row purged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnuHL7w3UisSk6KQ81q3Qf
…ment

KAN-230 (db85844) made CostLine.clean() require labour_subtype on time
lines but missed the three timesheet backfill commands, all of which
hand-roll CostLine creation: create_leave_entries and
create_overtime_entries built lines without it (crashing on the first
save), and reclassify_overtime_entries dropped it when splitting a line
into an OT copy. Worse, create_leave_entries --dry-run reported success
for input that would crash, because it returned before any CostLine was
even constructed.

- Both create commands now build their lines via a module-level builder
  that sets labour_subtype from the staff default and fails early with a
  named-staff CommandError when the default is missing.
- create_leave_entries --dry-run now performs the real saves inside a
  transaction and rolls back, so it exercises every model rule,
  entry_seq assignment, and DB constraint — it can no longer report
  success for entries a live run would reject. (Validating before save
  is impossible by design: CostLine.clean() requires entry_seq, which
  only save() assigns.)
- create_overtime_entries validates and builds every CSV row before its
  existing atomic write; reclassify_overtime_entries copies the source
  line's labour_subtype.
- New tests cover the builders: subtype carried and line saves (the
  regression that shipped), and the named-staff failure when a staff row
  has no default subtype.

Folded into KAN-326 per user decision - same payroll surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnuHL7w3UisSk6KQ81q3Qf
Typing the manual-command fix surfaced that CostLine.save/
_save_with_summary_update/_with_sequence_update_fields were untyped, so
every typed caller tripped no-untyped-call. Mirror the django-stubs
Model.save signature (keyword-only since Django 5) through the override
chain, and type create_overtime_entries' validated rows as a TypedDict.
Baseline shrinks by 30 lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnuHL7w3UisSk6KQ81q3Qf
…ilures

- _leave_total_units no longer falls back to number_of_units_taken:
  that is the consumed quantity, not the paid amount the reconciliation
  key is built on. A period without number_of_units now raises.
- The delete_employee_leave handler persists the failure with its
  business context (operation, employee, leave id) before converting to
  DraftPayRunBlocksLeaveChange, matching the update path and the
  converting-handler pattern; persist_app_error idempotency keeps it to
  one row.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnuHL7w3UisSk6KQ81q3Qf
@corrin

corrin commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

Re the outside-diff comment (persist the delete failure before converting): applied in d35512c — the handler now persists with operation/employee/leave context before raising DraftPayRunBlocksLeaveChange, matching the update path. Note the original claim wasn't quite right: the SSE boundary already persisted these via the cause walk, so no rows were being lost — the change adds business context at the right layer per the converting-handler pattern.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes KAN-326 by making Xero NZ Payroll leave reconciliation match the only data shape Xero round-trips (weekly total units), removing the impossible draft pay-run auto-delete path, and hardening manual time-entry backfill commands to always construct valid CostLine time entries (including required labour_subtype), even in --dry-run.

Changes:

  • Reworked leave differ/builder to key by (type, span, total units), update overlapping stale leave in place, and surface actionable “delete draft pay run in Xero UI” guidance when deletions are blocked.
  • Removed dead / impossible pay-run deletion and related retry recursion; rebuilt tests to exercise real xero_python model validation.
  • Fixed manual timesheet backfill commands by centralizing CostLine-building (sets labour_subtype, fails early when missing) and making --dry-run execute real saves inside a rolled-back transaction.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
mypy-baseline.txt Shrinks the mypy baseline to reflect newly-typed/cleaned code paths.
apps/workflow/tests/test_xero_payroll_leave.py Replaces mocked leave/payload tests with real xero_python model-based tests covering lumped totals, update-in-place, and actionable draft-block errors.
apps/workflow/api/xero/payroll.py Implements new leave request keying/building, update-in-place reconciliation, actionable error surfacing, and removes the impossible draft pay-run delete path.
apps/workflow/api/xero/init.py Updates generated re-exports for the renamed exception and new payroll helpers/types.
apps/timesheet/tests/test_manual_time_entry_commands.py Adds regression tests ensuring manual command builders produce savable time CostLines with labour_subtype.
apps/timesheet/management/commands/reclassify_overtime_entries.py Preserves labour_subtype when splitting time lines into overtime copies.
apps/timesheet/management/commands/create_overtime_entries.py Introduces a shared builder for manual time lines (sets subtype, typed validated rows) and saves validated CostLines atomically.
apps/timesheet/management/commands/create_leave_entries.py Uses a shared leave CostLine builder and makes --dry-run perform real saves inside a rolled-back transaction.
apps/job/models/costing.py Tightens typing of CostLine.save() / _save_with_summary_update() to match Django’s keyword-arg save contract and reduce mypy debt.

Comment on lines +2337 to +2339
drafts = list(
XeroPayRun.objects.filter(pay_run_status="Draft").order_by("period_start_date")
)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 22d8226, with a caveat on the premise: both dev and prod hold exactly one tenant's rows today (prod is single-tenant by construction), so the scenario needs a tenant re-link that leaves a stale Draft row behind. Taken anyway because current-tenant scoping is the semantically correct query for guidance describing the connected tenant's UI — matching ensure_pay_run_for_week's precedent — and the test now pins that a foreign-tenant draft is never named.

Copilot review: _draft_pay_run_summary listed all mirrored drafts, so a
stale row from a previously-connected tenant could be named in the
operator guidance even though it does not exist in the Xero UI being
described. Single-tenant-per-instance makes this mostly theoretical,
but current-tenant scoping is the semantically correct query and
matches ensure_pay_run_for_week's precedent of scoping mirror reads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PnuHL7w3UisSk6KQ81q3Qf
@corrin
corrin merged commit 8e01781 into main Aug 2, 2026
10 of 11 checks passed
@corrin
corrin deleted the kan-326-payroll-deleted-payrun-status branch August 2, 2026 08:30

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/timesheet/management/commands/create_leave_entries.py (1)

109-151: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared staff-labour-subtype guard and CostLine construction into one helper. Both builders independently re-implement the same staff.default_labour_subtype is None guard and the same core CostLine(...) field set (cost_set, kind="time", quantity, unit_cost, unit_rev, accounting_date, staff, xero_pay_item, labour_subtype, and a four-key meta dict). This is the exact validation the PR is fixing; keeping two copies risks one being updated without the other the next time the required field set changes.

  • apps/timesheet/management/commands/create_leave_entries.py#L109-L151: keep build_leave_cost_line's leave-specific parameters (job, leave_type) but delegate the guard clause and the shared CostLine(...) fields to one common helper.
  • apps/timesheet/management/commands/create_overtime_entries.py#L56-L96: have build_manual_time_cost_line call the same shared helper for the guard clause and shared CostLine(...) fields, keeping only its own desc/wage_rate_multiplier handling.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/timesheet/management/commands/create_leave_entries.py` around lines 109
- 151, Extract the shared staff.default_labour_subtype guard and common CostLine
construction into one reusable helper. In
apps/timesheet/management/commands/create_leave_entries.py lines 109-151, update
build_leave_cost_line to retain leave-specific job and leave_type handling while
delegating shared fields to that helper; in
apps/timesheet/management/commands/create_overtime_entries.py lines 56-96,
update build_manual_time_cost_line to use the same helper while retaining its
own description and wage_rate_multiplier handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/timesheet/tests/test_manual_time_entry_commands.py`:
- Around line 39-46: Suppress Ruff S106 for the test fixture password by adding
an inline noqa marker to the password assignment in the
Staff.objects.create_user call, or replace the literal with a named test
constant.

---

Nitpick comments:
In `@apps/timesheet/management/commands/create_leave_entries.py`:
- Around line 109-151: Extract the shared staff.default_labour_subtype guard and
common CostLine construction into one reusable helper. In
apps/timesheet/management/commands/create_leave_entries.py lines 109-151, update
build_leave_cost_line to retain leave-specific job and leave_type handling while
delegating shared fields to that helper; in
apps/timesheet/management/commands/create_overtime_entries.py lines 56-96,
update build_manual_time_cost_line to use the same helper while retaining its
own description and wage_rate_multiplier handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 850011fe-2317-4bff-94d8-55ff34b6c31e

📥 Commits

Reviewing files that changed from the base of the PR and between 1717429 and 22d8226.

📒 Files selected for processing (8)
  • apps/job/models/costing.py
  • apps/timesheet/management/commands/create_leave_entries.py
  • apps/timesheet/management/commands/create_overtime_entries.py
  • apps/timesheet/management/commands/reclassify_overtime_entries.py
  • apps/timesheet/tests/test_manual_time_entry_commands.py
  • apps/workflow/api/xero/payroll.py
  • apps/workflow/tests/test_xero_payroll_leave.py
  • mypy-baseline.txt
💤 Files with no reviewable changes (1)
  • mypy-baseline.txt
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/workflow/tests/test_xero_payroll_leave.py
  • apps/workflow/api/xero/payroll.py

Comment on lines +39 to +46
self.staff = Staff.objects.create_user(
email="leave-taker@example.com",
password="testpass",
first_name="Leave",
last_name="Taker",
is_workshop_staff=True,
base_wage_rate=Decimal("30.00"),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Silence the Ruff hardcoded-password hint on the test fixture.

Static analysis flags password="testpass" as a possible hardcoded password (S106). This is a test-only value, but if S106 is enforced on this path, it will fail CI lint. Add a # noqa: S106 on the line, or move the literal into a named test constant.

🧰 Tools
🪛 Ruff (0.16.0)

[error] 41-41: Possible hardcoded password assigned to argument: "password"

(S106)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/timesheet/tests/test_manual_time_entry_commands.py` around lines 39 -
46, Suppress Ruff S106 for the test fixture password by adding an inline noqa
marker to the password assignment in the Staff.objects.create_user call, or
replace the literal with a named test constant.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants