Skip to content

Add timesheet approval workflow (draft → submitted → approved/rejected) - #924

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1787580808-timesheet-approval
Open

devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1787580808-timesheet-approval

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an end-to-end approval workflow for work entries: owners submit entries, approvers approve/reject them, and approved entries become immutable.

State machine

draft ──submit──▶ submitted ──approve──▶ approved (terminal, immutable)
  ▲                    │
  └────submit──── rejected ◀──reject────┘
  • Owner-only: draft → submitted, rejected → submitted (resubmit)
  • Approver-only: submitted → approved, submitted → rejected
  • Any other transition → 409 with Cannot <action> work entry with status '<status>'
  • PUT/DELETE on an approved entry → 403 Approved entries cannot be modified

API changes

  • work_entries.status TEXT NOT NULL DEFAULT 'draft' (+ index); users.role TEXT NOT NULL DEFAULT 'member'
  • New endpoints (all under /api/work-entries):
    • POST /:id/submit — owner-scoped (user_email)
    • POST /:id/approve, POST /:id/reject — approver-only, operate on any user's entry
    • GET /pending-approvals — approver-only queue of all submitted entries across users (includes user_email, client_name)
  • requireApprover middleware in auth.js returns 403 unless users.role === 'approver'
  • Approver provisioning: on first login/user creation, role is set to approver if the email is listed in the APPROVER_EMAILS env var (comma-separated, case-insensitive). Existing user rows are the source of truth afterwards.
  • /api/auth/login and /api/auth/me now include role; existing work-entry responses now include status

Frontend

  • WorkEntriesPage: Status chip column, Submit button on draft/rejected rows, Edit/Delete disabled for approved rows
  • New PendingApprovalsPage (route /pending-approvals, nav item) shown only to approvers, with Approve/Reject actions
  • api/client.ts: submitWorkEntry, approveWorkEntry, rejectWorkEntry, getPendingApprovals

Reviewer manual checks

  • Set APPROVER_EMAILS in the backend env before starting it; log in with a listed email to see the Pending Approvals nav/view. Roles are assigned at user creation only — an existing member row stays member even if later added to APPROVER_EMAILS (DB is in-memory, so a backend restart resets this).
  • Verify a non-approver gets 403 on /api/work-entries/pending-approvals, /:id/approve, /:id/reject.
  • Verify an approved entry's Edit/Delete are disabled in the UI and rejected server-side.
  • Note: approvers can approve/reject their own submitted entries — no self-approval restriction was specified.

Tests

Backend jest suite extended (182 passing, coverage ~87%): all valid transitions, every invalid transition (409), role enforcement (403), approved-entry immutability, pending-approvals queue.

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/b9955f6683e243439999fc774759d8ad
Requested by: @sumitshatwara


Open in Devin Review

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@sonarqubecloud

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 1 additional finding.

Open in Devin Review

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

✅ End-to-end verification of the approval workflow

Ran the branch locally (backend with APPROVER_EMAILS=approver@example.com, Vite frontend) and drove the whole flow through the UI as a member and an approver, plus adversarial API checks. All assertions passed.

Approver queue → approve → member sees locked, approved entry

Approver pending queue
Member entry approved with Edit/Delete disabled

Status chip transitions (draft → submitted → rejected → resubmitted)

draft chip with Submit
submitted chip
rejected chip with Submit restored
resubmitted entry back in approver queue

Member is blocked from approver surfaces

No "Pending Approvals" nav item, and navigating directly to /pending-approvals redirects to /dashboard:

member redirected to dashboard

API guard checks (curl)
PUT    /api/work-entries/1 (approved, owner)          → 403
DELETE /api/work-entries/1 (approved, owner)          → 403
POST   /api/work-entries/2/approve (member)           → 403 {"error":"Approver role required"}
GET    /api/work-entries/pending-approvals (member)   → 403 {"error":"Approver role required"}
POST   /api/work-entries/2/submit (already submitted) → 409 {"error":"Cannot submit work entry with status 'submitted'"}
POST   /api/work-entries/1/approve (already approved) → 409 {"error":"Cannot approve work entry with status 'approved'"}

Not covered: multi-member queues, APPROVER_EMAILS case/multi-value parsing via UI, persistence (SQLite is in-memory by design).

Test recording: approval flow recording

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.

1 participant