Skip to content

Commit 294ad04

Browse files
committed
Squashed commit of the following:
commit 2202e50 Merge: 08a77c8 c70cae6 Author: prooflesben <122566738+prooflesben@users.noreply.github.com> Date: Sun Apr 12 16:36:14 2026 -0400 Merge pull request #395 from Code-4-Community/389-dev-change-cost-type-and-frontend-for-cost Enhance cashflow with frequency and projection start date commit c70cae6 Author: prooflesben <prooflesben@gmail.com> Date: Sun Apr 12 16:34:01 2026 -0400 fixed error commit 99417b3 Merge: fece561 08a77c8 Author: prooflesben <122566738+prooflesben@users.noreply.github.com> Date: Sun Apr 12 15:56:31 2026 -0400 Merge branch 'main' into 389-dev-change-cost-type-and-frontend-for-cost commit 08a77c8 Merge: 1da382c c2b410b Author: prooflesben <122566738+prooflesben@users.noreply.github.com> Date: Sun Apr 12 15:19:03 2026 -0400 Merge pull request #387 from Code-4-Community/383-dev---ensure-edit-revenue-works edit revenue commit fece561 Merge: 27dcfa8 1da382c Author: prooflesben <122566738+prooflesben@users.noreply.github.com> Date: Sun Apr 12 14:00:22 2026 -0400 Merge branch 'main' into 389-dev-change-cost-type-and-frontend-for-cost commit 27dcfa8 Author: Jane Kamata <janekamata8@gmail.com> Date: Sun Apr 5 02:29:05 2026 -0400 Updating start date default for cost commit 2cf186e Author: Jane Kamata <janekamata8@gmail.com> Date: Sun Apr 5 02:18:55 2026 -0400 Styling changes commit 3a7ce4b Author: Jane Kamata <janekamata8@gmail.com> Date: Sun Apr 5 02:02:47 2026 -0400 Added alpha sort to line items, added scrolling commit 60c9145 Author: Jane Kamata <janekamata8@gmail.com> Date: Sun Apr 5 01:53:14 2026 -0400 Updating frequency type commit 39479c1 Author: Jane Kamata <janekamata8@gmail.com> Date: Sun Apr 5 01:28:32 2026 -0400 Adding success message animation commit c38b79c Author: Jane Kamata <janekamata8@gmail.com> Date: Sun Apr 5 00:57:26 2026 -0400 Updating service check commit b5c2c2f Author: Jane Kamata <janekamata8@gmail.com> Date: Sun Apr 5 00:45:55 2026 -0400 Updating default values tests commit 35fb779 Author: Jane Kamata <janekamata8@gmail.com> Date: Sun Apr 5 00:37:31 2026 -0400 Updating tests commit 488b030 Author: Jane Kamata <janekamata8@gmail.com> Date: Sat Apr 4 23:52:47 2026 -0400 Styling fixes commit 6f96c7d Author: Jane Kamata <janekamata8@gmail.com> Date: Sat Apr 4 23:42:00 2026 -0400 Adding start date commit 8c59d00 Author: Jane Kamata <janekamata8@gmail.com> Date: Sat Apr 4 22:43:22 2026 -0400 Updating add/edit cost commit dea1ff7 Author: Jane Kamata <janekamata8@gmail.com> Date: Sat Apr 4 18:58:06 2026 -0400 Updating types and checks commit c2b410b Author: adityapat24 <appathak46@gmail.com> Date: Thu Apr 2 16:18:58 2026 -0400 edit revenue
1 parent 142f276 commit 294ad04

25 files changed

Lines changed: 951 additions & 320 deletions

backend/src/cost/__test__/cashflow-cost.service.spec.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import { describe, it, expect, beforeEach, vi } from 'vitest';
99
import { CostService } from '../cashflow-cost.service';
1010
import { CostType } from '../../../../middle-layer/types/CostType';
11+
import { Frequency } from '../../../../middle-layer/types/Frequency';
1112
import { CashflowCost } from '../../../../middle-layer/types/CashflowCost';
1213
import { TDateISO } from '../../utils/date';
1314

