checkpoint: goals feature, wallet balance, and goals/wallet detail UI

- Add goals feature (models, migrations, API, web pages)
- Add reserved/centralized wallet balance service
- Add wallet detail page and overview components
- Add new UI components (progress, multi-select, FAB)
- Remove stray empty -H/-d files from working tree
This commit is contained in:
Dwindi Ramadhana
2026-06-17 20:40:00 +07:00
parent 35e93b826a
commit 6a6e74562c
401 changed files with 9517 additions and 397 deletions

View File

@@ -0,0 +1,5 @@
export declare class CreateAllocationDto {
walletId: string;
amount: number;
notes?: string;
}

View File

@@ -0,0 +1,35 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateAllocationDto = void 0;
const class_validator_1 = require("class-validator");
class CreateAllocationDto {
walletId;
amount;
notes;
}
exports.CreateAllocationDto = CreateAllocationDto;
__decorate([
(0, class_validator_1.IsString)(),
(0, class_validator_1.IsNotEmpty)(),
__metadata("design:type", String)
], CreateAllocationDto.prototype, "walletId", void 0);
__decorate([
(0, class_validator_1.IsNumber)(),
(0, class_validator_1.Min)(0.01),
__metadata("design:type", Number)
], CreateAllocationDto.prototype, "amount", void 0);
__decorate([
(0, class_validator_1.IsString)(),
(0, class_validator_1.IsOptional)(),
__metadata("design:type", String)
], CreateAllocationDto.prototype, "notes", void 0);
//# sourceMappingURL=create-allocation.dto.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"create-allocation.dto.js","sourceRoot":"","sources":["../../../src/goals/dto/create-allocation.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAkF;AAElF,MAAa,mBAAmB;IAG9B,QAAQ,CAAS;IAIjB,MAAM,CAAS;IAIf,KAAK,CAAU;CAChB;AAZD,kDAYC;AATC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;qDACI;AAIjB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,IAAI,CAAC;;mDACK;AAIf;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;kDACE"}

View File

@@ -0,0 +1,9 @@
export declare class CreateGoalDto {
name: string;
description?: string;
targetAmount: number;
currency: string;
targetDate?: string;
imageUrl?: string;
category?: string;
}

59
apps/api/dist/goals/dto/create-goal.dto.js vendored Executable file
View File

@@ -0,0 +1,59 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateGoalDto = void 0;
const class_validator_1 = require("class-validator");
class CreateGoalDto {
name;
description;
targetAmount;
currency;
targetDate;
imageUrl;
category;
}
exports.CreateGoalDto = CreateGoalDto;
__decorate([
(0, class_validator_1.IsString)(),
(0, class_validator_1.IsNotEmpty)(),
__metadata("design:type", String)
], CreateGoalDto.prototype, "name", void 0);
__decorate([
(0, class_validator_1.IsString)(),
(0, class_validator_1.IsOptional)(),
__metadata("design:type", String)
], CreateGoalDto.prototype, "description", void 0);
__decorate([
(0, class_validator_1.IsNumber)(),
(0, class_validator_1.Min)(0),
__metadata("design:type", Number)
], CreateGoalDto.prototype, "targetAmount", void 0);
__decorate([
(0, class_validator_1.IsString)(),
(0, class_validator_1.IsNotEmpty)(),
__metadata("design:type", String)
], CreateGoalDto.prototype, "currency", void 0);
__decorate([
(0, class_validator_1.IsDateString)(),
(0, class_validator_1.IsOptional)(),
__metadata("design:type", String)
], CreateGoalDto.prototype, "targetDate", void 0);
__decorate([
(0, class_validator_1.IsString)(),
(0, class_validator_1.IsOptional)(),
__metadata("design:type", String)
], CreateGoalDto.prototype, "imageUrl", void 0);
__decorate([
(0, class_validator_1.IsString)(),
(0, class_validator_1.IsOptional)(),
__metadata("design:type", String)
], CreateGoalDto.prototype, "category", void 0);
//# sourceMappingURL=create-goal.dto.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"create-goal.dto.js","sourceRoot":"","sources":["../../../src/goals/dto/create-goal.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAgG;AAEhG,MAAa,aAAa;IAGxB,IAAI,CAAS;IAIb,WAAW,CAAU;IAIrB,YAAY,CAAS;IAIrB,QAAQ,CAAS;IAIjB,UAAU,CAAU;IAIpB,QAAQ,CAAU;IAIlB,QAAQ,CAAU;CACnB;AA5BD,sCA4BC;AAzBC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;2CACA;AAIb;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;kDACQ;AAIrB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,qBAAG,EAAC,CAAC,CAAC;;mDACc;AAIrB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;+CACI;AAIjB;IAFC,IAAA,8BAAY,GAAE;IACd,IAAA,4BAAU,GAAE;;iDACO;AAIpB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;+CACK;AAIlB;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;+CACK"}

