import { BadRequestException, ConflictException, Injectable, NotFoundException } from '@nestjs/common'; import { Admin } from '../entities/admin.entity'; import { Role } from '../../roles/entities/role.entity'; import { EntityManager } from '@mikro-orm/postgresql'; import { UpdateAdminDto } from '../dto/update-admin.dto'; import { UpdateAdminMeDto } from '../dto/update-admin-me.dto'; import { CreateAdminDto } from '../dto/create-admin.dto'; import { AdminRepository } from '../repositories/admin.repository'; import { RoleRepository } from 'src/modules/roles/respository/role.repository'; import { PermissionEnum } from 'src/common/enums/permission.enum'; import { Request } from 'src/modules/request/entities/request.entity'; import { Invoice } from 'src/modules/invoice/entities/invoice.entity'; import { InvoiceConfirmStatusEnum } from 'src/modules/invoice/enum/invoice-confirm-status.enum'; import { Order } from 'src/modules/order/entities/order.entity'; import { OrderStatusEnum } from 'src/modules/order/interface/order.interface'; import { Payment } from 'src/modules/payment/entities/payment.entity'; import { User } from 'src/modules/user/entities/user.entity'; @Injectable() export class AdminService { constructor( private readonly adminRepository: AdminRepository, private readonly roleRepository: RoleRepository, private readonly em: EntityManager, ) { } async create(dto: CreateAdminDto) { const { phone, firstName, lastName, roleId, avatarUrl } = dto; const currentAdmin = await this.adminRepository.findOne({ phone, }, { filters: { notDeleted: false } }); if (currentAdmin && !currentAdmin.deletedAt) { throw new BadRequestException('This phone number is already used'); } if (currentAdmin && currentAdmin.deletedAt) { currentAdmin.deletedAt = undefined; await this.em.flush(); return currentAdmin; } const role = await this.roleRepository.findOne({ id: roleId }); if (!role) { throw new BadRequestException('Role not found'); } const admin = this.em.create(Admin, { phone, firstName, lastName, avatarUrl, role, }); await this.em.persistAndFlush(admin); return admin; } async findByPhone(phone: string): Promise { return this.adminRepository.findOne({ phone }); } async findById(adminId: string): Promise { const admin = await this.adminRepository.findOne( { id: adminId }, { populate: ['role', 'role.permissions'] }, ); return admin; } async findAll(): Promise { return this.adminRepository.find({}, { populate: ['role'] }); } async updateMe(adminId: string, dto: UpdateAdminMeDto): Promise { return this.update(adminId, dto); } async update(adminId: string, dto: UpdateAdminDto): Promise { const admin = await this.adminRepository.findOne( { id: adminId }, { populate: ['role'] }, ); if (!admin) { throw new NotFoundException('Admin not found'); } const { roleId, ...rest } = dto; // Update scalar fields (firstName, lastName, phone, avatarUrl) if (rest.firstName !== undefined) { admin.firstName = rest.firstName; } if (rest.lastName !== undefined) { admin.lastName = rest.lastName; } if (rest.avatarUrl !== undefined) { admin.avatarUrl = rest.avatarUrl; } if (rest.phone !== undefined && rest.phone !== admin.phone) { const exists = await this.adminRepository.findOne({ phone: rest.phone,id: { $ne: adminId } }); if (exists) { throw new ConflictException('This Phone Number is already taken'); } admin.phone = rest.phone; } // Update role if roleId is provided if (roleId) { const role = await this.em.findOne(Role, { id: roleId }); if (!role) { throw new NotFoundException('Role not found'); } admin.role = role } await this.em.persistAndFlush(admin); return admin; } async softDelete(adminId: string): Promise { const admin = await this.adminRepository.findOne({ id: adminId }); if (!admin) { throw new NotFoundException('Admin role not found'); } admin.deletedAt = new Date() return this.em.flush(); } async findOrFail(adminId: string) { const admin = await this.findById(adminId) if (!admin) { throw new BadRequestException("Admin not found") } return admin } async findByIds(adminIds: string[]) { const admins = await this.adminRepository.find({ id: { $in: adminIds } }) if (adminIds.length !== admins.length) { throw new BadRequestException("some admins not found") } return admins } async ownedPermissions(adminId: string, permissionNames: PermissionEnum[]): Promise { if (permissionNames.length === 0) return [] const admin = await this.adminRepository.findOne( { id: adminId }, { populate: ['role', 'role.permissions'] }, ) if (!admin?.role?.permissions) return [] const adminPermissionNames = new Set( admin.role.permissions.getItems().map((p) => p.name), ) return permissionNames.filter((name) => adminPermissionNames.has(name)) } async getDashboardCounts() { const [requestsCount, invoicesCount, ordersCount, paymentsCount] = await Promise.all([ this.em.count(Request), this.em.count(Invoice), this.em.count(Order), this.em.count(Payment), ]); return { requestsCount, invoicesCount, ordersCount, paymentsCount, }; } async getWeeklyOrders(weeksCount = 8) { const knex = this.em.getConnection().getKnex(); const startDate = this.getWeekStartMonday(new Date()); startDate.setDate(startDate.getDate() - (weeksCount - 1) * 7); const rows = (await knex('orders') .select(knex.raw("date_trunc('week', created_at) as week_start")) .count('* as count') .whereNull('deleted_at') .where('created_at', '>=', startDate) .groupBy('week_start') .orderBy('week_start', 'asc')) as unknown as Array<{ week_start: Date; count: string }>; const countByWeek = new Map(); for (const row of rows) { const weekStart = this.getWeekStartMonday(new Date(row.week_start)); countByWeek.set(weekStart.toISOString(), Number(row.count)); } const result: Array<{ weekStart: string; count: number }> = []; const cursor = new Date(startDate); for (let i = 0; i < weeksCount; i++) { const weekStart = this.getWeekStartMonday(cursor); result.push({ weekStart: weekStart.toISOString(), count: countByWeek.get(weekStart.toISOString()) ?? 0, }); cursor.setDate(cursor.getDate() + 7); } return result; } private getWeekStartMonday(date: Date): Date { const d = new Date(date); d.setHours(0, 0, 0, 0); const day = d.getDay(); const diff = day === 0 ? -6 : 1 - day; d.setDate(d.getDate() + diff); return d; } async getHomeStats() { const [ requestsCount, unconfirmedInvoicesCount, inProgressOrdersCount, completedOrdersCount, customersCount, ] = await Promise.all([ this.em.count(Request), this.em.count(Invoice, { status: InvoiceConfirmStatusEnum.PENDING }), this.em.count(Order, { status: { $nin: [OrderStatusEnum.FINISHED, OrderStatusEnum.CANCELED] }, }), this.em.count(Order, { status: OrderStatusEnum.FINISHED }), this.em.count(User), ]); return { requestsCount, unconfirmedInvoicesCount, inProgressOrdersCount, completedOrdersCount, customersCount, }; } }