diff --git a/src/app.module.ts b/src/app.module.ts index 839a053..7b3826d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -31,6 +31,7 @@ import { TicketsModule } from "./modules/tickets/tickets.module"; import { UploaderModule } from "./modules/uploader/uploader.module"; import { UsersModule } from "./modules/users/users.module"; import { UtilsModule } from "./modules/utils/utils.module"; +import { BarnameModule } from "./modules/barname/barname.module"; @Module({ imports: [ @@ -67,6 +68,7 @@ import { UtilsModule } from "./modules/utils/utils.module"; ChatbotModule, SearchModule, BillsModule, + BarnameModule ], }) export class AppModule implements NestModule { diff --git a/src/modules/barname/barname.controller.ts b/src/modules/barname/barname.controller.ts new file mode 100644 index 0000000..8180e05 --- /dev/null +++ b/src/modules/barname/barname.controller.ts @@ -0,0 +1,56 @@ +import { + Body, Controller, Delete, Get, Param, Post, + Query, UseInterceptors +} from "@nestjs/common"; +import { ApiHeader, ApiOperation } from "@nestjs/swagger"; + +import { BarnameService } from "./barname.service"; +import { BillListQueryDto } from "./dto/bill-list-query.dto"; +import { CreateBarnameDto } from "./dto/create-barname.dto"; +import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; +import { BusinessDec } from "../../common/decorators/business.decorator"; +import { ParamDto } from "../../common/DTO/param.dto"; +import { BusinessInterceptor } from "../../core/interceptors/business.interceptor"; +import { Business } from "../businesses/entities/business.entity"; +import { UserDec } from "../../common/decorators/user.decorator"; + +@Controller("barname") +@UseInterceptors(BusinessInterceptor) +@AuthGuards() +export class BarnameController { + constructor(private readonly billsService: BarnameService) { } + + @ApiOperation({ summary: "Create water bill" }) + @Post("water") + @ApiHeader({ name: "x-business-id" }) + createWaterBill(@Body() dto: CreateBarnameDto, @BusinessDec() business: Business, @UserDec("id") userId: string,) { + return this.billsService.createBarname(dto, business, userId); + } + + + @ApiOperation({ summary: "Create" }) + @ApiHeader({ name: "x-business-id" }) + @Post("charge") + create(@Body() dto: CreateBarnameDto, @BusinessDec() business: Business, @UserDec("id") userId: string) { + return this.billsService.createBarname(dto, business,userId); + } + + @ApiOperation({ summary: "Get all bills with filters and pagination" }) + @Get() + @ApiHeader({ name: "x-business-id" }) + findAll(@Query() queryDto: BillListQueryDto, @BusinessDec("id") businessId: string) { + return this.billsService.findAll(queryDto, businessId); + } + + @ApiOperation({ summary: "Get a bill by id" }) + @Get(":id") + findOne(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) { + return this.billsService.findOne(paramDto.id, businessId); + } + + @ApiOperation({ summary: "Remove a bill" }) + @Delete(":id") + remove(@Param() paramDto: ParamDto, @BusinessDec("id") businessId: string) { + return this.billsService.remove(paramDto.id, businessId); + } +} diff --git a/src/modules/barname/barname.module.ts b/src/modules/barname/barname.module.ts new file mode 100644 index 0000000..7016ee5 --- /dev/null +++ b/src/modules/barname/barname.module.ts @@ -0,0 +1,26 @@ +import { MikroOrmModule } from "@mikro-orm/nestjs"; +import { BullModule } from "@nestjs/bullmq"; +import { Module } from "@nestjs/common"; + +import { BarnameController } from "./barname.controller"; +import { BarnameService } from "./barname.service"; +import { Barname } from "./entities/barname.entity"; +import { BusinessesModule } from "../businesses/businesses.module"; +import { CompaniesModule } from "../companies/companies.module"; +import { INVOICE } from "../invoices/constants"; +import { InvoicesModule } from "../invoices/invoices.module"; +import { NotificationModule } from "../notifications/notifications.module"; + +@Module({ + imports: [ + BullModule.registerQueue({ name: INVOICE.QUEUE_NAME }), + MikroOrmModule.forFeature([Barname]), + CompaniesModule, + BusinessesModule, + NotificationModule, + InvoicesModule, + ], + controllers: [BarnameController], + providers: [BarnameService], +}) +export class BarnameModule {} diff --git a/src/modules/barname/barname.service.ts b/src/modules/barname/barname.service.ts new file mode 100644 index 0000000..d394a4b --- /dev/null +++ b/src/modules/barname/barname.service.ts @@ -0,0 +1,65 @@ +import { EntityManager } from "@mikro-orm/postgresql"; +import { BadRequestException, Injectable } from "@nestjs/common"; + +import { BillListQueryDto } from "./dto/bill-list-query.dto"; +import { CreateBarnameDto } from "./dto/create-barname.dto"; +import { Barname } from "./entities/barname.entity"; +import { BarnameRepository } from "./repositories/barname.repository"; +import { CompanyMessage } from "../../common/enums/message.enum"; +import { Business } from "../businesses/entities/business.entity"; +import { Company } from "../companies/entities/company.entity"; +// import { CompaniesService } from "../companies/services/companies.service"; + + +@Injectable() +export class BarnameService { + readonly SEWAGE_RATE = 0.3; + readonly TAX_RATE = 0.1; + + constructor( + private readonly em: EntityManager, + // private readonly companiesService: CompaniesService, + private readonly billRepository: BarnameRepository, + ) { } + + async createBarname(dto: CreateBarnameDto, business: Business, userId: string) { + const { description, driverName, driverPhone, carType, exitAt, origin, plaque, weight,attachments } = dto; + + const company = await this.em.findOne(Company, { business, user: { id: userId }, deletedAt: null }); + if (!company) throw new BadRequestException(CompanyMessage.COMPANY_NOT_FOUND); + + const bill = this.em.create(Barname, + { carType, exitAt, origin, plaque, weight, business, description, driverName, driverPhone, company,attachments }); + + await this.em.persistAndFlush(bill); + + return bill + } + + + async findAll(queryDto: BillListQueryDto, businessId: string) { + const [bills, count] = await this.billRepository.getBillsList(queryDto, businessId); + return { + bills, + count, + paginate: true, + }; + } + + async findOne(id: string, businessId: string) { + const bill = await this.billRepository.findOne({ id, business: { id: businessId } }, { populate: [] }); + if (!bill) { + throw new BadRequestException("Bill not found"); + } + return { bill }; + } + + async remove(id: string, businessId: string) { + const bill = await this.billRepository.findOne({ id, business: { id: businessId } }, { populate: [] }); + if (!bill) { + throw new BadRequestException("Bill not found"); + } + await this.em.removeAndFlush(bill); + return { deleted: true }; + } +} diff --git a/src/modules/barname/dto/bill-list-query.dto.ts b/src/modules/barname/dto/bill-list-query.dto.ts new file mode 100644 index 0000000..53ca2fd --- /dev/null +++ b/src/modules/barname/dto/bill-list-query.dto.ts @@ -0,0 +1,22 @@ +import { ApiPropertyOptional } from "@nestjs/swagger"; +import { IsDateString, IsEnum, IsOptional } from "class-validator"; + +import { PaginationDto } from "../../../common/DTO/pagination.dto"; +import { BarnameStatus } from "../enums/bill-type.enum"; + +export class BillListQueryDto extends PaginationDto { + @IsOptional() + @IsEnum(BarnameStatus) + @ApiPropertyOptional({ enum: BarnameStatus, description: "Filter by bill type" }) + status?: BarnameStatus; + + @IsOptional() + @IsDateString() + @ApiPropertyOptional({ description: "Filter bills created since this date", example: "2025-01-01" }) + since?: string; + + @IsOptional() + @IsDateString() + @ApiPropertyOptional({ description: "Filter bills created until this date", example: "2025-12-31" }) + to?: string; +} diff --git a/src/modules/barname/dto/create-barname.dto.ts b/src/modules/barname/dto/create-barname.dto.ts new file mode 100644 index 0000000..4ad426a --- /dev/null +++ b/src/modules/barname/dto/create-barname.dto.ts @@ -0,0 +1,50 @@ +import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; +import { IsArray, IsDateString, IsNotEmpty, IsOptional, IsString, } from "class-validator"; + + +export class CreateBarnameDto { + @ApiProperty({ description: "Barname title" }) + @IsString() + @IsNotEmpty() + plaque: string; + + @ApiProperty({ description: "Barname description" }) + @IsString() + @IsNotEmpty() + description: string; + + @ApiProperty() + @IsString() + driverName: string; + + @ApiProperty() + @IsString() + driverPhone: string; + + @ApiPropertyOptional() + @IsString() + @IsOptional() + origin: string; + + @ApiPropertyOptional() + @IsString() + @IsOptional() + weight: string; + + @ApiPropertyOptional() + @IsString() + @IsOptional() + carType: string; + + @ApiPropertyOptional() + @IsString({each:true}) + @IsArray() + attachments: string[]; + + + @ApiProperty() + @IsDateString() + exitAt: string; + +} + diff --git a/src/modules/barname/dto/update-bill.dto.ts b/src/modules/barname/dto/update-bill.dto.ts new file mode 100644 index 0000000..b1239a5 --- /dev/null +++ b/src/modules/barname/dto/update-bill.dto.ts @@ -0,0 +1,5 @@ +import { PartialType } from "@nestjs/swagger"; + +import { CreateBarnameDto } from "./create-barname.dto"; + +export class UpdateBillDto extends PartialType(CreateBarnameDto) {} diff --git a/src/modules/barname/entities/barname.entity.ts b/src/modules/barname/entities/barname.entity.ts new file mode 100644 index 0000000..5b248c9 --- /dev/null +++ b/src/modules/barname/entities/barname.entity.ts @@ -0,0 +1,52 @@ +import { Entity, EntityRepositoryType, ManyToOne, Opt, Property } from "@mikro-orm/core"; + +import { BaseEntity } from "../../../common/entities/base.entity"; +import { Business } from "../../businesses/entities/business.entity"; +import { BarnameRepository } from "../repositories/barname.repository"; +import { Company } from "../../companies/entities/company.entity"; +import { BarnameStatus } from "../enums/bill-type.enum"; + +@Entity({ repository: () => BarnameRepository }) +export class Barname extends BaseEntity { + @ManyToOne(() => Business, { nullable: false, deleteRule: "cascade" }) + business!: Business; + + @ManyToOne(() => Company, { nullable: false, deleteRule: "cascade" }) + company!: Company; + + @Property({ type: 'numeric', autoincrement: true }) + number!: number & Opt; + + @Property({ type: 'string' }) + driverName: string; + + @Property({ type: 'string' }) + driverPhone: string; + + @Property({ type: 'string' }) + origin: string; + + @Property({ type: 'string' }) + weight: string; + + @Property({ type: 'string' }) + carType: string; + + @Property({ type: 'string' }) + plaque: string; + + @Property({ type: 'string' }) + description: string; + + + @Property({ type: 'json' }) + attachments: string[]; + + @Property({ nullable: true }) + exitAt?: Date; + + @Property() + status: BarnameStatus & Opt + + [EntityRepositoryType]?: BarnameRepository; +} diff --git a/src/modules/barname/enums/bill-type.enum.ts b/src/modules/barname/enums/bill-type.enum.ts new file mode 100755 index 0000000..d20df6a --- /dev/null +++ b/src/modules/barname/enums/bill-type.enum.ts @@ -0,0 +1,4 @@ +export enum BarnameStatus { + PENDING='pending', + COMPLETED='completed' +} diff --git a/src/modules/barname/repositories/barname.repository.ts b/src/modules/barname/repositories/barname.repository.ts new file mode 100755 index 0000000..7acfe7b --- /dev/null +++ b/src/modules/barname/repositories/barname.repository.ts @@ -0,0 +1,58 @@ +import { EntityRepository } from "@mikro-orm/postgresql"; + +import { PaginationUtils } from "../../utils/providers/pagination.utils"; +import { BillListQueryDto } from "../dto/bill-list-query.dto"; +import { Barname } from "../entities/barname.entity"; + +export class BarnameRepository extends EntityRepository { + async getBillsList(queryDto: BillListQueryDto, businessId: string) { + const { limit, skip } = PaginationUtils(queryDto); + + const knex = this.em.getKnex(); + const query = knex + .select("b.*") + .select(knex.raw("COUNT(i.id) as invoice_count")) + .from("bill as b") + .leftJoin("invoice as i", "i.bill_id", "b.id") + .where("b.business_id", businessId) + .groupBy("b.id") + .orderBy("b.created_at", "desc") + .limit(limit) + .offset(skip); + + // Add filters + if (queryDto.status) { + query.andWhere("b.status", queryDto.status); + } + + if (queryDto.since) { + query.andWhere("b.created_at", ">=", new Date(queryDto.since)); + } + + if (queryDto.to) { + query.andWhere("b.created_at", "<=", new Date(queryDto.to)); + } + + // Execute the query + const barname = await query; + + // Get total count for pagination + const countQuery = knex("bill").count("* as total").where("business_id", businessId); + + + if (queryDto.since) { + countQuery.andWhere("created_at", ">=", new Date(queryDto.since)); + } + + if (queryDto.to) { + countQuery.andWhere("created_at", "<=", new Date(queryDto.to)); + } + + const totalResult = await countQuery; + const total = Number(totalResult[0].total); + + + + return [barname, total]; + } +}