View File

@@ -0,0 +1,6 @@
import { CreateGoalDto } from './create-goal.dto';
declare const UpdateGoalDto_base: import("@nestjs/mapped-types").MappedType<Partial<CreateGoalDto>>;
export declare class UpdateGoalDto extends UpdateGoalDto_base {
status?: string;
}
export {};

25
apps/api/dist/goals/dto/update-goal.dto.js vendored Executable file
View File

@@ -0,0 +1,25 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpdateGoalDto = void 0;
const mapped_types_1 = require("@nestjs/mapped-types");
const create_goal_dto_1 = require("./create-goal.dto");
const class_validator_1 = require("class-validator");
class UpdateGoalDto extends (0, mapped_types_1.PartialType)(create_goal_dto_1.CreateGoalDto) {
status;
}
exports.UpdateGoalDto = UpdateGoalDto;
__decorate([
(0, class_validator_1.IsString)(),
(0, class_validator_1.IsOptional)(),
__metadata("design:type", String)
], UpdateGoalDto.prototype, "status", void 0);
//# sourceMappingURL=update-goal.dto.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"update-goal.dto.js","sourceRoot":"","sources":["../../../src/goals/dto/update-goal.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,uDAAmD;AACnD,uDAAkD;AAClD,qDAAuD;AAEvD,MAAa,aAAc,SAAQ,IAAA,0BAAW,EAAC,+BAAa,CAAC;IAG3D,MAAM,CAAU;CACjB;AAJD,sCAIC;AADC;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,4BAAU,GAAE;;6CACG"}

261
apps/api/dist/goals/goals.controller.d.ts vendored Executable file
View File

