Skip to content

Commit f288c0d

Browse files
authored
Feature: Finance Dashboard & Report Pages (#7702)
## What Changed <!-- Short summary - what and why (not how) --> This PR introduces a new Finance Dashboard module using Slim 4 MVC architecture, providing a centralized hub for financial operations and tax year reporting. ## Changes ### New Finance Module (`/finance/`) - **Dashboard** with YTD metrics (payments, pledges, donor families, payment count) - **Tax Year Reporting Checklist** to guide year-end financial workflows - **Quick Actions** for common tasks (Create Deposit, Add Pledge, Add Payment, Generate Reports) - **Reports Index** with organized categories (Tax & Giving, Pledge, Deposit, Membership) - **Recent Deposits** table filtered to current fiscal year ### Architecture - Slim 4 MVC structure matching `/admin/` pattern - `FinanceRoleAuthMiddleware` updated to allow admin OR finance permission - PhpRenderer for server-side views - FinancialService extended with dashboard data methods ### Reusable Components - **system-settings-panel** webpack component for embedding SystemConfig settings on any page - **_finance.scss** with metric card gradients, hover effects, and responsive styles ### UX Improvements - **PledgeEditor.php**: Color-coded mode indicators to distinguish Pledge vs Payment entry - **Menu**: Renamed "Deposit" to "Finance" with Dashboard as first item ### Testing - Cypress tests for dashboard and reports index pages - Access control tests using new `nofinance` test user (judith.matthews) - Integration tests for navigation flows ## Type <!-- Check one --> - [x] ✨ Feature - [ ] 🐛 Bug fix - [ ] ♻️ Refactor - [x] 🏗️ Build/Infrastructure - [ ] 🔒 Security ## Screenshots <!-- Only for UI changes - drag & drop images here --> <img width="2191" height="1181" alt="image" src="https://github.com/user-attachments/assets/ee390b98-14fc-48dc-b231-922249287607" /> <img width="1941" height="934" alt="image" src="https://github.com/user-attachments/assets/ab389e7c-ab53-4983-bb07-9b15d52c69a4" /> ## Security Check <!-- Only check if applicable --> - [ ] Introduces new input validation - [ ] Modifies authentication/authorization - [ ] Affects data privacy/GDPR ### Code Quality - [x] Database: Propel ORM only, no raw SQL - [ ] No deprecated attributes (align, valign, nowrap, border, cellpadding, cellspacing, bgcolor) - [ ] Bootstrap CSS classes used - [x] All CSS bundled via webpack ## Pre-Merge - [x] Tested locally - [ ] No new warnings - [ ] Build passes - [ ] Backward compatible (or migration documented)
2 parents fe097f8 + 39c5b5b commit f288c0d

28 files changed

+2564
-59
lines changed

.github/copilot-instructions.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ Routing & middleware
3232
- Routes are prefixed with `/admin/api/` when accessed from frontend
3333
- Use kebab-case for endpoint names (e.g., `/delete-all`)
3434
- AdminRoleAuthMiddleware is applied at the router level
35+
- **Finance Module** (consolidated at `/finance/`):
36+
- Entry point: `src/finance/index.php` with Slim 4 app
37+
- Routes in `src/finance/routes/` (dashboard.php, reports.php)
38+
- Views in `src/finance/views/` with PhpRenderer
39+
- Examples: `/finance/` (dashboard), `/finance/reports`
40+
- Use FinanceRoleAuthMiddleware for security (allows admin OR finance permission)
41+
- Menu entry in `src/ChurchCRM/Config/Menu/Menu.php` under "Finance"
3542
- **Deprecated locations** (DO NOT USE):
3643
- `src/v2/routes/admin/` - REMOVED (admin routes consolidated to `/admin/system/`)
3744
- `src/api/routes/system/` - Legacy admin APIs (no new files here)
@@ -464,6 +471,7 @@ describe('Feature X', () => {
464471
**Available Commands:**
465472
- `cy.setupAdminSession()` - Authenticates as admin (reads `admin.username`, `admin.password` from config)
466473
- `cy.setupStandardSession()` - Authenticates as standard user (reads `standard.username`, `standard.password` from config)
474+
- `cy.setupNoFinanceSession()` - Authenticates as user without finance permission (reads `nofinance.username`, `nofinance.password` from config)
467475
- `cy.typeInQuill()` - Rich text editor input
468476

469477
**Credentials Configuration:**
@@ -474,6 +482,8 @@ env: {
474482
'admin.password': 'changeme',
475483
'standard.username': 'tony.wade@example.com',
476484
'standard.password': 'basicjoe',
485+
'nofinance.username': 'judith.matthews@example.com',
486+
'nofinance.password': 'noMoney$',
477487
}
478488
```
479489
- DO NOT hardcode credentials in test files
@@ -598,6 +608,7 @@ Before committing code changes, verify:
598608
| `src/ChurchCRM/model/ChurchCRM/` | Propel ORM generated classes (don't edit) |
599609
| `src/api/` | REST API entry point + routes |
600610
| `src/admin/routes/api/` | Admin-only API endpoints (NEW - use this for admin APIs) |
611+
| `src/finance/` | Finance module (Slim 4 MVC) - dashboard, reports |
601612
| `src/Include/` | Utility functions, helpers, Config.php |
602613
| `src/locale/` | i18n/translation strings |
603614
| `src/skin/v2/` | Compiled CSS/JS from Webpack |
@@ -765,6 +776,6 @@ This ensures security vulnerabilities are not publicly disclosed and directs rep
765776

766777
---
767778

768-
Last updated: November 9, 2025
779+
Last updated: December 1, 2025
769780

770781
```

cypress/e2e/api/private/admin/private.admin.user.settings.spec.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
/// <reference types="cypress" />
22

3+
// Use user 99 (amanda.black) for these tests to avoid conflicts with
4+
// user 95 (judith.matthews) which is used for nofinance session tests
35
describe("API Private Admin User", () => {
46
it("Reset User failed logins", () => {
57
cy.makePrivateAdminAPICall(
68
"POST",
7-
"/api/user/95/login/reset",
9+
"/api/user/99/login/reset",
810
null,
911
200,
1012
);
@@ -13,7 +15,7 @@ describe("API Private Admin User", () => {
1315
it("Reset User Password", () => {
1416
cy.makePrivateAdminAPICall(
1517
"POST",
16-
"/api/user/95/password/reset",
18+
"/api/user/99/password/reset",
1719
null,
1820
200,
1921
);
@@ -22,7 +24,7 @@ describe("API Private Admin User", () => {
2224
it("DisableTwoFactor", () => {
2325
cy.makePrivateAdminAPICall(
2426
"POST",
25-
"/api/user/95/disableTwoFactor",
27+
"/api/user/99/disableTwoFactor",
2628
null,
2729
200,
2830
);

cypress/e2e/ui/admin/admin.user.spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ describe("Admin User Password", () => {
99
});
1010

1111
it("Admin Change password", () => {
12-
cy.visit("v2/user/95/changePassword");
13-
cy.contains("Change Password: Judith Kennedy");
12+
cy.visit("v2/user/99/changePassword");
13+
cy.contains("Change Password: Amanda Black");
1414
cy.get("#NewPassword1").type("new-user-password");
1515
cy.get("#NewPassword2").type("new-user-password");
1616
cy.get("form:nth-child(2)").submit();
17-
cy.url().should("contain", "v2/user/95/changePassword");
17+
cy.url().should("contain", "v2/user/99/changePassword");
1818
cy.contains("Password Change Successful");
1919
});
2020

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/// <reference types="cypress" />
2+
3+
/**
4+
* Finance Dashboard Tests
5+
*
6+
* Tests for the new /finance/ module with Slim 4 MVC structure.
7+
* The finance dashboard provides:
8+
* - YTD payment and pledge metrics
9+
* - Tax year reporting checklist
10+
* - Quick actions for deposits and reports
11+
* - Recent deposits list
12+
* - Donation funds overview
13+
*/
14+
15+
describe("Finance Dashboard", () => {
16+
beforeEach(() => {
17+
cy.setupAdminSession();
18+
});
19+
20+
it("should load the finance dashboard", () => {
21+
cy.visit("/finance/");
22+
cy.contains("Finance Dashboard");
23+
cy.contains("Fiscal Year");
24+
});
25+
26+
it("should display YTD metrics cards", () => {
27+
cy.visit("/finance/");
28+
29+
// Check for the 4 metric cards
30+
cy.contains("YTD Payments");
31+
cy.contains("YTD Pledges");
32+
cy.contains("Donor Families");
33+
cy.contains("Total Payments");
34+
});
35+
36+
it("should display Quick Actions section", () => {
37+
cy.visit("/finance/");
38+
39+
cy.contains("Quick Actions");
40+
cy.contains("Create Deposit");
41+
cy.contains("Add Payment");
42+
cy.contains("Generate Reports");
43+
});
44+
45+
it("should navigate to deposits page from Create Deposit button", () => {
46+
cy.visit("/finance/");
47+
48+
// Find and click the Create Deposit button
49+
cy.contains("a", "Create Deposit").click();
50+
cy.url().should("contain", "FindDepositSlip.php");
51+
cy.contains("Deposit Listing");
52+
});
53+
54+
it("should navigate to reports page from Generate Reports button", () => {
55+
cy.visit("/finance/");
56+
57+
// Find and click the Generate Reports button
58+
cy.contains("a", "Generate Reports").click();
59+
cy.url().should("contain", "/finance/reports");
60+
cy.contains("Financial Reports");
61+
});
62+
63+
it("should display Tax Year Reporting Checklist", () => {
64+
cy.visit("/finance/");
65+
66+
cy.contains("Tax Year Reporting Checklist");
67+
cy.contains("Close All Deposits");
68+
cy.contains("Review Donation Funds");
69+
cy.contains("Church Information");
70+
cy.contains("Tax Report Verbiage");
71+
cy.contains("Generate Tax Statements");
72+
});
73+
74+
it("should display Recent Deposits section", () => {
75+
cy.visit("/finance/");
76+
77+
cy.contains("Recent Deposits");
78+
cy.contains("View All");
79+
80+
// Check table headers if deposits exist
81+
cy.get("body").then(($body) => {
82+
if ($body.find("table.table-hover").length > 0) {
83+
cy.contains("th", "ID");
84+
cy.contains("th", "Date");
85+
cy.contains("th", "Type");
86+
cy.contains("th", "Status");
87+
}
88+
});
89+
});
90+
91+
it("should display Deposit Statistics sidebar", () => {
92+
cy.visit("/finance/");
93+
94+
cy.contains("Deposit Statistics");
95+
cy.contains("Total Deposits:");
96+
cy.contains("Open Deposits:");
97+
cy.contains("Closed Deposits:");
98+
});
99+
100+
it("should display Donation Funds sidebar", () => {
101+
cy.visit("/finance/");
102+
103+
cy.contains("Donation Funds");
104+
});
105+
106+
it("should link to Donation Fund Editor from Manage Funds button", () => {
107+
cy.visit("/finance/");
108+
109+
// Admin should see Manage Funds link
110+
cy.contains("a", "Manage Funds").click();
111+
cy.url().should("contain", "DonationFundEditor.php");
112+
});
113+
114+
it("should navigate to settings from Church Information checklist item", () => {
115+
cy.visit("/finance/");
116+
117+
// Find the Settings button in the Church Information row
118+
cy.contains("Church Information")
119+
.parents(".list-group-item")
120+
.find("a")
121+
.contains("Settings")
122+
.click();
123+
124+
cy.url().should("contain", "SystemSettings.php");
125+
});
126+
127+
it("should link deposits checklist to FindDepositSlip", () => {
128+
cy.visit("/finance/");
129+
130+
// Find the View button in the Close All Deposits row
131+
cy.contains("Close All Deposits")
132+
.parents(".list-group-item")
133+
.find("a")
134+
.contains("View")
135+
.click();
136+
137+
cy.url().should("contain", "FindDepositSlip.php");
138+
});
139+
});
140+
141+
describe("Finance Dashboard - Standard User Access", () => {
142+
beforeEach(() => {
143+
cy.setupStandardSession();
144+
});
145+
146+
it("should allow standard users with finance permission to access the dashboard", () => {
147+
// The standard test user (tony.wade) has finance permissions enabled in demo database
148+
cy.visit("/finance/");
149+
150+
// Should be able to see the dashboard
151+
cy.get("h1").should("contain", "Finance Dashboard");
152+
153+
// Metrics should be visible
154+
cy.get(".finance-metric-card").should("have.length.at.least", 3);
155+
});
156+
});
157+
158+
describe("Finance Dashboard - No Finance Permission", () => {
159+
beforeEach(() => {
160+
cy.setupNoFinanceSession();
161+
});
162+
163+
it("should deny access to users without finance permission", () => {
164+
// User judith.matthews has no finance permission
165+
cy.visit("/finance/", { failOnStatusCode: false });
166+
167+
// Should be redirected to access-denied page
168+
cy.url().should("include", "/v2/access-denied");
169+
cy.url().should("include", "role=Finance");
170+
});
171+
172+
it("should deny access to finance reports for users without finance permission", () => {
173+
cy.visit("/finance/reports", { failOnStatusCode: false });
174+
175+
// Should be redirected to access-denied page
176+
cy.url().should("include", "/v2/access-denied");
177+
cy.url().should("include", "role=Finance");
178+
});
179+
});

cypress/e2e/ui/finance/finance.deposits.spec.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,30 @@ describe("Finance Deposits", () => {
1111
cy.contains("Envelope Manager");
1212
});
1313

14+
it("Navigate to deposits from Finance Dashboard", () => {
15+
cy.visit("/finance/");
16+
cy.contains("Finance Dashboard");
17+
18+
// Click Create Deposit from Quick Actions
19+
cy.contains("a", "Create Deposit").click();
20+
cy.url().should("contain", "FindDepositSlip.php");
21+
cy.contains("Deposit Listing");
22+
});
23+
24+
it("Navigate to deposits from Finance Menu", () => {
25+
cy.visit("/finance/");
26+
27+
// Use the View All link in Recent Deposits section
28+
cy.contains("Recent Deposits")
29+
.parents(".card")
30+
.find("a")
31+
.contains("View All")
32+
.click();
33+
34+
cy.url().should("contain", "FindDepositSlip.php");
35+
cy.contains("Deposit Listing");
36+
});
37+
1438
it("Create a new Deposit without comment", () => {
1539
cy.visit("/FindDepositSlip.php");
1640
cy.get("#depositComment").clear();

0 commit comments

Comments
 (0)