Implement Phase 2 features: roles, assessment groups, budget import, Kanban

- Add hierarchical roles: SuperUser Admin (is_superadmin flag), Tenant Admin,
  Tenant User with separate /admin route and admin panel
- Add Assessment Groups module for property type-based assessment rates
  (SFHs, Condos, Estate Lots with different regular/special rates)
- Enhance Chart of Accounts: initial balance on create (with journal entry),
  archive/restore accounts, edit all fields including account number & fund type
- Add Budget CSV import with downloadable template and account mapping
- Add Capital Projects Kanban board with drag-and-drop between year columns,
  table/kanban view toggle, and PDF export via browser print
- Update seed data with assessment groups, second test user, superadmin flag
- Create repeatable reseed.sh script for clean database population
- Fix AgingReportPage Mantine v7 Table prop compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 14:28:46 -05:00
parent e0272f9d8a
commit 01502e07bc
29 changed files with 1792 additions and 142 deletions

View File

@@ -43,6 +43,9 @@ export class User {
@Column({ name: 'oauth_provider_id', nullable: true })
oauthProviderId: string;
@Column({ name: 'is_superadmin', default: false })
isSuperadmin: boolean;
@Column({ name: 'last_login_at', type: 'timestamptz', nullable: true })
lastLoginAt: Date;

View File

@@ -38,4 +38,25 @@ export class UsersService {
async updateLastLogin(id: string): Promise<void> {
await this.usersRepository.update(id, { lastLoginAt: new Date() });
}
async findAllUsers(): Promise<User[]> {
return this.usersRepository.find({
relations: ['userOrganizations', 'userOrganizations.organization'],
order: { createdAt: 'DESC' },
});
}
async findAllOrganizations(): Promise<any[]> {
const dataSource = this.usersRepository.manager.connection;
return dataSource.query(`
SELECT o.*,
(SELECT COUNT(*) FROM shared.user_organizations WHERE organization_id = o.id) as member_count
FROM shared.organizations o
ORDER BY o.created_at DESC
`);
}
async setSuperadmin(userId: string, isSuperadmin: boolean): Promise<void> {
await this.usersRepository.update(userId, { isSuperadmin });
}
}