@@ -0,0 +1,261 @@
import { GoalsService } from './goals.service';
import { CreateGoalDto } from './dto/create-goal.dto';
import { UpdateGoalDto } from './dto/update-goal.dto';
import { CreateAllocationDto } from './dto/create-allocation.dto';
export declare class GoalsController {
private readonly goalsService;
constructor(goalsService: GoalsService);
create(req: any, createGoalDto: CreateGoalDto): Promise<{
allocations: ({
wallet: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
deletedAt: Date | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
pricePerUnit: import("@prisma/client/runtime/library").Decimal | null;
reservedBalance: import("@prisma/client/runtime/library").Decimal;
};
} & {
id: string;
createdAt: Date;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
walletId: string;
notes: string | null;
goalId: string;
exchangeRate: import("@prisma/client/runtime/library").Decimal | null;
amountInGoalCurrency: import("@prisma/client/runtime/library").Decimal;
createdBy: string;
})[];
milestones: {
id: string;
targetAmount: import("@prisma/client/runtime/library").Decimal;
percentage: number;
goalId: string;
achievedAt: Date | null;
notifiedAt: Date | null;
}[];
} & {
category: string | null;
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
name: string;
userId: string;
currency: string;
description: string | null;
targetAmount: import("@prisma/client/runtime/library").Decimal;
targetDate: Date | null;
imageUrl: string | null;
teamId: string | null;
currentAmount: import("@prisma/client/runtime/library").Decimal;
completedAt: Date | null;
}>;
findAll(req: any, status?: string): Promise<({
allocations: ({
wallet: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
deletedAt: Date | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
pricePerUnit: import("@prisma/client/runtime/library").Decimal | null;
reservedBalance: import("@prisma/client/runtime/library").Decimal;
};
} & {
id: string;
createdAt: Date;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
walletId: string;
notes: string | null;
goalId: string;
exchangeRate: import("@prisma/client/runtime/library").Decimal | null;
amountInGoalCurrency: import("@prisma/client/runtime/library").Decimal;
createdBy: string;
})[];
milestones: {
id: string;
targetAmount: import("@prisma/client/runtime/library").Decimal;
percentage: number;
goalId: string;
achievedAt: Date | null;
notifiedAt: Date | null;
}[];
} & {
category: string | null;
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
name: string;
userId: string;
currency: string;
description: string | null;
targetAmount: import("@prisma/client/runtime/library").Decimal;
targetDate: Date | null;
imageUrl: string | null;
teamId: string | null;
currentAmount: import("@prisma/client/runtime/library").Decimal;
completedAt: Date | null;
})[]>;
getStats(req: any): Promise<{
totalGoals: number;
activeGoals: number;
completedGoals: number;
totalTargetAmount: number;
totalCurrentAmount: number;
overallProgress: number;
}>;
findOne(req: any, id: string): Promise<{
allocations: ({
wallet: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
deletedAt: Date | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
pricePerUnit: import("@prisma/client/runtime/library").Decimal | null;
reservedBalance: import("@prisma/client/runtime/library").Decimal;
};
} & {
id: string;
createdAt: Date;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
walletId: string;
notes: string | null;
goalId: string;
exchangeRate: import("@prisma/client/runtime/library").Decimal | null;
amountInGoalCurrency: import("@prisma/client/runtime/library").Decimal;
createdBy: string;
})[];
milestones: {
id: string;
targetAmount: import("@prisma/client/runtime/library").Decimal;
percentage: number;
goalId: string;
achievedAt: Date | null;
notifiedAt: Date | null;
}[];
} & {
category: string | null;
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
name: string;
userId: string;
currency: string;
description: string | null;
targetAmount: import("@prisma/client/runtime/library").Decimal;
targetDate: Date | null;
imageUrl: string | null;
teamId: string | null;
currentAmount: import("@prisma/client/runtime/library").Decimal;
completedAt: Date | null;
}>;
update(req: any, id: string, updateGoalDto: UpdateGoalDto): Promise<{
allocations: ({
wallet: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
deletedAt: Date | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
pricePerUnit: import("@prisma/client/runtime/library").Decimal | null;
reservedBalance: import("@prisma/client/runtime/library").Decimal;
};
} & {
id: string;
createdAt: Date;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
walletId: string;
notes: string | null;
goalId: string;
exchangeRate: import("@prisma/client/runtime/library").Decimal | null;
amountInGoalCurrency: import("@prisma/client/runtime/library").Decimal;
createdBy: string;
})[];
milestones: {
id: string;
targetAmount: import("@prisma/client/runtime/library").Decimal;
percentage: number;
goalId: string;
achievedAt: Date | null;
notifiedAt: Date | null;
}[];
} & {
category: string | null;
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
name: string;
userId: string;
currency: string;
description: string | null;
targetAmount: import("@prisma/client/runtime/library").Decimal;
targetDate: Date | null;
imageUrl: string | null;
teamId: string | null;
currentAmount: import("@prisma/client/runtime/library").Decimal;
completedAt: Date | null;
}>;
remove(req: any, id: string): Promise<{
message: string;
}>;
addAllocation(req: any, id: string, createAllocationDto: CreateAllocationDto): Promise<{
wallet: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
deletedAt: Date | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
pricePerUnit: import("@prisma/client/runtime/library").Decimal | null;
reservedBalance: import("@prisma/client/runtime/library").Decimal;
};
} & {
id: string;
createdAt: Date;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
walletId: string;
notes: string | null;
goalId: string;
exchangeRate: import("@prisma/client/runtime/library").Decimal | null;
amountInGoalCurrency: import("@prisma/client/runtime/library").Decimal;
createdBy: string;
}>;
removeAllocation(req: any, id: string, allocationId: string): Promise<{
message: string;
}>;
}

124
apps/api/dist/goals/goals.controller.js vendored Executable file
View File

