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
7 changes: 7 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import { AssetsModule } from './assets/assets.module';
import { QueueModule } from './queue/queue.module';
import { StorageModule } from './storage/storage.module';
import { CacheService } from './cache/cache.service';
import { InventoryModule } from './inventory/inventory.module';
import { VendorsModule } from './vendors/vendors.module';
import { DashboardModule } from './assets/dashboard.module';
import { ReportingModule } from './reporting/reporting.module';
import { ActivityLogModule } from './activity-log/activity-log.module';
import { LocationsModule } from './locations/locations.module';
Expand Down Expand Up @@ -86,6 +89,10 @@ import { TasksModule } from './tasks/tasks.module';
LocationsModule,
],
ActivityLogModule,
],
InventoryModule,
VendorsModule,
DashboardModule,
],
controllers: [AppController],
providers: [
Expand Down
3 changes: 2 additions & 1 deletion backend/src/assets/assets-ops.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import { Asset } from './entities/asset.entity';
import { AssetNote } from './entities/asset-note.entity';
import { AssetsOpsService } from './assets-ops.service';
import { AssetsOpsController } from './assets-ops.controller';
import { BulkController } from './bulk.controller';

@Module({
imports: [TypeOrmModule.forFeature([Asset, AssetNote])],
controllers: [AssetsOpsController],
controllers: [AssetsOpsController, BulkController],
providers: [AssetsOpsService],
exports: [AssetsOpsService],
})
Expand Down
44 changes: 44 additions & 0 deletions backend/src/assets/bulk.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Controller, Post, Body, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Asset } from './asset.entity';

@Controller('assets/bulk')
@UseGuards(AuthGuard('jwt'))
export class BulkController {
constructor(
@InjectRepository(Asset)
private readonly assetRepository: Repository<Asset>,
) {}

@Post('delete')
async bulkDelete(@Body() dto: { ids: string[] }) {
const { ids } = dto;
if (!ids || ids.length === 0) {
return { message: 'No ids provided' };
}
await this.assetRepository.softDelete(ids);
return { message: `${ids.length} assets deleted successfully` };
}

@Post('status')
async bulkStatusUpdate(@Body() dto: { ids: string[]; status: string }) {
const { ids, status } = dto;
if (!ids || ids.length === 0) {
return { message: 'No ids provided' };
}
await this.assetRepository.update(ids, { status });
return { message: `${ids.length} assets updated successfully` };
}

@Post('transfer')
async bulkTransfer(@Body() dto: { ids: string[]; assignedToId: string }) {
const { ids, assignedToId } = dto;
if (!ids || ids.length === 0) {
return { message: 'No ids provided' };
}
await this.assetRepository.update(ids, { assignedToId });
return { message: `${ids.length} assets transferred successfully` };
}
}
62 changes: 62 additions & 0 deletions backend/src/assets/dashboard.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Asset } from './asset.entity';

@Controller('dashboard')
@UseGuards(AuthGuard('jwt'))
export class DashboardController {
constructor(
@InjectRepository(Asset)
private readonly assetRepository: Repository<Asset>,
) {}

@Get('stats')
async getStats() {
const total = await this.assetRepository.count();
const byStatus = await this.assetRepository
.createQueryBuilder('asset')
.select('asset.status', 'status')
.addSelect('COUNT(asset.id)', 'count')
.groupBy('asset.status')
.getRawMany();
const byCondition = await this.assetRepository
.createQueryBuilder('asset')
.select('asset.condition', 'condition')
.addSelect('COUNT(asset.id)', 'count')
.groupBy('asset.condition')
.getRawMany();
const recentAssets = await this.assetRepository.find({
order: { createdAt: 'DESC' },
take: 10,
});
const totalValue = await this.assetRepository
.createQueryBuilder('asset')
.select('SUM(asset.purchasePrice)', 'total')
.getRawOne();

return {
total,
byStatus,
byCondition,
totalValue: totalValue?.total || 0,
recentAssets,
};
}

@Get('summary')
async getSummary() {
const totalAssets = await this.assetRepository.count();
const activeAssets = await this.assetRepository.count({ where: { status: 'ACTIVE' } });
const maintenanceAssets = await this.assetRepository.count({ where: { status: 'MAINTENANCE' } });
const retiredAssets = await this.assetRepository.count({ where: { status: 'RETIRED' } });

return {
totalAssets,
activeAssets,
maintenanceAssets,
retiredAssets,
};
}
}
10 changes: 10 additions & 0 deletions backend/src/assets/dashboard.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Asset } from './asset.entity';
import { DashboardController } from './dashboard.controller';

