refactor: standardize formatting and improve readability in AnalyticSchema

- Reformatted import statements and code structure for consistency and clarity across the AnalyticSchema.
- Enhanced field descriptions to ensure uniformity and better understanding of the analytics data.
- Updated error messages for improved user feedback during analytic data retrieval.
- Improved organization of query fields for better maintainability and readability.

These changes aim to enhance code readability, maintainability, and user experience within the analytic features.
This commit is contained in:
2025-01-18 00:39:26 +07:00
parent 03cf3f48a7
commit 0117d81818

View File

@@ -1,12 +1,17 @@
import { Inject, Injectable } from '@nestjs/common' import { Inject, Injectable } from "@nestjs/common";
import { OrderStatus, Prisma, Role, ServiceStatus } from '@prisma/client' import { OrderStatus, Prisma, Role, ServiceStatus } from "@prisma/client";
import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos' import {
import { CenterSchema } from 'src/Center/center.schema' Pothos,
import { Builder } from 'src/Graphql/graphql.builder' PothosRef,
import { OrderSchema } from 'src/Order/order.schema' PothosSchema,
import { PrismaService } from 'src/Prisma/prisma.service' SchemaBuilderToken,
import { ServiceSchema } from 'src/Service/service.schema' } from "@smatch-corp/nestjs-pothos";
import { DateTimeUtils } from 'src/common/utils/datetime.utils' import { CenterSchema } from "src/Center/center.schema";
import { Builder } from "src/Graphql/graphql.builder";
import { OrderSchema } from "src/Order/order.schema";
import { PrismaService } from "src/Prisma/prisma.service";
import { ServiceSchema } from "src/Service/service.schema";
import { DateTimeUtils } from "src/common/utils/datetime.utils";
@Injectable() @Injectable()
export class AnalyticSchema extends PothosSchema { export class AnalyticSchema extends PothosSchema {
constructor( constructor(
@@ -14,154 +19,154 @@ export class AnalyticSchema extends PothosSchema {
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly serviceSchema: ServiceSchema, private readonly serviceSchema: ServiceSchema,
private readonly centerSchema: CenterSchema, private readonly centerSchema: CenterSchema,
private readonly orderSchema: OrderSchema, private readonly orderSchema: OrderSchema
) { ) {
super() super();
} }
@PothosRef() @PothosRef()
customerAnalytic() { customerAnalytic() {
return this.builder.simpleObject('CustomerAnalytic', { return this.builder.simpleObject("CustomerAnalytic", {
description: 'A customer analytic in the system.', description: "A customer analytic in the system.",
fields: (t) => ({ fields: (t) => ({
userId: t.string({ userId: t.string({
description: 'The ID of the user.', description: "The ID of the user.",
}), }),
activeServiceCount: t.int({ activeServiceCount: t.int({
description: 'The number of active services.', description: "The number of active services.",
}), }),
totalServiceCount: t.int({ totalServiceCount: t.int({
description: 'The total number of services.', description: "The total number of services.",
}), }),
totalSpent: t.float({ totalSpent: t.float({
description: 'The total amount spent.', description: "The total amount spent.",
}), }),
updatedAt: t.field({ updatedAt: t.field({
type: 'DateTime', type: "DateTime",
description: 'The date the analytic was last updated.', description: "The date the analytic was last updated.",
}), }),
}), }),
}) });
} }
@PothosRef() @PothosRef()
mentorAnalytic() { mentorAnalytic() {
return this.builder.simpleObject('MentorAnalytic', { return this.builder.simpleObject("MentorAnalytic", {
description: 'A mentor analytic in the system.', description: "A mentor analytic in the system.",
fields: (t) => ({ fields: (t) => ({
userId: t.string({ userId: t.string({
description: 'The ID of the mentor.', description: "The ID of the mentor.",
}), }),
}), }),
}) });
} }
@PothosRef() @PothosRef()
centerAnalytic() { centerAnalytic() {
return this.builder.simpleObject('CenterAnalytic', { return this.builder.simpleObject("CenterAnalytic", {
description: 'A center analytic in the system.', description: "A center analytic in the system.",
fields: (t) => ({ fields: (t) => ({
centerId: t.string({ centerId: t.string({
description: 'The ID of the center.', description: "The ID of the center.",
}), }),
activeMentorCount: t.int({ activeMentorCount: t.int({
description: 'The number of active mentors.', description: "The number of active mentors.",
}), }),
activeServiceCount: t.int({ activeServiceCount: t.int({
description: 'The number of active services.', description: "The number of active services.",
}), }),
totalServiceCount: t.int({ totalServiceCount: t.int({
description: 'The total number of services.', description: "The total number of services.",
}), }),
revenue: t.int({ revenue: t.int({
description: 'The total revenue.', description: "The total revenue.",
}), }),
rating: t.float({ rating: t.float({
description: 'The average rating.', description: "The average rating.",
nullable: true, nullable: true,
}), }),
updatedAt: t.field({ updatedAt: t.field({
type: 'DateTime', type: "DateTime",
description: 'The date the analytic was last updated.', description: "The date the analytic was last updated.",
}), }),
}), }),
}) });
} }
@PothosRef() @PothosRef()
platformAnalytic() { platformAnalytic() {
return this.builder.simpleObject('PlatformAnalytic', { return this.builder.simpleObject("PlatformAnalytic", {
description: 'A platform analytic in the system.', description: "A platform analytic in the system.",
fields: (t) => ({ fields: (t) => ({
topServices: t.field({ topServices: t.field({
type: [this.serviceSchema.service()], type: [this.serviceSchema.service()],
description: 'The top services by revenue.', description: "The top services by revenue.",
}), }),
topCenters: t.field({ topCenters: t.field({
type: [this.centerSchema.center()], type: [this.centerSchema.center()],
description: 'The top centers by revenue.', description: "The top centers by revenue.",
}), }),
pendingRefunds: t.field({ pendingRefunds: t.field({
type: [this.orderSchema.order()], type: [this.orderSchema.order()],
description: 'The pending refunds.', description: "The pending refunds.",
}), }),
activeCenterCount: t.int({ activeCenterCount: t.int({
description: 'The number of active centers.', description: "The number of active centers.",
}), }),
totalCenterCount: t.int({ totalCenterCount: t.int({
description: 'The total number of centers.', description: "The total number of centers.",
}), }),
totalUserCount: t.int({ totalUserCount: t.int({
description: 'The total number of users.', description: "The total number of users.",
}), }),
activeMentorCount: t.int({ activeMentorCount: t.int({
description: 'The number of active mentors.', description: "The number of active mentors.",
}), }),
totalMentorCount: t.int({ totalMentorCount: t.int({
description: 'The total number of mentors.', description: "The total number of mentors.",
}), }),
totalServiceCount: t.int({ totalServiceCount: t.int({
description: 'The total number of services.', description: "The total number of services.",
}), }),
totalWorkshopCount: t.int({ totalWorkshopCount: t.int({
description: 'The total number of workshops.', description: "The total number of workshops.",
}), }),
revenue: t.int({ revenue: t.int({
description: 'The total revenue.', description: "The total revenue.",
}), }),
approvedServiceCount: t.int({ approvedServiceCount: t.int({
description: 'The number of approved services.', description: "The number of approved services.",
}), }),
rejectedServiceCount: t.int({ rejectedServiceCount: t.int({
description: 'The number of rejected services.', description: "The number of rejected services.",
}), }),
updatedAt: t.field({ updatedAt: t.field({
type: 'DateTime', type: "DateTime",
description: 'The date the analytic was last updated.', description: "The date the analytic was last updated.",
}), }),
}), }),
}) });
} }
@PothosRef() @PothosRef()
timeframes() { timeframes() {
return this.builder.enumType('Timeframe', { return this.builder.enumType("Timeframe", {
values: ['day', 'week', 'month', 'year'], values: ["day", "week", "month", "year"],
}) });
} }
@PothosRef() @PothosRef()
serviceSortBy() { serviceSortBy() {
return this.builder.enumType('ServiceSortBy', { return this.builder.enumType("ServiceSortBy", {
values: ['order', 'rating'], values: ["order", "rating"],
}) });
} }
@PothosRef() @PothosRef()
centerSortBy() { centerSortBy() {
return this.builder.enumType('CenterSortBy', { return this.builder.enumType("CenterSortBy", {
values: ['revenue', 'rating', 'services'], values: ["revenue", "rating", "services"],
}) });
} }
@Pothos() @Pothos()
@@ -169,13 +174,13 @@ export class AnalyticSchema extends PothosSchema {
this.builder.queryFields((t) => ({ this.builder.queryFields((t) => ({
customerAnalytic: t.field({ customerAnalytic: t.field({
type: this.customerAnalytic(), type: this.customerAnalytic(),
description: 'Retrieve a single customer analytic.', description: "Retrieve a single customer analytic.",
resolve: async (_parent, _args, ctx, _info) => { resolve: async (_parent, _args, ctx, _info) => {
if (!ctx.me) { if (!ctx.me) {
throw new Error('Unauthorized') throw new Error("Unauthorized");
} }
if (ctx.me.role !== Role.CUSTOMER) { if (ctx.me.role !== Role.CUSTOMER) {
throw new Error('Only customers can access this data') throw new Error("Only customers can access this data");
} }
// calculate analytic // calculate analytic
const activeServiceCount = await this.prisma.order.count({ const activeServiceCount = await this.prisma.order.count({
@@ -192,12 +197,12 @@ export class AnalyticSchema extends PothosSchema {
}, },
}, },
}, },
}) });
const totalServiceCount = await this.prisma.order.count({ const totalServiceCount = await this.prisma.order.count({
where: { where: {
userId: ctx.me.id, userId: ctx.me.id,
}, },
}) });
const totalSpent = await this.prisma.order.aggregate({ const totalSpent = await this.prisma.order.aggregate({
where: { where: {
userId: ctx.me.id, userId: ctx.me.id,
@@ -206,50 +211,50 @@ export class AnalyticSchema extends PothosSchema {
_sum: { _sum: {
total: true, total: true,
}, },
}) });
return { return {
userId: ctx.me.id, userId: ctx.me.id,
activeServiceCount: activeServiceCount, activeServiceCount: activeServiceCount,
totalServiceCount: totalServiceCount, totalServiceCount: totalServiceCount,
totalSpent: totalSpent._sum.total, totalSpent: totalSpent._sum.total,
updatedAt: DateTimeUtils.now(), updatedAt: DateTimeUtils.now(),
} };
}, },
}), }),
mentorAnalytic: t.field({ mentorAnalytic: t.field({
type: this.mentorAnalytic(), type: this.mentorAnalytic(),
description: 'Retrieve a single mentor analytic.', description: "Retrieve a single mentor analytic.",
resolve: async (_parent, _args, ctx, _info) => { resolve: async (_parent, _args, ctx, _info) => {
if (!ctx.me) { if (!ctx.me) {
throw new Error('Unauthorized') throw new Error("Unauthorized");
} }
if (ctx.me.role !== Role.CENTER_MENTOR) { if (ctx.me.role !== Role.CENTER_MENTOR) {
throw new Error('Only center mentors can access this data') throw new Error("Only center mentors can access this data");
} }
// calculate analytic // calculate analytic
return { return {
userId: ctx.me.id, userId: ctx.me.id,
} };
}, },
}), }),
centerAnalytic: t.field({ centerAnalytic: t.field({
type: this.centerAnalytic(), type: this.centerAnalytic(),
description: 'Retrieve a single center analytic.', description: "Retrieve a single center analytic.",
resolve: async (_parent, _args, ctx, _info) => { resolve: async (_parent, _args, ctx, _info) => {
if (!ctx.me) { if (!ctx.me) {
throw new Error('Unauthorized') throw new Error("Unauthorized");
} }
if (ctx.me.role !== Role.CENTER_OWNER) { if (ctx.me.role !== Role.CENTER_OWNER) {
throw new Error('Only center owners can access this data') throw new Error("Only center owners can access this data");
} }
// get center by owner id // get center by owner id
const center = await this.prisma.center.findUnique({ const center = await this.prisma.center.findUnique({
where: { where: {
centerOwnerId: ctx.me.id, centerOwnerId: ctx.me.id,
}, },
}) });
if (!center) { if (!center) {
throw new Error('Center not found') throw new Error("Center not found");
} }
// calculate analytic // calculate analytic
@@ -261,39 +266,40 @@ export class AnalyticSchema extends PothosSchema {
}, },
banned: false, banned: false,
}, },
}) });
const activeServiceCount = await this.prisma.service.count({ const activeServiceCount = await this.prisma.service.count({
where: { where: {
centerId: center.id, centerId: center.id,
status: ServiceStatus.APPROVED, status: ServiceStatus.APPROVED,
}, },
}) });
const totalServiceCount = await this.prisma.service.count({ const totalServiceCount = await this.prisma.service.count({
where: { where: {
centerId: center.id, centerId: center.id,
}, },
}) });
// calculate revenue from orders of services in the center and factor in commission percentage // calculate revenue from orders of services in the center and factor in commission percentage
// query all orders of services in the center and calculate actual revenue of each order // query all orders of services in the center and calculate actual revenue of each order
// then sum up the revenue // then sum up the revenue
let revenue = 0 let revenue = 0;
const orders = await this.prisma.order.findMany({ const orders = await this.prisma.order.findMany({
where: { where: {
service: { centerId: center.id }, service: { centerId: center.id },
status: OrderStatus.PAID, status: OrderStatus.PAID,
}, },
}) });
for (const order of orders) { for (const order of orders) {
const service = await this.prisma.service.findUnique({ const service = await this.prisma.service.findUnique({
where: { id: order.serviceId }, where: { id: order.serviceId },
}) });
if (!service) { if (!service) {
continue continue;
} }
const commission = service.commission const commission = service.commission;
const actualRevenue = (order.total || 0) - (order.total || 0) * commission const actualRevenue =
revenue += actualRevenue (order.total || 0) - (order.total || 0) * commission;
revenue += actualRevenue;
} }
return { return {
centerId: center.id, centerId: center.id,
@@ -302,40 +308,40 @@ export class AnalyticSchema extends PothosSchema {
totalServiceCount: totalServiceCount, totalServiceCount: totalServiceCount,
revenue: revenue, revenue: revenue,
updatedAt: DateTimeUtils.now(), updatedAt: DateTimeUtils.now(),
} };
}, },
}), }),
platformAnalytic: t.field({ platformAnalytic: t.field({
type: this.platformAnalytic(), type: this.platformAnalytic(),
args: { args: {
take: t.arg({ take: t.arg({
type: 'Int', type: "Int",
description: 'The number of services to take.', description: "The number of services to take.",
required: true, required: true,
}), }),
serviceSortBy: t.arg({ serviceSortBy: t.arg({
type: this.serviceSortBy(), type: this.serviceSortBy(),
description: 'The field to sort by.', description: "The field to sort by.",
required: true, required: true,
}), }),
centerSortBy: t.arg({ centerSortBy: t.arg({
type: this.centerSortBy(), type: this.centerSortBy(),
description: 'The field to sort by.', description: "The field to sort by.",
required: true, required: true,
}), }),
timeframes: t.arg({ timeframes: t.arg({
type: this.timeframes(), type: this.timeframes(),
description: 'The frame of time Eg day, week, month, year.', description: "The frame of time Eg day, week, month, year.",
required: true, required: true,
}), }),
}, },
description: 'Retrieve a single platform analytic.', description: "Retrieve a single platform analytic.",
resolve: async (_parent, args, ctx, _info) => { resolve: async (_parent, args, ctx, _info) => {
if (!ctx.me) { if (!ctx.me) {
throw new Error('Unauthorized') throw new Error("Unauthorized");
} }
if (ctx.me.role !== Role.ADMIN && ctx.me.role !== Role.MODERATOR) { if (ctx.me.role !== Role.ADMIN && ctx.me.role !== Role.MODERATOR) {
throw new Error('Only admins and moderators can access this data') throw new Error("Only admins and moderators can access this data");
} }
// calculate analytic for services sorted by args.serviceSortBy and args.timeframes // calculate analytic for services sorted by args.serviceSortBy and args.timeframes
const topServices = await this.prisma.service.findMany({ const topServices = await this.prisma.service.findMany({
@@ -348,7 +354,7 @@ export class AnalyticSchema extends PothosSchema {
}, },
}, },
take: args.take, take: args.take,
}) });
// get top centers by args.centerSortBy // get top centers by args.centerSortBy
const topCenters = await this.prisma.center.findMany({ const topCenters = await this.prisma.center.findMany({
orderBy: { orderBy: {
@@ -357,13 +363,13 @@ export class AnalyticSchema extends PothosSchema {
}, },
}, },
take: args.take, take: args.take,
}) });
// get pending refunds // get pending refunds
const pendingRefunds = await this.prisma.order.findMany({ const pendingRefunds = await this.prisma.order.findMany({
where: { where: {
status: OrderStatus.PENDING_REFUND, status: OrderStatus.PENDING_REFUND,
}, },
}) });
// get active center count by center owner not banned and have schedule with dates in the future // get active center count by center owner not banned and have schedule with dates in the future
const activeCenterCount = await this.prisma.center.count({ const activeCenterCount = await this.prisma.center.count({
where: { where: {
@@ -390,47 +396,49 @@ export class AnalyticSchema extends PothosSchema {
}, },
}, },
}, },
}) });
// get total center count // get total center count
const totalCenterCount = await this.prisma.center.count() const totalCenterCount = await this.prisma.center.count();
// get total user count // get total user count
const totalUserCount = await this.prisma.user.count() const totalUserCount = await this.prisma.user.count();
// get active mentor count // get active mentor count
const activeMentorCount = await this.prisma.user.count({ const activeMentorCount = await this.prisma.user.count({
where: { where: {
role: Role.CENTER_MENTOR, role: Role.CENTER_MENTOR,
banned: false, banned: false,
}, },
}) });
// get total mentor count // get total mentor count
const totalMentorCount = await this.prisma.user.count({ const totalMentorCount = await this.prisma.user.count({
where: { where: {
role: Role.CENTER_MENTOR, role: Role.CENTER_MENTOR,
}, },
}) });
// get approved service count // get approved service count
const approvedServiceCount = await this.prisma.service.count({ const approvedServiceCount = await this.prisma.service.count({
where: { where: {
status: ServiceStatus.APPROVED, status: ServiceStatus.APPROVED,
}, },
}) });
// get rejected service count // get rejected service count
const rejectedServiceCount = await this.prisma.service.count({ const rejectedServiceCount = await this.prisma.service.count({
where: { where: {
status: ServiceStatus.REJECTED, status: ServiceStatus.REJECTED,
}, },
}) });
// get total workshop count // get total workshop count
const totalWorkshopCount = await this.prisma.workshop.count() const totalWorkshopCount = await this.prisma.workshop.count();
// get total order count // get total order count
const totalOrderCount = await this.prisma.order.count() const totalOrderCount = await this.prisma.order.count();
// get total service count // get total service count
const totalServiceCount = await this.prisma.service.count() const totalServiceCount = await this.prisma.service.count();
// get revenue // get revenue
let revenue = 0 let revenue = 0;
// query all orders of services in all centers in the past args.timeframes and calculate actual revenue of each order by convert commission percentage to float // query all orders of services in all centers in the past args.timeframes and calculate actual revenue of each order by convert commission percentage to float
// convert args.timeframes to number of days // convert args.timeframes to number of days
const timeframes = DateTimeUtils.subtractDaysFromTimeframe(args.timeframes) const timeframes = DateTimeUtils.subtractDaysFromTimeframe(
args.timeframes
);
const orders = await this.prisma.order.findMany({ const orders = await this.prisma.order.findMany({
where: { where: {
status: OrderStatus.PAID, status: OrderStatus.PAID,
@@ -438,17 +446,20 @@ export class AnalyticSchema extends PothosSchema {
gte: timeframes.toJSDate(), gte: timeframes.toJSDate(),
}, },
}, },
}) });
for (const order of orders) { for (const order of orders) {
const service = await this.prisma.service.findUnique({ const service = await this.prisma.service.findUnique({
where: { id: order.serviceId }, where: { id: order.serviceId },
}) });
if (!service) { if (!service) {
continue continue;
}
const orderTotal = Number(order.total ?? 0);
const commission = Number(service.commission ?? 0);
const actualRevenue = orderTotal * (1 - commission);
if (!isNaN(actualRevenue)) {
revenue += actualRevenue;
} }
const commission = service.commission
const actualRevenue = (order.total || 0) - (order.total || 0) * commission
revenue += actualRevenue
} }
// return analytic // return analytic
return { return {
@@ -467,9 +478,9 @@ export class AnalyticSchema extends PothosSchema {
totalOrderCount: totalOrderCount, totalOrderCount: totalOrderCount,
totalServiceCount: totalServiceCount, totalServiceCount: totalServiceCount,
updatedAt: DateTimeUtils.now(), updatedAt: DateTimeUtils.now(),
} };
}, },
}), }),
})) }));
} }
} }