@@ -0,0 +1,124 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GoalsController = void 0;
const common_1 = require("@nestjs/common");
const goals_service_1 = require("./goals.service");
const create_goal_dto_1 = require("./dto/create-goal.dto");
const update_goal_dto_1 = require("./dto/update-goal.dto");
const create_allocation_dto_1 = require("./dto/create-allocation.dto");
const auth_guard_1 = require("../auth/auth.guard");
let GoalsController = class GoalsController {
goalsService;
constructor(goalsService) {
this.goalsService = goalsService;
}
create(req, createGoalDto) {
return this.goalsService.create(req.user.userId, createGoalDto);
}
findAll(req, status) {
return this.goalsService.findAll(req.user.userId, status);
}
getStats(req) {
return this.goalsService.getStats(req.user.userId);
}
findOne(req, id) {
return this.goalsService.findOne(req.user.userId, id);
}
update(req, id, updateGoalDto) {
return this.goalsService.update(req.user.userId, id, updateGoalDto);
}
remove(req, id) {
return this.goalsService.remove(req.user.userId, id);
}
addAllocation(req, id, createAllocationDto) {
return this.goalsService.addAllocation(req.user.userId, id, createAllocationDto);
}
removeAllocation(req, id, allocationId) {
return this.goalsService.removeAllocation(req.user.userId, id, allocationId);
}
};
exports.GoalsController = GoalsController;
__decorate([
(0, common_1.Post)(),
__param(0, (0, common_1.Request)()),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, create_goal_dto_1.CreateGoalDto]),
__metadata("design:returntype", void 0)
], GoalsController.prototype, "create", null);
__decorate([
(0, common_1.Get)(),
__param(0, (0, common_1.Request)()),
__param(1, (0, common_1.Query)('status')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String]),
__metadata("design:returntype", void 0)
], GoalsController.prototype, "findAll", null);
__decorate([
(0, common_1.Get)('stats'),
__param(0, (0, common_1.Request)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], GoalsController.prototype, "getStats", null);
__decorate([
(0, common_1.Get)(':id'),
__param(0, (0, common_1.Request)()),
__param(1, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String]),
__metadata("design:returntype", void 0)
], GoalsController.prototype, "findOne", null);
__decorate([
(0, common_1.Patch)(':id'),
__param(0, (0, common_1.Request)()),
__param(1, (0, common_1.Param)('id')),
__param(2, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String, update_goal_dto_1.UpdateGoalDto]),
__metadata("design:returntype", void 0)
], GoalsController.prototype, "update", null);
__decorate([
(0, common_1.Delete)(':id'),
__param(0, (0, common_1.Request)()),
__param(1, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String]),
__metadata("design:returntype", void 0)
], GoalsController.prototype, "remove", null);
__decorate([
(0, common_1.Post)(':id/allocations'),
__param(0, (0, common_1.Request)()),
__param(1, (0, common_1.Param)('id')),
__param(2, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String, create_allocation_dto_1.CreateAllocationDto]),
__metadata("design:returntype", void 0)
], GoalsController.prototype, "addAllocation", null);
__decorate([
(0, common_1.Delete)(':id/allocations/:allocationId'),
__param(0, (0, common_1.Request)()),
__param(1, (0, common_1.Param)('id')),
__param(2, (0, common_1.Param)('allocationId')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String, String]),
__metadata("design:returntype", void 0)
], GoalsController.prototype, "removeAllocation", null);
exports.GoalsController = GoalsController = __decorate([
(0, common_1.Controller)('goals'),
(0, common_1.UseGuards)(auth_guard_1.AuthGuard),
__metadata("design:paramtypes", [goals_service_1.GoalsService])
], GoalsController);
//# sourceMappingURL=goals.controller.js.map

1
apps/api/dist/goals/goals.controller.js.map vendored Executable file
View File