@Module({
imports: [TypeOrmModule.forFeature([Asset])],
controllers: [DashboardController],
})
export class DashboardModule {}
39 changes: 39 additions & 0 deletions backend/src/inventory/dtos/create-inventory.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { IsString, IsOptional, IsNumber, IsInt, Min } from 'class-validator';

export class CreateInventoryDto {
@IsOptional()
@IsString()
assetId?: string;

@IsOptional()
@IsString()
categoryId?: string;

@IsOptional()
@IsInt()
@Min(0)
quantity?: number;

@IsOptional()
@IsNumber()
@Min(0)
unitPrice?: number;

@IsOptional()
@IsInt()
@Min(0)
reorderLevel?: number;

@IsOptional()
@IsInt()
@Min(0)
reorderQuantity?: number;

@IsOptional()
@IsString()
location?: string;

@IsOptional()
@IsString()
notes?: string;
}
44 changes: 44 additions & 0 deletions backend/src/inventory/dtos/inventory-query.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { IsOptional, IsString, IsBoolean, IsInt, Min, Max } from 'class-validator';
import { Type } from 'class-transformer';

export class InventoryQueryDto {
@IsOptional()
@IsString()
categoryId?: string;

@IsOptional()
@IsString()
location?: string;

@IsOptional()
@IsString()
search?: string;

@IsOptional()
@Type(() => Boolean)
@IsBoolean()
lowStock?: boolean;

@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
sortBy?: string;

@IsOptional()
@IsString()
sortOrder?: 'ASC' | 'DESC';

@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number = 1;

@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100)
limit?: number = 20;
}
39 changes: 39 additions & 0 deletions backend/src/inventory/dtos/update-inventory.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { IsString, IsOptional, IsNumber, IsInt, Min } from 'class-validator';

export class UpdateInventoryDto {
@IsOptional()
@IsString()
assetId?: string;

@IsOptional()
@IsString()
categoryId?: string;

@IsOptional()
@IsInt()
@Min(0)
quantity?: number;

@IsOptional()
@IsNumber()
@Min(0)
unitPrice?: number;

@IsOptional()
@IsInt()
@Min(0)
reorderLevel?: number;

@IsOptional()
@IsInt()
@Min(0)
reorderQuantity?: number;

@IsOptional()
@IsString()
location?: string;

@IsOptional()
@IsString()
notes?: string;
}
45 changes: 45 additions & 0 deletions backend/src/inventory/entities/inventory.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn } from 'typeorm';
import { Asset } from '../../assets/asset.entity';

@Entity('inventory')
export class Inventory {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ nullable: true })
assetId: string;

@ManyToOne(() => Asset, { nullable: true })
@JoinColumn({ name: 'assetId' })
asset: Asset;

@Column({ nullable: true })
categoryId: string;

@Column({ default: 0 })
quantity: number;

@Column({ type: 'decimal', precision: 12, scale: 2, nullable: true })
unitPrice: number;

@Column({ type: 'decimal', precision: 12, scale: 2, nullable: true })
totalValue: number;

@Column({ nullable: true })
reorderLevel: number;

@Column({ nullable: true })
reorderQuantity: number;

@Column({ nullable: true })
location: string;

@Column({ nullable: true, type: 'text' })
notes: string;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
38 changes: 38 additions & 0 deletions backend/src/inventory/inventory.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Controller, Get, Post, Put, Delete, Param, Body, Query, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { InventoryService } from './inventory.service';
import { CreateInventoryDto } from './dtos/create-inventory.dto';
import { UpdateInventoryDto } from './dtos/update-inventory.dto';
import { InventoryQueryDto } from './dtos/inventory-query.dto';

@Controller('inventory')
@UseGuards(AuthGuard('jwt'))
export class InventoryController {
constructor(private readonly inventoryService: InventoryService) {}

@Post()
async create(@Body() dto: CreateInventoryDto) {
return this.inventoryService.create(dto);
}

@Get()
async findAll(@Query() query: InventoryQueryDto) {
return this.inventoryService.findAll(query);
}

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

@Put(':id')
async update(@Param('id') id: string, @Body() dto: UpdateInventoryDto) {
return this.inventoryService.update(id, dto);
}

@Delete(':id')
async remove(@Param('id') id: string) {
await this.inventoryService.remove(id);
return { message: 'Inventory item deleted successfully' };
}
}
13 changes: 13 additions & 0 deletions backend/src/inventory/inventory.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Inventory } from './entities/inventory.entity';
import { InventoryService } from './inventory.service';
import { InventoryController } from './inventory.controller';

@Module({
imports: [TypeOrmModule.forFeature([Inventory])],
controllers: [InventoryController],
providers: [InventoryService],
exports: [InventoryService],
})
export class InventoryModule {}
Loading
Loading