diff --git a/src/modules/admin/controllers/admin.controller.ts b/src/modules/admin/controllers/admin.controller.ts index 1eb7500..480d517 100644 --- a/src/modules/admin/controllers/admin.controller.ts +++ b/src/modules/admin/controllers/admin.controller.ts @@ -51,6 +51,18 @@ export class AdminController { return this.adminService.getDashboardCounts(); } + @Get('admin/home/stats') + @ApiOperation({ summary: 'Get home page stats for admin panel' }) + getHomeStats() { + return this.adminService.getHomeStats(); + } + + @Get('admin/home/weekly-orders') + @ApiOperation({ summary: 'Get weekly order counts for admin home page chart' }) + getWeeklyOrders() { + return this.adminService.getWeeklyOrders(); + } + @Patch('admin/admins/:adminId') @Permissions(PermissionEnum.MANAGE_ADMINS) diff --git a/src/modules/admin/providers/admin.service.ts b/src/modules/admin/providers/admin.service.ts index f019506..f40a0fc 100644 --- a/src/modules/admin/providers/admin.service.ts +++ b/src/modules/admin/providers/admin.service.ts @@ -9,8 +9,11 @@ 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 { @@ -172,4 +175,73 @@ export class AdminService { 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, { confirmStatus: 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, + }; + } } diff --git a/src/modules/invoice/entities/invoice-item.entity.ts b/src/modules/invoice/entities/invoice-item.entity.ts index d7ea88b..5d449a2 100644 --- a/src/modules/invoice/entities/invoice-item.entity.ts +++ b/src/modules/invoice/entities/invoice-item.entity.ts @@ -1,4 +1,4 @@ -import { Collection, Entity, ManyToOne, OptionalProps, Property, OneToMany, Opt, Index } from '@mikro-orm/core'; +import { Entity, ManyToOne, OptionalProps, Property, OneToOne, Opt, Index } from '@mikro-orm/core'; import { Product } from 'src/modules/product/entities/product.entity'; import { BaseEntity } from 'src/common/entities/base.entity'; import { Invoice } from './invoice.entity'; @@ -16,8 +16,8 @@ export class InvoiceItem extends BaseEntity { @ManyToOne(() => Product) product!: Product; - @OneToMany(() => Order, order => order.invoiceItem) - orders = new Collection(this); + @OneToOne(() => Order, order => order.invoiceItem, { mappedBy: 'invoiceItem' }) + order?: Order; @Property({ type: 'int' }) diff --git a/src/modules/order/entities/order.entity.ts b/src/modules/order/entities/order.entity.ts index a7292a1..9408161 100644 --- a/src/modules/order/entities/order.entity.ts +++ b/src/modules/order/entities/order.entity.ts @@ -28,7 +28,7 @@ import { OrderDesigner } from './order-designer.entity'; export class Order extends BaseEntity { [OptionalProps]?: 'createdAt' | 'deletedAt' | 'orderNumber' - @ManyToOne(() => InvoiceItem, { nullable: true }) + @OneToOne(() => InvoiceItem, (invoiceItem) => invoiceItem.order, { owner: true, nullable: true }) invoiceItem?: InvoiceItem @ManyToOne(() => Product, { nullable: true })