@@ -0,0 +1 @@
{"version":3,"file":"goals.controller.js","sourceRoot":"","sources":["../../src/goals/goals.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAWwB;AACxB,mDAA+C;AAC/C,2DAAsD;AACtD,2DAAsD;AACtD,uEAAkE;AAClE,mDAA+C;AAIxC,IAAM,eAAe,GAArB,MAAM,eAAe;IACG;IAA7B,YAA6B,YAA0B;QAA1B,iBAAY,GAAZ,YAAY,CAAc;IAAG,CAAC;IAG3D,MAAM,CAAY,GAAG,EAAU,aAA4B;QACzD,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IAClE,CAAC;IAGD,OAAO,CAAY,GAAG,EAAmB,MAAe;QACtD,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC;IAGD,QAAQ,CAAY,GAAG;QACrB,OAAO,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACrD,CAAC;IAGD,OAAO,CAAY,GAAG,EAAe,EAAU;QAC7C,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACxD,CAAC;IAGD,MAAM,CACO,GAAG,EACD,EAAU,EACf,aAA4B;QAEpC,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,aAAa,CAAC,CAAC;IACtE,CAAC;IAGD,MAAM,CAAY,GAAG,EAAe,EAAU;QAC5C,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACvD,CAAC;IAGD,aAAa,CACA,GAAG,EACD,EAAU,EACf,mBAAwC;QAEhD,OAAO,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,mBAAmB,CAAC,CAAC;IACnF,CAAC;IAGD,gBAAgB,CACH,GAAG,EACD,EAAU,EACA,YAAoB;QAE3C,OAAO,IAAI,CAAC,YAAY,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,YAAY,CAAC,CAAC;IAC/E,CAAC;CACF,CAAA;AAtDY,0CAAe;AAI1B;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAO,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAgB,+BAAa;;6CAE1D;AAGD;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAO,WAAA,IAAA,cAAK,EAAC,QAAQ,CAAC,CAAA;;;;8CAEvC;AAGD;IADC,IAAA,YAAG,EAAC,OAAO,CAAC;IACH,WAAA,IAAA,gBAAO,GAAE,CAAA;;;;+CAElB;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACF,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAO,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;8CAEnC;AAGD;IADC,IAAA,cAAK,EAAC,KAAK,CAAC;IAEV,WAAA,IAAA,gBAAO,GAAE,CAAA;IACT,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,aAAI,GAAE,CAAA;;qDAAgB,+BAAa;;6CAGrC;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,gBAAO,GAAE,CAAA;IAAO,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;6CAElC;AAGD;IADC,IAAA,aAAI,EAAC,iBAAiB,CAAC;IAErB,WAAA,IAAA,gBAAO,GAAE,CAAA;IACT,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,aAAI,GAAE,CAAA;;qDAAsB,2CAAmB;;oDAGjD;AAGD;IADC,IAAA,eAAM,EAAC,+BAA+B,CAAC;IAErC,WAAA,IAAA,gBAAO,GAAE,CAAA;IACT,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,cAAK,EAAC,cAAc,CAAC,CAAA;;;;uDAGvB;0BArDU,eAAe;IAF3B,IAAA,mBAAU,EAAC,OAAO,CAAC;IACnB,IAAA,kBAAS,EAAC,sBAAS,CAAC;qCAEwB,4BAAY;GAD5C,eAAe,CAsD3B"}

2
apps/api/dist/goals/goals.module.d.ts vendored Executable file
View File

@@ -0,0 +1,2 @@
export declare class GoalsModule {
}

26
apps/api/dist/goals/goals.module.js vendored Executable file
View File

@@ -0,0 +1,26 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GoalsModule = void 0;
const common_1 = require("@nestjs/common");
const goals_service_1 = require("./goals.service");
const goals_controller_1 = require("./goals.controller");
const prisma_module_1 = require("../prisma/prisma.module");
const wallets_module_1 = require("../wallets/wallets.module");
let GoalsModule = class GoalsModule {
};
exports.GoalsModule = GoalsModule;
exports.GoalsModule = GoalsModule = __decorate([
(0, common_1.Module)({
imports: [prisma_module_1.PrismaModule, wallets_module_1.WalletsModule],
controllers: [goals_controller_1.GoalsController],
providers: [goals_service_1.GoalsService],
exports: [goals_service_1.GoalsService],
})
], GoalsModule);
//# sourceMappingURL=goals.module.js.map

1
apps/api/dist/goals/goals.module.js.map vendored Executable file
View File

@@ -0,0 +1 @@
{"version":3,"file":"goals.module.js","sourceRoot":"","sources":["../../src/goals/goals.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,mDAA+C;AAC/C,yDAAqD;AACrD,2DAAuD;AACvD,8DAA0D;AAQnD,IAAM,WAAW,GAAjB,MAAM,WAAW;CAAG,CAAA;AAAd,kCAAW;sBAAX,WAAW;IANvB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,EAAE,8BAAa,CAAC;QACtC,WAAW,EAAE,CAAC,kCAAe,CAAC;QAC9B,SAAS,EAAE,CAAC,4BAAY,CAAC;QACzB,OAAO,EAAE,CAAC,4BAAY,CAAC;KACxB,CAAC;GACW,WAAW,CAAG"}

267
apps/api/dist/goals/goals.service.d.ts vendored Executable file
View File

@@ -0,0 +1,267 @@
import { PrismaService } from '../prisma/prisma.service';
import { WalletBalanceService } from '../wallets/wallet-balance.service';
import { CreateGoalDto } from './dto/create-goal.dto';
import { UpdateGoalDto } from './dto/update-goal.dto';
import { CreateAllocationDto } from './dto/create-allocation.dto';
import { Decimal } from '@prisma/client/runtime/library';
export declare class GoalsService {
private prisma;
private walletBalanceService;
constructor(prisma: PrismaService, walletBalanceService: WalletBalanceService);
create(userId: string, createGoalDto: CreateGoalDto): Promise<{
allocations: ({
wallet: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
deletedAt: Date | null;
initialAmount: Decimal | null;
pricePerUnit: Decimal | null;
reservedBalance: Decimal;
};
} & {
id: string;
createdAt: Date;
currency: string;
amount: Decimal;
walletId: string;
notes: string | null;
goalId: string;
exchangeRate: Decimal | null;
amountInGoalCurrency: Decimal;
createdBy: string;
})[];
milestones: {
id: string;
targetAmount: Decimal;
percentage: number;
goalId: string;
achievedAt: Date | null;
notifiedAt: Date | null;
}[];
} & {
category: string | null;
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
name: string;
userId: string;
currency: string;
description: string | null;
targetAmount: Decimal;
targetDate: Date | null;
imageUrl: string | null;
teamId: string | null;
currentAmount: Decimal;
completedAt: Date | null;
}>;
findAll(userId: string, status?: string): Promise<({
allocations: ({
wallet: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
deletedAt: Date | null;
initialAmount: Decimal | null;
pricePerUnit: Decimal | null;
reservedBalance: Decimal;
};
} & {
id: string;
createdAt: Date;
currency: string;
amount: Decimal;
walletId: string;
notes: string | null;
goalId: string;
exchangeRate: Decimal | null;
amountInGoalCurrency: Decimal;
createdBy: string;
})[];
milestones: {
id: string;
targetAmount: Decimal;
percentage: number;
goalId: string;
achievedAt: Date | null;
notifiedAt: Date | null;
}[];
} & {
category: string | null;
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
name: string;
userId: string;
currency: string;
description: string | null;
targetAmount: Decimal;
targetDate: Date | null;
imageUrl: string | null;
teamId: string | null;
currentAmount: Decimal;
completedAt: Date | null;
})[]>;
findOne(userId: string, id: string): Promise<{
allocations: ({
wallet: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
deletedAt: Date | null;
initialAmount: Decimal | null;
pricePerUnit: Decimal | null;
reservedBalance: Decimal;
};
} & {
id: string;
createdAt: Date;
currency: string;
amount: Decimal;
walletId: string;
notes: string | null;
goalId: string;
exchangeRate: Decimal | null;
amountInGoalCurrency: Decimal;
createdBy: string;
})[];
milestones: {
id: string;
targetAmount: Decimal;
percentage: number;
goalId: string;
achievedAt: Date | null;
notifiedAt: Date | null;
}[];
} & {
category: string | null;
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
name: string;
userId: string;
currency: string;
description: string | null;
targetAmount: Decimal;
targetDate: Date | null;
imageUrl: string | null;
teamId: string | null;
currentAmount: Decimal;
completedAt: Date | null;
}>;
update(userId: string, id: string, updateGoalDto: UpdateGoalDto): Promise<{
allocations: ({
wallet: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
deletedAt: Date | null;
initialAmount: Decimal | null;
pricePerUnit: Decimal | null;
reservedBalance: Decimal;
};
} & {
id: string;
createdAt: Date;
currency: string;
amount: Decimal;
walletId: string;
notes: string | null;
goalId: string;
exchangeRate: Decimal | null;
amountInGoalCurrency: Decimal;
createdBy: string;
})[];
milestones: {
id: string;
targetAmount: Decimal;
percentage: number;
goalId: string;
achievedAt: Date | null;
notifiedAt: Date | null;
}[];
} & {
category: string | null;
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
name: string;
userId: string;
currency: string;
description: string | null;
targetAmount: Decimal;
targetDate: Date | null;
imageUrl: string | null;
teamId: string | null;
currentAmount: Decimal;
completedAt: Date | null;
}>;
remove(userId: string, id: string): Promise<{
message: string;
}>;
addAllocation(userId: string, goalId: string, createAllocationDto: CreateAllocationDto): Promise<{
wallet: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
deletedAt: Date | null;
initialAmount: Decimal | null;
pricePerUnit: Decimal | null;
reservedBalance: Decimal;
};
} & {
id: string;
createdAt: Date;
currency: string;
amount: Decimal;
walletId: string;
notes: string | null;
goalId: string;
exchangeRate: Decimal | null;
amountInGoalCurrency: Decimal;
createdBy: string;
}>;
removeAllocation(userId: string, goalId: string, allocationId: string): Promise<{
message: string;
}>;
getStats(userId: string): Promise<{
totalGoals: number;
activeGoals: number;
completedGoals: number;
totalTargetAmount: number;
totalCurrentAmount: number;
overallProgress: number;
}>;
private createMilestones;
private updateMilestones;
private getExchangeRate;
}