@@ -170,6 +171,7 @@ describe('CostService', () => {
170171
name: ' Food ',
171172
amount: 200,
172173
type: CostType.MealsFood,
174+
frequency: Frequency.Yearly,
173175
date: '2026-03-22' as TDateISO,
174176
};
175177

@@ -179,6 +181,7 @@ describe('CostService', () => {
179181
name: 'Food',
180182
amount: 200,
181183
type: CostType.MealsFood,
184+
frequency: Frequency.Yearly,
182185
date: '2026-03-22',
183186
});
184187
expect(mockPut).toHaveBeenCalledWith({
@@ -187,6 +190,7 @@ describe('CostService', () => {
187190
name: 'Food',
188191
amount: 200,
189192
type: CostType.MealsFood,
193+
frequency: Frequency.Yearly,
190194
date: '2026-03-22',
191195
},
192196
ConditionExpression: 'attribute_not_exists(#name)',
@@ -202,6 +206,7 @@ describe('CostService', () => {
202206
name: 'Food',
203207
amount: 0,
204208
type: CostType.MealsFood,
209+
frequency: Frequency.Yearly,
205210
date: '2026-03-22' as TDateISO,
206211
}),
207212
).rejects.toThrow(BadRequestException);
@@ -210,6 +215,7 @@ describe('CostService', () => {
210215
name: 'Food',
211216
amount: 0,
212217
type: CostType.MealsFood,
218+
frequency: Frequency.Yearly,
213219
date: '2026-03-22' as TDateISO,
214220
}),
215221
).rejects.toThrow('amount must be a finite positive number');
@@ -221,6 +227,19 @@ describe('CostService', () => {
221227
name: 'Food',
222228
amount: 100,
223229
type: 'INVALID' as unknown as CostType,
230+
frequency: Frequency.Yearly,
231+
date: '2026-03-22' as TDateISO,
232+
}),
233+
).rejects.toThrow(BadRequestException);
234+
});
235+
236+
it('throws BadRequestException for invalid frequency', async () => {
237+
await expect(
238+
service.createCost({
239+
name: 'Food',
240+
amount: 100,
241+
type: CostType.MealsFood,
242+
frequency: 'INVALID' as unknown as Frequency,
224243
date: '2026-03-22' as TDateISO,
225244
}),
226245
).rejects.toThrow(BadRequestException);
@@ -232,6 +251,7 @@ describe('CostService', () => {
232251
name: ' ',
233252
amount: 100,
234253
type: CostType.MealsFood,
254+
frequency: Frequency.Yearly,
235255
date: '2026-03-22' as TDateISO,
236256
}),
237257
).rejects.toThrow(BadRequestException);
@@ -240,6 +260,7 @@ describe('CostService', () => {
240260
name: ' ',
241261
amount: 100,
242262
type: CostType.MealsFood,
263+
frequency: Frequency.Yearly,
243264
date: '2026-03-22' as TDateISO,
244265
}),
245266
).rejects.toThrow('name must be a non-empty string');
@@ -253,6 +274,7 @@ describe('CostService', () => {
253274
name: 'Food',
254275
amount: 100,
255276
type: CostType.MealsFood,
277+
frequency: Frequency.Yearly,
256278
date: '2026-03-22' as TDateISO,
257279
}),
258280
).rejects.toThrow(ConflictException);
@@ -261,6 +283,7 @@ describe('CostService', () => {
261283
name: 'Food',
262284
amount: 100,
263285
type: CostType.MealsFood,
286+
frequency: Frequency.Yearly,
264287
date: '2026-03-22' as TDateISO,
265288
}),
266289
).rejects.toThrow('Cost with name Food already exists');
@@ -274,6 +297,7 @@ describe('CostService', () => {
274297
name: 'Food',
275298
amount: 100,
276299
type: CostType.MealsFood,
300+
frequency: Frequency.Yearly,
277301
date: '2026-03-22' as TDateISO,
278302
}),
279303
).rejects.toThrow(InternalServerErrorException);
@@ -287,6 +311,7 @@ describe('CostService', () => {
287311
name: 'Food',
288312
amount: 100,
289313
type: CostType.MealsFood,
314+
frequency: Frequency.Yearly,
290315
date: '2026-03-22' as TDateISO,
291316
}),
292317
).rejects.toThrow(InternalServerErrorException);
@@ -295,6 +320,7 @@ describe('CostService', () => {
295320
name: 'Food',
296321
amount: 100,
297322
type: CostType.MealsFood,
323+
frequency: Frequency.Yearly,
298324
date: '2026-03-22' as TDateISO,
299325
}),
300326
).rejects.toThrow('Failed to create cost');
@@ -308,6 +334,7 @@ describe('CostService', () => {
308334
name: 'Food',
309335
amount: 200,
310336
type: CostType.MealsFood,
337+
frequency: Frequency.Yearly,
311338
date: '2026-03-22',
312339
},
313340
});
@@ -318,6 +345,7 @@ describe('CostService', () => {
318345
name: 'Food',
319346
amount: 300,
320347
type: CostType.Services,
348+
frequency: Frequency.OneTime,
321349
date: '2026-03-22' as TDateISO,
322350
};
323351
mockPutPromise.mockResolvedValue({});
@@ -326,6 +354,7 @@ describe('CostService', () => {
326354
name: 'Food',
327355
amount: 300,
328356
type: CostType.Services,
357+
frequency: Frequency.OneTime,
329358
date: '2026-03-22' as TDateISO,
330359
});
331360

@@ -340,6 +369,7 @@ describe('CostService', () => {
340369
name: 'Food',
341370
amount: 300,
342371
type: CostType.Services,
372+
frequency: Frequency.OneTime,
343373
date: '2026-03-22',
344374
},
345375
ConditionExpression: 'attribute_exists(#name)',
@@ -354,13 +384,15 @@ describe('CostService', () => {
354384
name: 'Food',
355385
amount: 200,
356386
type: CostType.MealsFood,
387+
frequency: Frequency.Yearly,
357388
date: '2026-03-22' as TDateISO,
358389
});
359390

360391
expect(result).toEqual({
361392
name: 'Food',
362393
amount: 200,
363394
type: CostType.MealsFood,
395+
frequency: Frequency.Yearly,
364396
date: '2026-03-22',
365397
});
366398
expect(mockPut).not.toHaveBeenCalled();
@@ -373,6 +405,7 @@ describe('CostService', () => {
373405
name: 'Food',
374406
amount: Number.NaN,
375407
type: CostType.MealsFood,
408+
frequency: Frequency.Yearly,
376409
date: '2026-03-22' as TDateISO,
377410
}),
378411
).rejects.toThrow(BadRequestException);
@@ -384,6 +417,19 @@ describe('CostService', () => {
384417
name: 'Food',
385418
amount: 250,
386419
type: 'INVALID' as unknown as CostType,
420+
frequency: Frequency.Yearly,
421+
date: '2026-03-22' as TDateISO,
422+
}),
423+
).rejects.toThrow(BadRequestException);
424+
});
425+
426+
it('throws BadRequestException for invalid frequency', async () => {
427+
await expect(
428+
service.updateCost('Food', {
429+
name: 'Food',
430+
amount: 250,
431+
type: CostType.MealsFood,
432+
frequency: 'INVALID' as unknown as Frequency,
387433
date: '2026-03-22' as TDateISO,
388434
}),
389435
).rejects.toThrow(BadRequestException);
@@ -395,6 +441,7 @@ describe('CostService', () => {
395441
name: 'Food',
396442
amount: 250,
397443
type: CostType.MealsFood,
444+
frequency: Frequency.Yearly,
398445
date: 'not-a-date' as unknown as TDateISO,
399446
}),
400447
).rejects.toThrow(BadRequestException);
@@ -403,6 +450,7 @@ describe('CostService', () => {
403450
name: 'Food',
404451
amount: 250,
405452
type: CostType.MealsFood,
453+
frequency: Frequency.Yearly,
406454
date: 'not-a-date' as unknown as TDateISO,
407455
}),
408456
).rejects.toThrow('date must be a valid ISO 8601 format string');
@@ -416,6 +464,7 @@ describe('CostService', () => {
416464
name: 'Food',
417465
amount: 250,
418466
type: CostType.MealsFood,
467+
frequency: Frequency.Yearly,
419468
date: '2026-03-22' as TDateISO,
420469
}),
421470
).rejects.toThrow(NotFoundException);
@@ -424,6 +473,7 @@ describe('CostService', () => {
424473
name: 'Food',
425474
amount: 250,
426475
type: CostType.MealsFood,
476+
frequency: Frequency.Yearly,
427477
date: '2026-03-22' as TDateISO,
428478
}),
429479
).rejects.toThrow('Cost with name Food not found');
@@ -438,6 +488,7 @@ describe('CostService', () => {
438488
name: 'Food',
439489
amount: 250,
440490
type: CostType.MealsFood,
491+
frequency: Frequency.Yearly,
441492
date: '2026-03-22' as TDateISO,
442493
}),
443494
).rejects.toThrow(InternalServerErrorException);
@@ -446,6 +497,7 @@ describe('CostService', () => {
446497
name: 'Food',
447498
amount: 250,
448499
type: CostType.MealsFood,
500+
frequency: Frequency.Yearly,
449501
date: '2026-03-22' as TDateISO,
450502
}),
451503
).rejects.toThrow('Failed to update cost Food');
@@ -461,13 +513,15 @@ describe('CostService', () => {
461513
name: 'Meals',
462514
amount: 300,
463515
type: CostType.MealsFood,
516+
frequency: Frequency.Yearly,
464517
date: '2026-03-22' as TDateISO,
465518
});
466519

