Phase 3: Optimize & clean up — unified projects, account enhancements, new tenant fix
- Unify reserve_components + capital_projects into single projects model with full CRUD backend and new Projects page frontend - Rewrite Capital Planning to read from unified projects/planning endpoint; add empty state directing users to Projects page when no planning items exist - Add default designation to assessment groups with auto-set on first creation; units now require an assessment group (pre-populated with default) - Add primary account designation (one per fund type) and balance adjustment via journal entries against equity offset accounts (3000/3100) - Add computed investment fields (interest earned, maturity value, days remaining) with PostgreSQL date arithmetic fix for DATE - DATE integer result - Restructure sidebar: investments in Accounts tab, Year-End under Reports, Planning section with Projects and Capital Planning - Fix new tenant creation seeding unwanted default chart of accounts — new tenants now start with a blank slate Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,9 @@ export class AssessmentGroupsController {
|
||||
@Get('summary')
|
||||
getSummary() { return this.service.getSummary(); }
|
||||
|
||||
@Get('default')
|
||||
getDefault() { return this.service.getDefault(); }
|
||||
|
||||
@Get(':id')
|
||||
findOne(@Param('id') id: string) { return this.service.findOne(id); }
|
||||
|
||||
@@ -24,4 +27,7 @@ export class AssessmentGroupsController {
|
||||
|
||||
@Put(':id')
|
||||
update(@Param('id') id: string, @Body() dto: any) { return this.service.update(id, dto); }
|
||||
|
||||
@Put(':id/set-default')
|
||||
setDefault(@Param('id') id: string) { return this.service.setDefault(id); }
|
||||
}
|
||||
|
||||
@@ -6,10 +6,6 @@ export class AssessmentGroupsService {
|
||||
constructor(private tenant: TenantService) {}
|
||||
|
||||
async findAll() {
|
||||
// Normalize all income calculations to monthly equivalent
|
||||
// monthly: amount * units (already monthly)
|
||||
// quarterly: amount/3 * units (convert to monthly)
|
||||
// annual: amount/12 * units (convert to monthly)
|
||||
return this.tenant.query(`
|
||||
SELECT ag.*,
|
||||
(SELECT COUNT(*) FROM units u WHERE u.assessment_group_id = ag.id) as actual_unit_count,
|
||||
@@ -39,17 +35,38 @@ export class AssessmentGroupsService {
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
async create(dto: any) {
|
||||
async getDefault() {
|
||||
const rows = await this.tenant.query(
|
||||
`INSERT INTO assessment_groups (name, description, regular_assessment, special_assessment, unit_count, frequency)
|
||||
VALUES ($1, $2, $3, $4, $5, $6) RETURNING *`,
|
||||
[dto.name, dto.description || null, dto.regularAssessment || 0, dto.specialAssessment || 0, dto.unitCount || 0, dto.frequency || 'monthly'],
|
||||
'SELECT * FROM assessment_groups WHERE is_default = true AND is_active = true LIMIT 1',
|
||||
);
|
||||
return rows.length ? rows[0] : null;
|
||||
}
|
||||
|
||||
async create(dto: any) {
|
||||
const existingGroups = await this.tenant.query('SELECT COUNT(*) as cnt FROM assessment_groups');
|
||||
const isFirstGroup = parseInt(existingGroups[0].cnt) === 0;
|
||||
const shouldBeDefault = dto.isDefault || isFirstGroup;
|
||||
|
||||
if (shouldBeDefault) {
|
||||
await this.tenant.query('UPDATE assessment_groups SET is_default = false WHERE is_default = true');
|
||||
}
|
||||
|
||||
const rows = await this.tenant.query(
|
||||
`INSERT INTO assessment_groups (name, description, regular_assessment, special_assessment, unit_count, frequency, is_default)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`,
|
||||
[dto.name, dto.description || null, dto.regularAssessment || 0, dto.specialAssessment || 0,
|
||||
dto.unitCount || 0, dto.frequency || 'monthly', shouldBeDefault],
|
||||
);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
async update(id: string, dto: any) {
|
||||
await this.findOne(id);
|
||||
|
||||
if (dto.isDefault === true) {
|
||||
await this.tenant.query('UPDATE assessment_groups SET is_default = false WHERE is_default = true');
|
||||
}
|
||||
|
||||
const sets: string[] = [];
|
||||
const params: any[] = [];
|
||||
let idx = 1;
|
||||
@@ -61,6 +78,7 @@ export class AssessmentGroupsService {
|
||||
if (dto.unitCount !== undefined) { sets.push(`unit_count = $${idx++}`); params.push(dto.unitCount); }
|
||||
if (dto.isActive !== undefined) { sets.push(`is_active = $${idx++}`); params.push(dto.isActive); }
|
||||
if (dto.frequency !== undefined) { sets.push(`frequency = $${idx++}`); params.push(dto.frequency); }
|
||||
if (dto.isDefault !== undefined) { sets.push(`is_default = $${idx++}`); params.push(dto.isDefault); }
|
||||
|
||||
if (!sets.length) return this.findOne(id);
|
||||
|
||||
@@ -74,6 +92,16 @@ export class AssessmentGroupsService {
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
async setDefault(id: string) {
|
||||
await this.findOne(id);
|
||||
await this.tenant.query('UPDATE assessment_groups SET is_default = false WHERE is_default = true');
|
||||
await this.tenant.query(
|
||||
'UPDATE assessment_groups SET is_default = true, updated_at = NOW() WHERE id = $1',
|
||||
[id],
|
||||
);
|
||||
return this.findOne(id);
|
||||
}
|
||||
|
||||
async getSummary() {
|
||||
const rows = await this.tenant.query(`
|
||||
SELECT
|
||||
|
||||
Reference in New Issue
Block a user