Project Initialization

This commit is contained in:
Muluhabt
2026-05-12 15:17:16 +03:00
parent 33fa742e8a
commit 3b8b6979db
259 changed files with 15962 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
import { Body, Controller, Delete, Get, HttpCode, Param, ParseUUIDPipe, Post, Query } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { BookingsService } from './bookings.service';
import { CreateBookingDto } from './dto/create-booking.dto';
import { FilterBookingDto } from './dto/filter-booking.dto';
@ApiTags('bookings')
// @UseGuards(JwtAuthGuard) — TODO: integrate @edr/auth
@Controller('bookings')
export class BookingsController {
constructor(private readonly bookingsService: BookingsService) {}
@Post()
@ApiOperation({ summary: 'Create a new freight booking' })
create(@Body() dto: CreateBookingDto) {
return this.bookingsService.create(dto);
}
@Get()
@ApiOperation({ summary: 'List freight bookings (paginated)' })
findAll(@Query() filter: FilterBookingDto) {
return this.bookingsService.findAll(filter);
}
@Get(':id')
@ApiOperation({ summary: 'Get a freight booking by ID' })
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.bookingsService.findById(id);
}
@Delete(':id')
@HttpCode(204)
@ApiOperation({ summary: 'Soft-delete a freight booking' })
remove(@Param('id', ParseUUIDPipe) id: string) {
return this.bookingsService.remove(id);
}
}

View File

@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BookingsController } from './bookings.controller';
import { BookingsRepository } from './bookings.repository';
import { BookingsService } from './bookings.service';
import { Booking } from './entities/booking.entity';
@Module({
imports: [TypeOrmModule.forFeature([Booking])],
controllers: [BookingsController],
providers: [BookingsService, BookingsRepository],
exports: [BookingsService],
})
export class BookingsModule {}

View File

@@ -0,0 +1,21 @@
import { BaseRepository } from '@edr/api-common';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Booking } from './entities/booking.entity';
@Injectable()
export class BookingsRepository extends BaseRepository<Booking> {
constructor(
@InjectRepository(Booking)
repository: Repository<Booking>,
) {
super(repository);
}
/** Find a booking by its human-readable reference number. */
findByReference(reference: string): Promise<Booking | null> {
return this.repository.findOne({ where: { reference } });
}
}

View File

@@ -0,0 +1,50 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { BookingsRepository } from './bookings.repository';
import { CreateBookingDto } from './dto/create-booking.dto';
import { FilterBookingDto } from './dto/filter-booking.dto';
import { Booking } from './entities/booking.entity';
@Injectable()
export class BookingsService {
constructor(private readonly bookingsRepository: BookingsRepository) {}
/** Create a new freight booking. */
async create(dto: CreateBookingDto): Promise<Booking> {
return this.bookingsRepository.create({
...dto,
scheduledDate: new Date(dto.scheduledDate),
});
}
/** Return a paginated list of bookings matching the filter. */
async findAll(filter: FilterBookingDto): Promise<{ items: Booking[]; total: number }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 20;
const [items, total] = await this.bookingsRepository.findAndCount({
where: {
...(filter.status ? { status: filter.status } : {}),
...(filter.customerId ? { customerId: filter.customerId } : {}),
},
skip: (page - 1) * pageSize,
take: pageSize,
order: { createdAt: 'DESC' },
});
return { items, total };
}
/** Get a single booking by ID, throwing if not found. */
async findById(id: string): Promise<Booking> {
const booking = await this.bookingsRepository.findById(id);
if (!booking) {
throw new NotFoundException(`Booking ${id} not found`);
}
return booking;
}
/** Soft-delete a booking. */
async remove(id: string): Promise<void> {
await this.findById(id);
await this.bookingsRepository.softDelete(id);
}
}

View File

@@ -0,0 +1,33 @@
import { Freight } from '@edr/types';
import {
IsDateString,
IsEnum,
IsNumber,
IsOptional,
IsString,
IsUUID,
Min,
} from 'class-validator';
export class CreateBookingDto {
@IsString()
reference!: string;
@IsUUID()
customerId!: string;
@IsOptional()
@IsUUID()
trainId?: string;
@IsDateString()
scheduledDate!: string;
@IsNumber()
@Min(0)
totalAmount!: number;
@IsOptional()
@IsEnum(Freight.BookingStatus)
status?: Freight.BookingStatus;
}

View File

@@ -0,0 +1,25 @@
import { Freight } from '@edr/types';
import { Type } from 'class-transformer';
import { IsEnum, IsInt, IsOptional, IsUUID, Min } from 'class-validator';
export class FilterBookingDto {
@IsOptional()
@IsEnum(Freight.BookingStatus)
status?: Freight.BookingStatus;
@IsOptional()
@IsUUID()
customerId?: string;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number = 1;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
pageSize?: number = 20;
}

View File

@@ -0,0 +1,37 @@
import { BaseEntity } from '@edr/api-common';
import { Freight } from '@edr/types';
import { Column, Entity } from 'typeorm';
@Entity({ name: 'bookings' })
export class Booking extends BaseEntity {
@Column({ name: 'reference', type: 'varchar', length: 64, unique: true })
reference!: string;
@Column({ name: 'customer_id', type: 'uuid' })
customerId!: string;
@Column({ name: 'train_id', type: 'uuid', nullable: true })
trainId?: string | null;
@Column({
name: 'status',
type: 'enum',
enum: Freight.BookingStatus,
default: Freight.BookingStatus.Draft,
})
status!: Freight.BookingStatus;
@Column({ name: 'scheduled_date', type: 'timestamptz' })
scheduledDate!: Date;
@Column({ name: 'total_amount', type: 'numeric', precision: 14, scale: 2, default: 0 })
totalAmount!: number;
@Column({
name: 'payment_status',
type: 'enum',
enum: Freight.PaymentStatus,
default: Freight.PaymentStatus.Pending,
})
paymentStatus!: Freight.PaymentStatus;
}