467520
expect(result).toEqual({
468521
name: 'Meals',
469522
amount: 300,
470523
type: CostType.MealsFood,
524+
frequency: Frequency.Yearly,
471525
date: '2026-03-22',
472526
});
473527
expect(mockGet).toHaveBeenCalledWith({
@@ -483,6 +537,7 @@ describe('CostService', () => {
483537
name: 'Meals',
484538
amount: 300,
485539
type: CostType.MealsFood,
540+
frequency: Frequency.Yearly,
486541
date: '2026-03-22',
487542
},
488543
ConditionExpression: 'attribute_not_exists(#name)',
@@ -515,13 +570,15 @@ describe('CostService', () => {
515570
name: 'Meals',
516571
amount: 300,
517572
type: CostType.MealsFood,
573+
frequency: Frequency.Yearly,
518574
date: '2026-03-22' as TDateISO,
519575
});
520576

521577
expect(result).toEqual({
522578
name: 'Meals',
523579
amount: 300,
524580
type: CostType.MealsFood,
581+
frequency: Frequency.Yearly,
525582
date: '2026-03-22',
526583
});
527584
});
@@ -534,6 +591,7 @@ describe('CostService', () => {
534591
name: 'Meals',
535592
amount: 300,
536593
type: CostType.MealsFood,
594+
frequency: Frequency.Yearly,
537595
date: '2026-03-22' as TDateISO,
538596
}),
539597
).rejects.toThrow(NotFoundException);
@@ -542,6 +600,7 @@ describe('CostService', () => {
542600
name: 'Meals',
543601
amount: 300,
544602
type: CostType.MealsFood,
603+
frequency: Frequency.Yearly,
545604
date: '2026-03-22' as TDateISO,
546605
}),
547606
).rejects.toThrow('Cost with name Food not found');
@@ -560,6 +619,7 @@ describe('CostService', () => {
560619
name: 'Meals',
561620
amount: 300,
562621
type: CostType.MealsFood,
622+
frequency: Frequency.Yearly,
563623
date: '2026-03-22' as TDateISO,
564624
}),
565625
).rejects.toThrow(ConflictException);
@@ -568,6 +628,7 @@ describe('CostService', () => {
568628
name: 'Meals',
569629
amount: 300,
570630
type: CostType.MealsFood,
631+
frequency: Frequency.Yearly,
571632
date: '2026-03-22' as TDateISO,
572633
}),
573634
).rejects.toThrow('Cost with name Meals already exists');
@@ -584,6 +645,7 @@ describe('CostService', () => {
584645
name: 'Meals',
585646
amount: 300,
586647
type: CostType.MealsFood,
648+
frequency: Frequency.Yearly,
587649
date: '2026-03-22' as TDateISO,
588650
}),
589651
).rejects.toThrow(InternalServerErrorException);
@@ -592,6 +654,7 @@ describe('CostService', () => {
592654
name: 'Meals',
593655
amount: 300,
594656
type: CostType.MealsFood,
657+
frequency: Frequency.Yearly,
595658
date: '2026-03-22' as TDateISO,
596659
}),
597660
).rejects.toThrow('Failed to update cost Food');
@@ -605,6 +668,7 @@ describe('CostService', () => {
605668
name: 'Food',
606669
amount: 200,
607670
type: CostType.MealsFood,
671+
frequency: Frequency.Yearly,
608672
date: '2026-03-22' as TDateISO,
609673
}),
610674
).rejects.toThrow(InternalServerErrorException);