303
apps/api/dist/goals/goals.service.js vendored Executable file
View File

@@ -0,0 +1,303 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GoalsService = void 0;
const common_1 = require("@nestjs/common");
const prisma_service_1 = require("../prisma/prisma.service");
const wallet_balance_service_1 = require("../wallets/wallet-balance.service");
const library_1 = require("@prisma/client/runtime/library");
let GoalsService = class GoalsService {
prisma;
walletBalanceService;
constructor(prisma, walletBalanceService) {
this.prisma = prisma;
this.walletBalanceService = walletBalanceService;
}
async create(userId, createGoalDto) {
const goal = await this.prisma.goal.create({
data: {
userId,
name: createGoalDto.name,
description: createGoalDto.description,
targetAmount: new library_1.Decimal(createGoalDto.targetAmount),
currency: createGoalDto.currency,
targetDate: createGoalDto.targetDate ? new Date(createGoalDto.targetDate) : null,
imageUrl: createGoalDto.imageUrl,
category: createGoalDto.category,
},
include: {
allocations: true,
milestones: true,
},
});
await this.createMilestones(goal.id, new library_1.Decimal(createGoalDto.targetAmount));
return this.findOne(userId, goal.id);
}
async findAll(userId, status) {
const where = { userId };
if (status) {
where.status = status;
}
return this.prisma.goal.findMany({
where,
include: {
allocations: {
include: {
wallet: true,
},
},
milestones: {
orderBy: { percentage: 'asc' },
},
},
orderBy: { createdAt: 'desc' },
});
}
async findOne(userId, id) {
const goal = await this.prisma.goal.findFirst({
where: { id, userId },
include: {
allocations: {
include: {
wallet: true,
},
orderBy: { createdAt: 'desc' },
},
milestones: {
orderBy: { percentage: 'asc' },
},
},
});
if (!goal) {
throw new common_1.NotFoundException('Goal not found');
}
return goal;
}
async update(userId, id, updateGoalDto) {
await this.findOne(userId, id);
const updateData = {};
if (updateGoalDto.name !== undefined)
updateData.name = updateGoalDto.name;
if (updateGoalDto.description !== undefined)
updateData.description = updateGoalDto.description;
if (updateGoalDto.targetAmount !== undefined) {
updateData.targetAmount = new library_1.Decimal(updateGoalDto.targetAmount);
await this.prisma.goalMilestone.deleteMany({ where: { goalId: id } });
await this.createMilestones(id, new library_1.Decimal(updateGoalDto.targetAmount));
}
if (updateGoalDto.currency !== undefined)
updateData.currency = updateGoalDto.currency;
if (updateGoalDto.targetDate !== undefined) {
updateData.targetDate = updateGoalDto.targetDate ? new Date(updateGoalDto.targetDate) : null;
}
if (updateGoalDto.imageUrl !== undefined)
updateData.imageUrl = updateGoalDto.imageUrl;
if (updateGoalDto.category !== undefined)
updateData.category = updateGoalDto.category;
if (updateGoalDto.status !== undefined) {
updateData.status = updateGoalDto.status;
if (updateGoalDto.status === 'completed') {
updateData.completedAt = new Date();
}
}
const goal = await this.prisma.goal.update({
where: { id },
data: updateData,
include: {
allocations: {
include: {
wallet: true,
},
},
milestones: true,
},
});
return goal;
}
async remove(userId, id) {
await this.findOne(userId, id);
await this.prisma.goal.delete({
where: { id },
});
return { message: 'Goal deleted successfully' };
}
async addAllocation(userId, goalId, createAllocationDto) {
const goal = await this.findOne(userId, goalId);
const wallet = await this.prisma.wallet.findFirst({
where: {
id: createAllocationDto.walletId,
userId,
deletedAt: null,
},
});
if (!wallet) {
throw new common_1.NotFoundException('Wallet not found');
}
const walletBalance = await this.walletBalanceService.calculateBalance(wallet.id);
const allocationAmountInIDR = new library_1.Decimal(createAllocationDto.amount);
let amountInWalletCurrency = allocationAmountInIDR;
let exchangeRate = new library_1.Decimal(1);
if (wallet.currency && wallet.currency !== 'IDR') {
exchangeRate = await this.getExchangeRate('IDR', wallet.currency);
amountInWalletCurrency = allocationAmountInIDR.times(exchangeRate);
}
else if (wallet.kind === 'asset' && wallet.pricePerUnit) {
amountInWalletCurrency = allocationAmountInIDR.dividedBy(wallet.pricePerUnit);
}
if (walletBalance.availableBalance.lessThan(amountInWalletCurrency)) {
const currency = wallet.kind === 'money' ? wallet.currency : wallet.unit;
throw new common_1.BadRequestException(`Insufficient available balance. Available: ${walletBalance.availableBalance.toString()} ${currency}, Reserved: ${walletBalance.reservedBalance.toString()} ${currency}`);
}
const allocation = await this.prisma.goalAllocation.create({
data: {
goalId,
walletId: wallet.id,
amount: allocationAmountInIDR,
currency: 'IDR',
exchangeRate: wallet.currency !== 'IDR' ? exchangeRate : null,
amountInGoalCurrency: allocationAmountInIDR,
notes: createAllocationDto.notes,
createdBy: userId,
},
include: {
wallet: true,
},
});
const newCurrentAmount = new library_1.Decimal(goal.currentAmount).plus(allocationAmountInIDR);
await this.prisma.goal.update({
where: { id: goalId },
data: { currentAmount: newCurrentAmount },
});
await this.prisma.wallet.update({
where: { id: wallet.id },
data: {
reservedBalance: {
increment: amountInWalletCurrency,
},
},
});
await this.updateMilestones(goalId, newCurrentAmount);
return allocation;
}
async removeAllocation(userId, goalId, allocationId) {
const goal = await this.findOne(userId, goalId);
const allocation = await this.prisma.goalAllocation.findFirst({
where: {
id: allocationId,
goalId,
},
});
if (!allocation) {
throw new common_1.NotFoundException('Allocation not found');
}
const newCurrentAmount = new library_1.Decimal(goal.currentAmount).minus(allocation.amountInGoalCurrency);
await this.prisma.goal.update({
where: { id: goalId },
data: { currentAmount: newCurrentAmount.greaterThanOrEqualTo(0) ? newCurrentAmount : new library_1.Decimal(0) },
});
await this.prisma.wallet.update({
where: { id: allocation.walletId },
data: {
reservedBalance: {
decrement: allocation.amount,
},
},
});
await this.prisma.goalAllocation.delete({
where: { id: allocationId },
});
await this.updateMilestones(goalId, newCurrentAmount);
return { message: 'Allocation removed successfully' };
}
async getStats(userId) {
const goals = await this.prisma.goal.findMany({
where: { userId },
include: {
allocations: true,
},
});
const totalGoals = goals.length;
const activeGoals = goals.filter(g => g.status === 'active').length;
const completedGoals = goals.filter(g => g.status === 'completed').length;
let totalTargetAmount = new library_1.Decimal(0);
let totalCurrentAmount = new library_1.Decimal(0);
for (const goal of goals) {
if (goal.status === 'active') {
totalTargetAmount = totalTargetAmount.plus(goal.targetAmount);
totalCurrentAmount = totalCurrentAmount.plus(goal.currentAmount);
}
}
const overallProgress = totalTargetAmount.greaterThan(0)
? totalCurrentAmount.dividedBy(totalTargetAmount).times(100).toNumber()
: 0;
return {
totalGoals,
activeGoals,
completedGoals,
totalTargetAmount: totalTargetAmount.toNumber(),
totalCurrentAmount: totalCurrentAmount.toNumber(),
overallProgress: Math.round(overallProgress * 100) / 100,
};
}
async createMilestones(goalId, targetAmount) {
const percentages = [25, 50, 75, 100];
for (const percentage of percentages) {
await this.prisma.goalMilestone.create({
data: {
goalId,
percentage,
targetAmount: targetAmount.times(percentage).dividedBy(100),
},
});
}
}
async updateMilestones(goalId, currentAmount) {
const milestones = await this.prisma.goalMilestone.findMany({
where: { goalId },
orderBy: { percentage: 'asc' },
});
for (const milestone of milestones) {
if (currentAmount.greaterThanOrEqualTo(milestone.targetAmount) && !milestone.achievedAt) {
await this.prisma.goalMilestone.update({
where: { id: milestone.id },
data: { achievedAt: new Date() },
});
}
else if (currentAmount.lessThan(milestone.targetAmount) && milestone.achievedAt) {
await this.prisma.goalMilestone.update({
where: { id: milestone.id },
data: { achievedAt: null },
});
}
}
}
async getExchangeRate(fromCurrency, toCurrency) {
const rates = {
'USD': 15000,
'EUR': 16500,
'GBP': 19000,
'JPY': 100,
'SGD': 11000,
'MYR': 3500,
'IDR': 1,
};
const fromRate = rates[fromCurrency] || 1;
const toRate = rates[toCurrency] || 1;
return new library_1.Decimal(fromRate).dividedBy(toRate);
}
};
exports.GoalsService = GoalsService;
exports.GoalsService = GoalsService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
wallet_balance_service_1.WalletBalanceService])
], GoalsService);
//# sourceMappingURL=goals.service.js.map

1
apps/api/dist/goals/goals.service.js.map vendored Executable file

File diff suppressed because one or more lines are too long