Approvals — Services¶
Overview¶
The services layer is the core workflow layer of the approvals app.
It is responsible for:
- computing approval assignees
- resolving delegation relationships
- building inbox visibility rules
- deciding approvals
- forwarding and unforwarding approvals
- creating and disabling delegations
- applying post-decision side effects
- writing audit records for workflow actions
The services layer should contain the business rules of the approvals system.
It sits between:
- views and serializers
- models
- related domain apps such as timesheets
- notifications
Service modules¶
services/approvals.py¶
Contains the main approval workflow actions.
Primary responsibilities:
- decide an approval
- forward an approval
- unforward an approval
- determine whether a user may forward an approval
- expose lightweight workflow helpers such as approval labeling
Typical functions:
decide_approval(...)forward_approval(...)unforward_approval(...)can_forward(...)approval_label(...)week_label(...)
This module owns the main write-side workflow for active approvals.
services/delegations.py¶
Contains delegation lifecycle logic.
Primary responsibilities:
- validate delegation creation inputs
- create delegations
- replace existing active delegations for the same scope
- disable delegations
- notify relevant users when delegation changes
Typical functions:
create_delegation(...)disable_delegation(...)validate_delegation_users(...)
This module owns delegation workflow behavior.
services/assignees.py¶
Contains assignee computation and delegation-aware approver resolution.
Primary responsibilities:
- compute who should be assigned to an approval
- refresh assignment rows
- resolve delegates for a manager
- resolve delegators for a user
- resolve effective approvers for timesheet approvals
Typical functions:
compute_assignees_for_approval(...)refresh_assignees(...)delegate_user_ids_for_manager(...)delegator_user_ids_for_user(...)approver_users_for_timesheet(...)
This module defines who should act on an approval.
services/inbox.py¶
Contains inbox visibility query logic.
Primary responsibilities:
- build inbox querysets for a user
- include direct assignees
- include delegated visibility
- include forwarded approvals
- enforce forwarding visibility rules
Typical functions:
inbox_queryset_for_user(...)
This module owns the read-model visibility logic for approval inboxes.
services/handlers.py¶
Contains post-decision side effects.
Primary responsibilities:
- apply business-specific target side effects after a decision
- keep target updates separate from generic approval workflow
Typical functions:
apply_decision_side_effects(...)
Currently supports:
- timesheet approval/rejection updates
This module is the boundary between generic approval workflow and target-specific domain behavior.
services/audit.py¶
Contains approval-specific audit helpers.
Primary responsibilities:
- write workflow audit entries
- capture user snapshots
- capture delegation context when someone acts on behalf of another user
Typical functions:
write_audit(...)user_snapshot(...)delegation_audit_for_decision(...)
This module supports workflow traceability.
services/helpers.py¶
Contains shared presentation-oriented helper functions used across services and serializers.
Primary responsibilities:
- build stable user labels
- build lightweight user meta payloads
Typical functions:
user_label(...)user_meta(...)
This module helps keep helper logic centralized instead of duplicated across views and serializers.
Core workflow flows¶
Approval decision flow¶
The approval decision workflow is the most important service path.
Main steps¶
- validate approval is still pending
- capture delegation audit context if actor is acting on behalf of someone else
- mark approval approved or rejected
- apply target-specific side effects
- notify affected users if needed
- return updated approval
flowchart TD
A[decide_approval] --> B{Approval pending?}
B -->|no| C[Raise validation error]
B -->|yes| D[Capture delegation audit context]
D --> E{Decision}
E -->|approve| F[mark_approved]
E -->|reject| G[mark_rejected]
F --> H[apply_decision_side_effects]
G --> H
H --> I[Send notifications if applicable]
I --> J[Return updated approval]
¶
flowchart TD
A[decide_approval] --> B{Approval pending?}
B -->|no| C[Raise validation error]
B -->|yes| D[Capture delegation audit context]
D --> E{Decision}
E -->|approve| F[mark_approved]
E -->|reject| G[mark_rejected]
F --> H[apply_decision_side_effects]
G --> H
H --> I[Send notifications if applicable]
I --> J[Return updated approval]
Forwarding flow¶
Forwarding allows an approval to be sent to another active org member.
Main steps¶
- ensure approval is pending
- ensure actor is allowed to forward
- ensure recipient is an active org member
- replace previous forwarded assignee if needed
- create forwarded assignee row
- append forward metadata to payload
- notify recipient
- return updated approval
flowchart TD
A[forward_approval] --> B{Approval pending?}
B -->|no| C[Raise validation error]
B -->|yes| D{Actor can forward?}
D -->|no| E[Raise permission error]
D -->|yes| F{Recipient active in org?}
F -->|no| G[Raise validation error]
F -->|yes| H[Remove previous forwarded assignees]
H --> I[Create or keep forwarded assignee]
I --> J[Append forward payload audit]
J --> K[Send forwarded notification]
K --> L[Return updated approval]
¶
flowchart TD
A[forward_approval] --> B{Approval pending?}
B -->|no| C[Raise validation error]
B -->|yes| D{Actor can forward?}
D -->|no| E[Raise permission error]
D -->|yes| F{Recipient active in org?}
F -->|no| G[Raise validation error]
F -->|yes| H[Remove previous forwarded assignees]
H --> I[Create or keep forwarded assignee]
I --> J[Append forward payload audit]
J --> K[Send forwarded notification]
K --> L[Return updated approval]Unforwarding flow¶
Unforwarding removes a forwarded assignment from an approval.
Main steps¶
- ensure approval is pending
- ensure actor is allowed to unforward
- remove forwarded assignee row
- write audit event
- notify recipient that the forward was removed
- return updated approval
flowchart TD
A[unforward_approval] --> B{Approval pending?}
B -->|no| C[Raise validation error]
B -->|yes| D{Actor can unforward?}
D -->|no| E[Raise permission error]
D -->|yes| F[Delete forwarded assignee]
F -->|not found| G[Raise validation error]
F -->|deleted| H[Write audit]
H --> I[Send removal notification]
I --> J[Return updated approval]
Delegation creation flow¶
Delegation creation is a service-backed workflow, not serializer-owned logic.
Main steps¶
- normalize kind and timestamps
- validate users
- disable existing active delegation in same scope
- create new delegation row
- notify both parties
- return delegation
flowchart TD
A[create_delegation] --> B[Normalize kind and dates]
B --> C[Validate users and membership]
C --> D[Disable existing active delegation]
D --> E[Create new delegation]
E --> F[Send delegation created notifications]
F --> G[Return delegation]
Assignee computation flow¶
Assignee computation determines who should act on an approval.
Current behavior¶
For timesheet submission approvals: 1. find effective manager 2. include active delegates for that manager 3. exclude the engineer themselves 4. filter to active org members 5. preserve stable order 6. return assignee tuples
flowchart TD
A[compute_assignees_for_approval] --> B{Approval kind}
B -->|timesheet_submit| C[Find effective manager]
C --> D[Resolve delegates]
D --> E[Remove submitter]
E --> F[Filter active org members]
F --> G[Return assignee tuples]
B -->|other| H[Return empty list]
¶
flowchart TD
A[compute_assignees_for_approval] --> B{Approval kind}
B -->|timesheet_submit| C[Find effective manager]
C --> D[Resolve delegates]
D --> E[Remove submitter]
E --> F[Filter active org members]
F --> G[Return assignee tuples]
B -->|other| H[Return empty list]Inbox visibility flow¶
The inbox service defines which approvals a user sees.
Visibility includes¶
• directly assigned approvals
• approvals visible through delegation
• approvals forwarded to the user
Special rule¶
If an approval has been forwarded, only the forwarded assignee should see it.
flowchart TD
A[inbox_queryset_for_user] --> B[Base pending approvals]
B --> C[Annotate direct assignment]
C --> D[Annotate forward presence]
D --> E[Annotate forwarded_to_me]
E --> F{Include delegated?}
F -->|yes| G[Annotate delegated visibility]
F -->|no| H[Skip delegated annotation]
G --> I[Build visible filter]
H --> I
I --> J[Apply forwarded-only visibility rule]
J --> K[Return ordered queryset]
Side-effect handling¶
The approvals app is generic, but decisions may affect target objects.
That behavior is deliberately separated into services/handlers.py.
Current supported side effect¶
For timesheet_submit: • approval approved -> weekly timesheet becomes approved • approval rejected -> weekly timesheet becomes rejected
Why this matters¶
This keeps: • generic approval workflow • target-specific business rules
cleanly separated.
Service responsibilities¶
Services layer owns¶
• approval workflow actions
• delegation lifecycle
• assignee resolution
• inbox visibility logic
• post-decision side effects
• workflow audit metadata
• notification emission for approval events
Models layer owns¶
• persistence
• field validation
• status helper methods
Views layer owns¶
• HTTP request handling
• serializer invocation
• permission wiring
• response mapping
Data flow¶
flowchart TD
A[View] --> B[Serializer validation]
B --> C[Service]
C --> D[Models]
C --> E[Notifications]
C --> F[Related domain side effects]
D --> G[Updated state]
G --> H[Serializer output]
H --> I[Response]
Service error model¶
The service layer should raise domain-specific errors rather than HTTP responses.
Examples include: - permission errors - validation errors - workflow state errors
Views should translate these into: - 400 - 403 - 404 - 409
as appropriate.
This keeps services transport-agnostic.
Integration with other apps¶
The approvals services integrate with:
Timesheets¶
- approval side effects update
WeeklyTimesheet - assignee computation uses reporting structure / manager resolution
Notifications¶
Approval workflows emit notifications such as: - approval forwarded to user - forward removed - delegation created - delegation removed - timesheet approved - timesheet declined
Orgs¶
Approval permissions and assignee validation depend on active org membership.
Teams or management logic¶
Assignee computation may depend on external manager resolution logic.
What services do not do¶
Services do not: - expose HTTP responses - parse request query parameters - define serializer fields - define model schemas - own provider delivery logic - replace permissions
Those concerns belong to: - views - serializers - models - notifications - permissions
Design principles¶
Thin views, rich services¶
Views should call service functions rather than implement workflow inline.
Generic approval core, specific side-effect handlers¶
Approval state is generic, but target behavior is domain-specific.
The handlers layer bridges that safely.
Delegation-aware workflow¶
Delegation is a first-class service concern, not a UI-only feature.
It affects: - decision audit - inbox visibility - assignee resolution
Explicit workflow entry points¶
Actions such as decide, forward, and disable delegation should each have explicit service functions.
This improves: - readability - testability - consistency
Reusable read-model services¶
Inbox visibility and delegation enrichment should be built once in services, not reimplemented in views repeatedly.
Best practices¶
- call service functions for all approval workflow changes
- keep workflow decisions out of views and serializers
- raise domain errors from services
- centralize helper functions instead of duplicating them
- keep side effects isolated in handler modules
- keep assignee computation deterministic and explicit
- use notifications from service workflows, not from views
Future extensions¶
The service layer is well positioned to support: - multi-step approvals - escalation rules - SLA reminders - approval chains - approval reassignment - comment history - bulk approval actions - richer audit timelines
As the app grows, services can be further split into: - read services - write services - integration services
if needed.
Summary¶
The approvals services layer is the workflow engine of the app.
It provides the business behavior for: - deciding approvals - forwarding approvals - managing delegations - computing assignees - building inbox visibility - applying side effects - recording workflow context
This makes the approvals app: - reusable - testable - delegation-aware - easy to evolve