team-expense-management-system: TEMS: The Expense App That Behaves Like a Money Pipeline
How a Spring Boot backend turns reimbursement into a controlled sequence of states, approvals, and Stripe-backed payouts.
- TEMS is built like a workflow engine, not a CRUD app, because each expense moves through explicit states and role-based gates.
- The backend separates expense state, approval state, and payment state so the system can enforce who acts next and when.
- Stripe is used as a reconciled payout layer, with metadata and a paid-once guard that make retries safe.
- The repo’s strongest signals are enterprise patterns like DTOs, service interfaces, audit logs, and role-specific controllers.
A Corporate Expense App, or a State Machine in Disguise?
The interesting thing about TEMS is not that it stores expenses. It is that it treats each expense as a controlled transition through a company. An employee submits. A manager approves. An admin pays. An audit trail records the whole path. That is a state machine with money attached.
That framing matters because it changes the unit of design. The repo is not optimizing for faster form entry. It is optimizing for enforceable sequence, visible ownership, and fewer ways to pay twice.
The Approval Chain Is the Product
The repo’s cleanest move is structural. When an expense is created, the backend also creates a matching approval record. That means the approval workflow is not an afterthought tacked onto the expense table. It is part of the same business event.
// Simplified flow based on ExpenseService and ApprovalService
Expense expense = expenseService.createExpense(request);
Approval approval = approvalService.createApproval(expense.getId());
// Manager acts on the approval record
approvalService.updateStatus(approvalId, APPROVED, managerId);
expenseService.updateStatus(expenseId, APPROVED);
// Admin can pay only after approval
paymentService.createCheckoutSession(expenseId);
That separation gives the codebase room to enforce responsibility. The expense record can say what was spent. The approval record can say who signed off. The payment layer can say whether the company has actually reimbursed it. Each answer lives in the right place.
Why that split matters
A lot of expense apps collapse everything into one record with a status field and a few nullable columns. That looks simpler until you need auditability, role boundaries, or payment retries. TEMS pushes those concerns apart early, which is why the workflow reads like a process rather than a pile of conditionals.
Why Stripe Shows Up in a Reimbursement App
The payment path is where the repo starts to feel real. Instead of inventing a custom payout flow, the backend creates a Stripe Checkout session and stores the expense ID in metadata so the external payment can be tied back to the internal record.
SessionCreateParams params = SessionCreateParams.builder()
.setMode(SessionCreateParams.Mode.PAYMENT)
.putMetadata("expenseId", String.valueOf(expenseId))
.build();
Session session = Session.create(params);
// Webhook reconciliation
if (expense.getPaidAt() != null) return;
expense.setPaidAt(Instant.now());
That last guard is the important bit. Webhooks retry. Networks fail. External systems repeat themselves. The paidAt check turns Stripe from a risky callback source into a safe reconciliation step. That is the difference between a demo and a system that behaves like production software.
| Concern | Generic CRUD app | TEMS | Why it matters |
|---|---|---|---|
| Expense creation | Insert a row with a status | Create the expense and a matching approval record | The workflow starts with ownership, not just storage |
| Approval handling | Update the same row | Track approval separately from the expense | Cleaner audit trail and role separation |
| Payment reconciliation | Mark as paid after a callback | Use Stripe metadata and a paidAt guard | Retries cannot double-pay |
| Role-specific views | One dashboard for everyone | Admin, manager, and employee flows | Each person sees only their responsibility |
| Auditability | Best effort logging | Audit-oriented entities and repositories | Financial actions stay traceable |
Security Is Layered Around Roles, Not Just Login
TEMS does not stop at authentication. It combines JWT filtering, BCrypt password hashing, and role-aware controllers for employees, managers, and admins. That means the system is organized around responsibilities, not around a single generic signed-in user.
That matters in a financial app. The person who submits an expense should not see the same actions as the person who approves it, and neither should expose the same tools as the person who pays it. TEMS reflects that hierarchy in the backend itself, which is where it belongs.
| Layer | What TEMS uses | What it protects |
|---|---|---|
| Authentication | JWT plus BCrypt | Identity and password safety |
| Authorization | Role-specific controllers | Who can approve, pay, or view |
| Transport of claims | JWT filter before auth filter | Consistent request identity |
| Data access | DTOs and service boundaries | Reduced leakage between workflows |
The Enterprise Signals Are in the Boring Parts
The repo’s maturity shows up in the unglamorous choices. DTOs keep API contracts separate from JPA entities. Service interfaces keep business logic off the controllers. Repositories keep data access tidy. Those are the patterns that let a project grow without collapsing into one giant application class.
The model layer also shows care with relational hygiene. Bidirectional relationships are handled carefully to avoid serialization problems, and audit logs hint that the project expects changes to matter after the fact. That is a serious signal in a money workflow.
| Pattern | What it buys | What it suggests |
|---|---|---|
| DTOs | Stable API boundaries | The author expects the frontend to evolve |
| Service interfaces | Replaceable business logic | The code is meant to be extended, not frozen |
| Audit logs | Traceability | The domain is treated as financially sensitive |
| Role-specific dashboards | Less UI overlap | Different users have different jobs |
| JPA relationship hygiene | Fewer serialization traps | The data model was designed with care |
What This Project Gets Right, and What It Still Reveals
TEMS gets the central thing right. It models reimbursement as a sequence of controlled states, not as a flat list of expenses. That single choice gives the whole system more shape, more safety, and more room to grow.
It also shows its early-stage edges. Placeholder secrets, demo-style seeding, and a few implementation shortcuts remind you that this is still a project in motion. But those rough edges do not cancel the architecture. They make the architecture easier to see.
| Strength | Constraint | What to conclude |
|---|---|---|
| Workflow-first design | Still early stage | The domain model is stronger than the polish |
| Stripe reconciliation | Needs careful secret handling | The payment path is realistic, not toy-like |
| Role-based backend | Frontend can still mature | The system is already thinking like a product |
| Audit and service layering | Some demo artifacts remain | The foundation is more serious than the packaging |