backend/src/cost/cashflow-cost.service.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import * as AWS from 'aws-sdk';
1010
import { CashflowCost } from '../../../middle-layer/types/CashflowCost';
1111
import { CostType } from '../../../middle-layer/types/CostType';
12+
import { Frequency } from '../../../middle-layer/types/Frequency';
1213

1314
interface UpdateCostBody {
1415
amount?: number;
@@ -30,6 +31,15 @@ export class CostService {
3031
}
3132
}
3233

34+
private validateFrequency(frequency: string) {
35+
if (!Object.values(Frequency).includes(frequency as Frequency) || frequency === null) {
36+
throw new BadRequestException(
37+
`frequency must be one of: ${Object.values(Frequency).join(', ')}`,
38+
);
39+
}
40+
}
41+
42+
3343
private validateAmount(amount: number) {
3444
if (!Number.isFinite(amount) || amount <= 0 || amount === null) {
3545
throw new BadRequestException('amount must be a finite positive number');
@@ -121,6 +131,7 @@ export class CostService {
121131
const tableName = process.env.CASHFLOW_COST_TABLE_NAME || '';
122132
this.validateAmount(cost.amount);
123133
this.validateCostType(cost.type);
134+
this.validateFrequency(cost.frequency);
124135
this.validateName(cost.name);
125136
const normalizedName = cost.name.trim();
126137

@@ -178,6 +189,7 @@ export class CostService {
178189

179190
this.validateAmount(updates.amount);
180191
this.validateCostType(updates.type);
192+
this.validateFrequency(updates.frequency);
181193

182194
if (updates.name !== undefined) {
183195
this.validateName(updates.name);
@@ -209,6 +221,7 @@ export class CostService {
209221
existingCost.name === updates.name &&
210222
existingCost.amount === updates.amount &&
211223
existingCost.type === updates.type &&
224+
existingCost.frequency === updates.frequency &&
212225
datesAreEqual;
213226

214227
if (isUnchanged) {

0 commit comments

Comments
 (0)