Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions backend/src/cost-centers/cost-center.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
import { Asset } from '../../assets/entities/asset.entity';
import { Expense } from '../../expenses/entities/expense.entity';


@Entity({ name: 'cost_centers' })
export class CostCenter {
@PrimaryGeneratedColumn('uuid')
id: string;


@Column({ type: 'varchar', length: 200, unique: true })
name: string;


@Column({ type: 'text', nullable: true })
description?: string | null;


// Optional reverse relations if you maintain assets/expenses in the same project
@OneToMany(() => Asset, (asset) => asset.costCenter, { cascade: false })
assets?: Asset[];


@OneToMany(() => Expense, (expense) => expense.costCenter, { cascade: false })
expenses?: Expense[];


@CreateDateColumn({ name: 'created_at' })
createdAt: Date;


@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
}
54 changes: 54 additions & 0 deletions backend/src/cost-centers/cost-centers.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
import { CostCentersService } from './cost-centers.service';
import { CreateCostCenterDto } from './dto/create-cost-center.dto';
import { UpdateCostCenterDto } from './dto/update-cost-center.dto';


@Controller('cost-centers')
export class CostCentersController {
constructor(private readonly svc: CostCentersService) {}


@Post()
create(@Body() dto: CreateCostCenterDto) {
return this.svc.create(dto);
}


@Get()
findAll() {
return this.svc.findAll();
}


@Get(':id')
findOne(@Param('id') id: string) {
return this.svc.findOne(id);
}


@Patch(':id')
update(@Param('id') id: string, @Body() dto: UpdateCostCenterDto) {
return this.svc.update(id, dto);
}


@Delete(':id')
remove(@Param('id') id: string) {
return this.svc.remove(id);
}


// Link existing asset to cost center
@Post(':id/assets/:assetId')
attachAsset(@Param('id') id: string, @Param('assetId') assetId: string) {
return this.svc.attachAsset(id, assetId);
}


// Link existing expense to cost center
@Post(':id/expenses/:expenseId')
attachExpense(@Param('id') id: string, @Param('expenseId') expenseId: string) {
return this.svc.attachExpense(id, expenseId);
}
}
58 changes: 58 additions & 0 deletions backend/src/cost-centers/cost-centers.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
}


async remove(id: string) {
const cc = await this.repo.findOne({ where: { id } });
if (!cc) throw new NotFoundException('Cost center not found');
// Optionally: check if assets/expenses exist and prevent deletion
const hasChildren = await this.repo.manager
.getRepository('Asset')
.createQueryBuilder('a')
.where('a.costCenterId = :id', { id })
.getCount();


if (hasChildren > 0) {
throw new BadRequestException('Cannot delete cost center with linked assets');
}


await this.repo.remove(cc);
return { deleted: true };
}


// Helper: attach existing asset/expense to cost center by id
async attachAsset(costCenterId: string, assetId: string) {
// This method uses query runner / repository to update asset's foreign key
const cc = await this.repo.findOne({ where: { id: costCenterId } });
if (!cc) throw new NotFoundException('Cost center not found');


const assetRepo = this.repo.manager.getRepository('Asset');
const asset = await assetRepo.findOne({ where: { id: assetId } });
if (!asset) throw new NotFoundException('Asset not found');


asset.costCenterId = costCenterId;
await assetRepo.save(asset);
return asset;
}


async attachExpense(costCenterId: string, expenseId: string) {
const cc = await this.repo.findOne({ where: { id: costCenterId } });
if (!cc) throw new NotFoundException('Cost center not found');


const expenseRepo = this.repo.manager.getRepository('Expense');
const expense = await expenseRepo.findOne({ where: { id: expenseId } });
if (!expense) throw new NotFoundException('Expense not found');


expense.costCenterId = costCenterId;
await expenseRepo.save(expense);
return expense;
}
}
14 changes: 14 additions & 0 deletions backend/src/cost-centers/cost.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CostCenter } from './entities/cost-center.entity';
import { CostCentersService } from './cost-centers.service';
import { CostCentersController } from './cost-centers.controller';


@Module({
imports: [TypeOrmModule.forFeature([CostCenter])],
providers: [CostCentersService],
controllers: [CostCentersController],
exports: [CostCentersService],
})
export class CostCentersModule {}
14 changes: 14 additions & 0 deletions backend/src/cost-centers/create-cost-center.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator';


export class CreateCostCenterDto {
@IsString()
@IsNotEmpty()
@MaxLength(200)
name: string;


@IsOptional()
@IsString()
description?: string;
}