253 lines
7.6 KiB
TypeScript
253 lines
7.6 KiB
TypeScript
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<Admin | null> {
|
|
return this.adminRepository.findOne({ phone });
|
|
}
|
|
|
|
async findById(adminId: string): Promise<Admin | null> {
|
|
const admin = await this.adminRepository.findOne(
|
|
{ id: adminId },
|
|
{ populate: ['role', 'role.permissions'] },
|
|
);
|
|
return admin;
|
|
}
|
|
|
|
async findAll(): Promise<Admin[]> {
|
|
return this.adminRepository.find({}, { populate: ['role'] });
|
|
}
|
|
|
|
async updateMe(adminId: string, dto: UpdateAdminMeDto): Promise<Admin> {
|
|
return this.update(adminId, dto);
|
|
}
|
|
|
|
async update(adminId: string, dto: UpdateAdminDto): Promise<Admin> {
|
|
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<void> {
|
|
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<PermissionEnum[]> {
|
|
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<string, number>();
|
|
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,
|
|
};
|
|
}
|
|
}
|