Skip to content

Commit ba9d021

Browse files
Feature/underfunded carryover (#18)
* added ENV to disable registration, also surfacing that error message in the UI * removed toBeBudgeted from budget as this is now on a month-by-month basis, added in necessary DB event logic to calculate carry-over of this amount as transactions / budgets change * fixed date diff logic * frontend changes for budget month's available amount * changing from typeorm sync to migrations - initial migration checks for existing user table and skips migration if it exists as this was a DB already generated by the sync * update docker init script * added migration for budget month availablity * cascade deletes, fixed tests
1 parent 1c45934 commit ba9d021

21 files changed

+250
-64
lines changed

backend/src/controllers/BudgetsController.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ export class BudgetsController extends Controller {
2424
{
2525
id: 'abc123',
2626
name: 'My Budget',
27-
toBeBudgeted: 0,
2827
accounts: [
2928
{
3029
id: 'def123',
@@ -46,7 +45,6 @@ export class BudgetsController extends Controller {
4645
{
4746
id: 'abc456',
4847
name: 'Another Budget',
49-
toBeBudgeted: 0,
5048
accounts: [],
5149
created: new Date('2011-10-05T14:48:00.000Z'),
5250
updated: new Date('2011-10-05T14:48:00.000Z'),
@@ -76,7 +74,6 @@ export class BudgetsController extends Controller {
7674
data: {
7775
id: 'abc123',
7876
name: 'My Budget',
79-
toBeBudgeted: 0,
8077
accounts: [],
8178
created: new Date('2011-10-05T14:48:00.000Z'),
8279
updated: new Date('2011-10-05T14:48:00.000Z'),
@@ -111,7 +108,6 @@ export class BudgetsController extends Controller {
111108
data: {
112109
id: 'abc123',
113110
name: 'My Budget',
114-
toBeBudgeted: 0,
115111
accounts: [],
116112
created: new Date('2011-10-05T14:48:00.000Z'),
117113
updated: new Date('2011-10-05T14:48:00.000Z'),
@@ -153,7 +149,6 @@ export class BudgetsController extends Controller {
153149
data: {
154150
id: 'abc123',
155151
name: 'My Budget',
156-
toBeBudgeted: 0,
157152
accounts: [],
158153
created: new Date('2011-10-05T14:48:00.000Z'),
159154
updated: new Date('2011-10-05T14:48:00.000Z'),
@@ -201,6 +196,7 @@ export class BudgetsController extends Controller {
201196
income: 0,
202197
activity: 0,
203198
budgeted: 0,
199+
available: 0,
204200
underfunded: 0,
205201
created: new Date('2011-10-05T14:48:00.000Z'),
206202
updated: new Date('2011-10-05T14:48:00.000Z'),
@@ -248,6 +244,7 @@ export class BudgetsController extends Controller {
248244
income: 0,
249245
activity: 0,
250246
budgeted: 0,
247+
available: 0,
251248
underfunded: 0,
252249
categories: [
253250
{

backend/src/controllers/UsersController.ts

+5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ export class UsersController extends Controller {
2222
},
2323
})
2424
public async createUser(@Body() requestBody: UserCreateRequest): Promise<UserResponse | ErrorResponse> {
25+
if (process.env.REGISTRATION_DISABLED?.match(/true|1/i)) {
26+
this.setStatus(400)
27+
return { message: 'Registration is disabled' }
28+
}
29+
2530
const { email } = requestBody
2631

2732
const emailCheck: User = await getRepository(User).findOne({ email })

backend/src/entities/Account.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class Account {
6666
/**
6767
* Belongs to a budget
6868
*/
69-
@ManyToOne(() => Budget, budget => budget.accounts)
69+
@ManyToOne(() => Budget, budget => budget.accounts, { onDelete: 'CASCADE' })
7070
budget: Promise<Budget>
7171

7272
/**

backend/src/entities/Budget.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { CategoryGroup } from './CategoryGroup'
66
import { Category } from './Category'
77
import { BudgetMonth } from './BudgetMonth'
88
import { Transaction } from './Transaction'
9+
import { Payee } from './Payee'
910

1011
@Entity('budgets')
1112
export class Budget {
@@ -18,12 +19,6 @@ export class Budget {
1819
@Column({ type: 'varchar' })
1920
name: string
2021

21-
@Column({
22-
type: 'int',
23-
default: 0,
24-
})
25-
toBeBudgeted: number = 0
26-
2722
@CreateDateColumn()
2823
created: Date
2924

@@ -33,7 +28,7 @@ export class Budget {
3328
/**
3429
* Belongs to a user
3530
*/
36-
@ManyToOne(() => User, user => user.budgets)
31+
@ManyToOne(() => User, user => user.budgets, { onDelete: 'CASCADE' })
3732
user: User
3833

3934
/**
@@ -66,6 +61,12 @@ export class Budget {
6661
@OneToMany(() => Transaction, transaction => transaction.budget)
6762
transactions: Promise<Transaction[]>
6863

64+
/**
65+
* Has many budget transactions
66+
*/
67+
@OneToMany(() => Payee, payee => payee.budget)
68+
payees: Promise<Payee[]>
69+
6970
public update(partial: DeepPartial<Budget>): Budget {
7071
Object.assign(this, partial)
7172
return this
@@ -76,15 +77,13 @@ export class Budget {
7677
id: this.id,
7778
userId: this.userId,
7879
name: this.name,
79-
toBeBudgeted: this.toBeBudgeted || 0,
8080
}
8181
}
8282

8383
public async toResponseModel(): Promise<BudgetModel> {
8484
return {
8585
id: this.id,
8686
name: this.name,
87-
toBeBudgeted: this.toBeBudgeted,
8887
accounts: await Promise.all((await this.accounts).map(account => account.toResponseModel())),
8988
created: this.created,
9089
updated: this.updated,

backend/src/entities/BudgetMonth.ts

+61-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,53 @@
11
import { BudgetMonthModel } from '../models/BudgetMonth'
2-
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, Index, OneToMany } from 'typeorm'
2+
import {
3+
Entity,
4+
PrimaryGeneratedColumn,
5+
Column,
6+
CreateDateColumn,
7+
ManyToOne,
8+
Index,
9+
OneToMany,
10+
AfterLoad,
11+
} from 'typeorm'
312
import { Budget } from './Budget'
413
import { CategoryMonth } from './CategoryMonth'
514

15+
export type BudgetMonthOriginalValues = {
16+
income: number
17+
budgeted: number
18+
activity: number
19+
available: number
20+
underfunded: number
21+
}
22+
23+
export class BudgetMonthCache {
24+
static cache: { [key: string]: BudgetMonthOriginalValues } = {}
25+
26+
public static get(id: string): BudgetMonthOriginalValues | null {
27+
if (BudgetMonthCache.cache[id]) {
28+
return BudgetMonthCache.cache[id]
29+
}
30+
31+
return {
32+
income: 0,
33+
budgeted: 0,
34+
activity: 0,
35+
available: 0,
36+
underfunded: 0,
37+
}
38+
}
39+
40+
public static set(budgetMonth: BudgetMonth) {
41+
BudgetMonthCache.cache[budgetMonth.id] = {
42+
income: budgetMonth.income,
43+
budgeted: budgetMonth.budgeted,
44+
activity: budgetMonth.activity,
45+
available: budgetMonth.available,
46+
underfunded: budgetMonth.underfunded,
47+
}
48+
}
49+
}
50+
651
@Entity('budget_months')
752
export class BudgetMonth {
853
@PrimaryGeneratedColumn('uuid')
@@ -34,6 +79,12 @@ export class BudgetMonth {
3479
})
3580
activity: number = 0
3681

82+
@Column({
83+
type: 'int',
84+
default: 0,
85+
})
86+
available: number = 0
87+
3788
@Column({
3889
type: 'int',
3990
default: 0,
@@ -49,15 +100,20 @@ export class BudgetMonth {
49100
/**
50101
* Belongs to a budget
51102
*/
52-
@ManyToOne(() => Budget, budget => budget.months)
103+
@ManyToOne(() => Budget, budget => budget.months, { onDelete: 'CASCADE' })
53104
budget: Promise<Budget>
54105

55106
/**
56-
* Has man category months
107+
* Has many category months
57108
*/
58109
@OneToMany(() => CategoryMonth, categoryMonth => categoryMonth.budgetMonth)
59110
categories: Promise<CategoryMonth[]>
60111

112+
@AfterLoad()
113+
private storeOriginalValues(): void {
114+
BudgetMonthCache.set(this)
115+
}
116+
61117
public getUpdatePayload() {
62118
return {
63119
id: this.id,
@@ -66,6 +122,7 @@ export class BudgetMonth {
66122
income: this.income,
67123
budgeted: this.budgeted,
68124
activity: this.activity,
125+
available: this.available,
69126
underfunded: this.underfunded,
70127
}
71128
}
@@ -78,6 +135,7 @@ export class BudgetMonth {
78135
income: this.income,
79136
budgeted: this.budgeted,
80137
activity: this.activity,
138+
available: this.available,
81139
underfunded: this.underfunded,
82140
created: this.created,
83141
updated: this.updated,

backend/src/entities/Category.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class Category {
4848
/**
4949
* Belongs to a category group
5050
*/
51-
@ManyToOne(() => CategoryGroup, categoryGroup => categoryGroup.categories)
51+
@ManyToOne(() => CategoryGroup, categoryGroup => categoryGroup.categories, { onDelete: 'CASCADE' })
5252
categoryGroup: CategoryGroup
5353

5454
/**

backend/src/entities/CategoryGroup.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export class CategoryGroup {
3535
/**
3636
* Belongs to a budget
3737
*/
38-
@ManyToOne(() => Budget, budget => budget.categoryGroups)
38+
@ManyToOne(() => Budget, budget => budget.categoryGroups, { onDelete: 'CASCADE' })
3939
budget: Budget
4040

4141
/**

backend/src/entities/CategoryMonth.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class CategoryMonth {
7777
/**
7878
* Belongs to a category
7979
*/
80-
@ManyToOne(() => Category, category => category.categoryMonths)
80+
@ManyToOne(() => Category, category => category.categoryMonths, { onDelete: 'CASCADE' })
8181
category: Promise<Category>
8282

8383
/**

backend/src/entities/Payee.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1-
import { Entity, OneToOne, JoinColumn, PrimaryGeneratedColumn, Column, CreateDateColumn, OneToMany } from 'typeorm'
1+
import {
2+
Entity,
3+
OneToOne,
4+
JoinColumn,
5+
PrimaryGeneratedColumn,
6+
Column,
7+
CreateDateColumn,
8+
OneToMany,
9+
ManyToOne,
10+
} from 'typeorm'
211
import { Account } from './Account'
312
import { PayeeModel } from '../models/Payee'
413
import { Transaction } from './Transaction'
14+
import { Budget } from './Budget'
515

616
@Entity('payees')
717
export class Payee {
@@ -26,7 +36,10 @@ export class Payee {
2636
@CreateDateColumn()
2737
updated: Date
2838

29-
@OneToOne(() => Account, account => account.transferPayee)
39+
@ManyToOne(() => Budget, budget => budget.payees, { onDelete: 'CASCADE' })
40+
budget: Promise<Budget>
41+
42+
@OneToOne(() => Account, account => account.transferPayee, { onDelete: 'CASCADE' })
3043
@JoinColumn()
3144
transferAccount: Promise<Account>
3245

backend/src/entities/Transaction.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export class Transaction {
130130
/**
131131
* Belongs to an account
132132
*/
133-
@ManyToOne(() => Account, account => account.transactions)
133+
@ManyToOne(() => Account, account => account.transactions, { onDelete: 'CASCADE' })
134134
account: Promise<Account>
135135

136136
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { MigrationInterface, QueryRunner } from 'typeorm'
2+
3+
export class budgetMonthAvailable1649260258195 implements MigrationInterface {
4+
name = 'budgetMonthAvailable1649260258195'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(
8+
`CREATE TABLE "temporary_budgets" ("id" varchar PRIMARY KEY NOT NULL, "userId" varchar NOT NULL, "name" varchar NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "updated" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "FK_27e688ddf1ff3893b43065899f9" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`,
9+
)
10+
await queryRunner.query(
11+
`INSERT INTO "temporary_budgets"("id", "userId", "name", "created", "updated") SELECT "id", "userId", "name", "created", "updated" FROM "budgets"`,
12+
)
13+
await queryRunner.query(`DROP TABLE "budgets"`)
14+
await queryRunner.query(`ALTER TABLE "temporary_budgets" RENAME TO "budgets"`)
15+
await queryRunner.query(`DROP INDEX "IDX_0c21df54422306fdf78621fc18"`)
16+
await queryRunner.query(`DROP INDEX "IDX_398c07457719d1899ba4f11914"`)
17+
await queryRunner.query(
18+
`CREATE TABLE "temporary_budget_months" ("id" varchar PRIMARY KEY NOT NULL, "budgetId" varchar NOT NULL, "month" varchar NOT NULL, "income" integer NOT NULL DEFAULT (0), "budgeted" integer NOT NULL DEFAULT (0), "activity" integer NOT NULL DEFAULT (0), "underfunded" integer NOT NULL DEFAULT (0), "created" datetime NOT NULL DEFAULT (datetime('now')), "updated" datetime NOT NULL DEFAULT (datetime('now')), "available" integer NOT NULL DEFAULT (0), CONSTRAINT "FK_398c07457719d1899ba4f11914d" FOREIGN KEY ("budgetId") REFERENCES "budgets" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`,
19+
)
20+
await queryRunner.query(
21+
`INSERT INTO "temporary_budget_months"("id", "budgetId", "month", "income", "budgeted", "activity", "underfunded", "created", "updated") SELECT "id", "budgetId", "month", "income", "budgeted", "activity", "underfunded", "created", "updated" FROM "budget_months"`,
22+
)
23+
await queryRunner.query(`DROP TABLE "budget_months"`)
24+
await queryRunner.query(`ALTER TABLE "temporary_budget_months" RENAME TO "budget_months"`)
25+
await queryRunner.query(`CREATE INDEX "IDX_0c21df54422306fdf78621fc18" ON "budget_months" ("month") `)
26+
await queryRunner.query(`CREATE INDEX "IDX_398c07457719d1899ba4f11914" ON "budget_months" ("budgetId") `)
27+
}
28+
29+
public async down(queryRunner: QueryRunner): Promise<void> {
30+
await queryRunner.query(`DROP INDEX "IDX_398c07457719d1899ba4f11914"`)
31+
await queryRunner.query(`DROP INDEX "IDX_0c21df54422306fdf78621fc18"`)
32+
await queryRunner.query(`ALTER TABLE "budget_months" RENAME TO "temporary_budget_months"`)
33+
await queryRunner.query(
34+
`CREATE TABLE "budget_months" ("id" varchar PRIMARY KEY NOT NULL, "budgetId" varchar NOT NULL, "month" varchar NOT NULL, "income" integer NOT NULL DEFAULT (0), "budgeted" integer NOT NULL DEFAULT (0), "activity" integer NOT NULL DEFAULT (0), "underfunded" integer NOT NULL DEFAULT (0), "created" datetime NOT NULL DEFAULT (datetime('now')), "updated" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "FK_398c07457719d1899ba4f11914d" FOREIGN KEY ("budgetId") REFERENCES "budgets" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`,
35+
)
36+
await queryRunner.query(
37+
`INSERT INTO "budget_months"("id", "budgetId", "month", "income", "budgeted", "activity", "underfunded", "created", "updated") SELECT "id", "budgetId", "month", "income", "budgeted", "activity", "underfunded", "created", "updated" FROM "temporary_budget_months"`,
38+
)
39+
await queryRunner.query(`DROP TABLE "temporary_budget_months"`)
40+
await queryRunner.query(`CREATE INDEX "IDX_398c07457719d1899ba4f11914" ON "budget_months" ("budgetId") `)
41+
await queryRunner.query(`CREATE INDEX "IDX_0c21df54422306fdf78621fc18" ON "budget_months" ("month") `)
42+
await queryRunner.query(`ALTER TABLE "budgets" RENAME TO "temporary_budgets"`)
43+
await queryRunner.query(
44+
`CREATE TABLE "budgets" ("id" varchar PRIMARY KEY NOT NULL, "userId" varchar NOT NULL, "name" varchar NOT NULL, "toBeBudgeted" integer NOT NULL DEFAULT (0), "created" datetime NOT NULL DEFAULT (datetime('now')), "updated" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "FK_27e688ddf1ff3893b43065899f9" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`,
45+
)
46+
await queryRunner.query(
47+
`INSERT INTO "budgets"("id", "userId", "name", "created", "updated") SELECT "id", "userId", "name", "created", "updated" FROM "temporary_budgets"`,
48+
)
49+
await queryRunner.query(`DROP TABLE "temporary_budgets"`)
50+
}
51+
}

backend/src/models/Budget.ts

-5
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,6 @@ export interface BudgetModel {
2525
*/
2626
accounts: AccountModel[]
2727

28-
/**
29-
* Amount left to budget
30-
*/
31-
toBeBudgeted: number
32-
3328
/**
3429
* Datetime user was created
3530
*/

backend/src/models/BudgetMonth.ts

+5
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ export interface BudgetMonthModel {
4545
*/
4646
activity: number
4747

48+
/**
49+
* Amount available to budget
50+
*/
51+
available: number
52+
4853
/**
4954
* Deficit amount for the month
5055
*/

0 commit comments

Comments
 (0)