Quality-of-life enhancements: CSV import/export, opening balances, interest rates, mobile UX
- CSV import/export for Units, Projects, and Vendors with match-on-name/number upsert - Cash Flow report toggle for Cash Only vs Cash + Investments - Per-account and bulk opening balance setting with as-of date - Interest rate field on normal accounts with estimated monthly/annual interest display - Mobile sidebar auto-close on navigation - Shared CSV parsing/export utility extracted to frontend/src/utils/csv.ts DB migration needed for existing tenants: ALTER TABLE accounts ADD COLUMN IF NOT EXISTS interest_rate DECIMAL(6,4); Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -55,8 +55,8 @@ export class AccountsService {
|
||||
}
|
||||
|
||||
const insertResult = await this.tenant.query(
|
||||
`INSERT INTO accounts (account_number, name, description, account_type, fund_type, parent_account_id, is_1099_reportable)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
`INSERT INTO accounts (account_number, name, description, account_type, fund_type, parent_account_id, is_1099_reportable, interest_rate)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id`,
|
||||
[
|
||||
dto.accountNumber,
|
||||
@@ -66,6 +66,7 @@ export class AccountsService {
|
||||
dto.fundType,
|
||||
dto.parentAccountId || null,
|
||||
dto.is1099Reportable || false,
|
||||
dto.interestRate || null,
|
||||
],
|
||||
);
|
||||
const accountId = Array.isArray(insertResult[0]) ? insertResult[0][0].id : insertResult[0].id;
|
||||
@@ -172,6 +173,7 @@ export class AccountsService {
|
||||
if (dto.is1099Reportable !== undefined) { sets.push(`is_1099_reportable = $${idx++}`); params.push(dto.is1099Reportable); }
|
||||
if (dto.isActive !== undefined) { sets.push(`is_active = $${idx++}`); params.push(dto.isActive); }
|
||||
if (dto.isPrimary !== undefined) { sets.push(`is_primary = $${idx++}`); params.push(dto.isPrimary); }
|
||||
if (dto.interestRate !== undefined) { sets.push(`interest_rate = $${idx++}`); params.push(dto.interestRate); }
|
||||
|
||||
if (!sets.length) return account;
|
||||
|
||||
@@ -204,7 +206,30 @@ export class AccountsService {
|
||||
return this.findOne(id);
|
||||
}
|
||||
|
||||
async adjustBalance(id: string, dto: { targetBalance: number; asOfDate: string; memo?: string }) {
|
||||
async setOpeningBalance(id: string, dto: { targetBalance: number; asOfDate: string; memo?: string }) {
|
||||
return this.adjustBalance(id, dto, 'opening_balance');
|
||||
}
|
||||
|
||||
async bulkSetOpeningBalances(dto: { asOfDate: string; entries: { accountId: string; targetBalance: number }[] }) {
|
||||
let processed = 0, skipped = 0;
|
||||
const errors: string[] = [];
|
||||
|
||||
for (const entry of dto.entries) {
|
||||
try {
|
||||
const result = await this.setOpeningBalance(entry.accountId, {
|
||||
targetBalance: entry.targetBalance,
|
||||
asOfDate: dto.asOfDate,
|
||||
});
|
||||
if (result.message === 'No adjustment needed') skipped++;
|
||||
else processed++;
|
||||
} catch (err: any) {
|
||||
errors.push(`${entry.accountId}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
return { processed, skipped, errors };
|
||||
}
|
||||
|
||||
async adjustBalance(id: string, dto: { targetBalance: number; asOfDate: string; memo?: string }, entryType = 'adjustment') {
|
||||
const account = await this.findOne(id);
|
||||
|
||||
// Get current balance for this account using trial balance logic
|
||||
@@ -282,16 +307,20 @@ export class AccountsService {
|
||||
const equityDebit = targetCredit > 0 ? targetCredit : 0;
|
||||
const equityCredit = targetDebit > 0 ? targetDebit : 0;
|
||||
|
||||
const memo = dto.memo || `Balance adjustment to ${dto.targetBalance}`;
|
||||
const defaultMemo = entryType === 'opening_balance'
|
||||
? `Opening balance for ${account.name}`
|
||||
: `Balance adjustment to ${dto.targetBalance}`;
|
||||
const memo = dto.memo || defaultMemo;
|
||||
|
||||
// Create journal entry
|
||||
const jeRows = await this.tenant.query(
|
||||
`INSERT INTO journal_entries (entry_date, description, entry_type, fiscal_period_id, is_posted, posted_at, created_by)
|
||||
VALUES ($1, $2, 'adjustment', $3, true, NOW(), $4)
|
||||
VALUES ($1, $2, $3, $4, true, NOW(), $5)
|
||||
RETURNING *`,
|
||||
[
|
||||
dto.asOfDate,
|
||||
memo,
|
||||
entryType,
|
||||
fiscalPeriodId,
|
||||
'00000000-0000-0000-0000-000000000000',
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user