refactor: standardize formatting and improve OrderSchema

- Reformatted import statements in order.schema.ts for consistency and readability.
- Enhanced field descriptions in the OrderSchema to ensure clarity and uniformity.
- Updated error messages for better user feedback during order processing.
- Improved the structure of query and mutation fields for better maintainability.

These changes aim to enhance code readability, maintainability, and user experience within the Order management features.
This commit is contained in:
2025-01-16 19:20:02 +07:00
parent 26a5e6fe89
commit 1896974484

View File

@@ -1,14 +1,24 @@
import { Inject, Injectable, Logger } from '@nestjs/common'
import { OrderStatus, Role, ScheduleDateStatus, ScheduleStatus } from '@prisma/client'
import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
import _ from 'lodash'
import { CenterSchema } from 'src/Center/center.schema'
import { CenterMentorSchema } from 'src/CenterMentor/centermentor.schema'
import { UserSchema } from 'src/User/user.schema'
import { Builder } from '../Graphql/graphql.builder'
import { PayosService } from '../Payos/payos.service'
import { PrismaService } from '../Prisma/prisma.service'
import { DateTimeUtils } from '../common/utils/datetime.utils'
import { Inject, Injectable, Logger } from "@nestjs/common";
import {
OrderStatus,
Role,
ScheduleDateStatus,
ScheduleStatus,
} from "@prisma/client";
import {
Pothos,
PothosRef,
PothosSchema,
SchemaBuilderToken,
} from "@smatch-corp/nestjs-pothos";
import _ from "lodash";
import { CenterSchema } from "src/Center/center.schema";
import { CenterMentorSchema } from "src/CenterMentor/centermentor.schema";
import { UserSchema } from "src/User/user.schema";
import { Builder } from "../Graphql/graphql.builder";
import { PayosService } from "../Payos/payos.service";
import { PrismaService } from "../Prisma/prisma.service";
import { DateTimeUtils } from "../common/utils/datetime.utils";
@Injectable()
export class OrderSchema extends PothosSchema {
constructor(
@@ -17,107 +27,107 @@ export class OrderSchema extends PothosSchema {
private readonly payosService: PayosService,
private readonly centerSchema: CenterSchema,
private readonly centerMentorSchema: CenterMentorSchema,
private readonly userSchema: UserSchema,
private readonly userSchema: UserSchema
) {
super()
super();
}
// Types section
@PothosRef()
order() {
return this.builder.prismaObject('Order', {
description: 'An order in the system.',
return this.builder.prismaObject("Order", {
description: "An order in the system.",
fields: (t) => ({
id: t.exposeID('id', {
description: 'The ID of the order.',
id: t.exposeID("id", {
description: "The ID of the order.",
}),
userId: t.exposeID('userId', {
description: 'The ID of the user.',
userId: t.exposeID("userId", {
description: "The ID of the user.",
}),
serviceId: t.exposeID('serviceId', {
description: 'The ID of the service.',
serviceId: t.exposeID("serviceId", {
description: "The ID of the service.",
}),
status: t.expose('status', {
status: t.expose("status", {
type: OrderStatus,
description: 'The status of the order.',
description: "The status of the order.",
}),
total: t.exposeInt('total', {
description: 'The total price of the order.',
total: t.exposeInt("total", {
description: "The total price of the order.",
}),
scheduleId: t.exposeID('scheduleId', {
description: 'The ID of the schedule.',
scheduleId: t.exposeID("scheduleId", {
description: "The ID of the schedule.",
}),
schedule: t.relation('schedule', {
description: 'The schedule of the order.',
schedule: t.relation("schedule", {
description: "The schedule of the order.",
}),
disbursed: t.exposeBoolean('disbursed', {
description: 'Whether the order has been disbursed.',
disbursed: t.exposeBoolean("disbursed", {
description: "Whether the order has been disbursed.",
}),
chatRoomId: t.exposeID('chatRoomId', {
description: 'The ID of the chat room.',
chatRoomId: t.exposeID("chatRoomId", {
description: "The ID of the chat room.",
}),
chatRoom: t.relation('chatRoom', {
description: 'The chat room of the order.',
chatRoom: t.relation("chatRoom", {
description: "The chat room of the order.",
}),
createdAt: t.expose('createdAt', {
type: 'DateTime',
description: 'The date and time the order was created.',
createdAt: t.expose("createdAt", {
type: "DateTime",
description: "The date and time the order was created.",
}),
updatedAt: t.expose('updatedAt', {
type: 'DateTime',
description: 'The date and time the order was updated.',
updatedAt: t.expose("updatedAt", {
type: "DateTime",
description: "The date and time the order was updated.",
}),
commission: t.exposeFloat('commission', {
description: 'The commission of the order.',
commission: t.exposeFloat("commission", {
description: "The commission of the order.",
}),
user: t.relation('user', {
description: 'The user who made the order.',
user: t.relation("user", {
description: "The user who made the order.",
}),
service: t.relation('service', {
description: 'The service for the order.',
service: t.relation("service", {
description: "The service for the order.",
}),
refundTicket: t.relation('refundTicket', {
description: 'The refund ticket for the order.',
refundTicket: t.relation("refundTicket", {
description: "The refund ticket for the order.",
}),
payment: t.relation('payment', {
description: 'The payment for the order.',
payment: t.relation("payment", {
description: "The payment for the order.",
}),
paymentId: t.exposeString('paymentId', {
description: 'The ID of the payment.',
paymentId: t.exposeString("paymentId", {
description: "The ID of the payment.",
}),
}),
})
});
}
@PothosRef()
orderDetails() {
return this.builder.simpleObject('OrderDetails', {
return this.builder.simpleObject("OrderDetails", {
fields: (t) => ({
order: t.field({
type: this.order(),
description: 'The order of the details.',
description: "The order of the details.",
}),
center: t.field({
type: this.centerSchema.center(),
description: 'The center of the order.',
description: "The center of the order.",
}),
centerMentor: t.field({
type: this.centerMentorSchema.centerMentor(),
description: 'The mentor of the order.',
description: "The mentor of the order.",
}),
user: t.field({
type: this.userSchema.user(),
description: 'The user of the order.',
description: "The user of the order.",
}),
status: t.string(),
total: t.int(),
commission: t.float(),
completedAt: t.field({
type: 'DateTime',
description: 'The date and time the order was completed.',
type: "DateTime",
description: "The date and time the order was completed.",
}),
}),
})
});
}
@Pothos()
@@ -126,8 +136,9 @@ export class OrderSchema extends PothosSchema {
this.builder.queryFields((t) => ({
orders: t.prismaField({
type: [this.order()],
description: 'Retrieve a list of orders with optional filtering, ordering, and pagination.',
args: this.builder.generator.findManyArgs('Order'),
description:
"Retrieve a list of orders with optional filtering, ordering, and pagination.",
args: this.builder.generator.findManyArgs("Order"),
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.order.findMany({
...query,
@@ -135,27 +146,27 @@ export class OrderSchema extends PothosSchema {
skip: args.skip ?? undefined,
orderBy: args.orderBy ?? undefined,
where: args.filter ?? undefined,
})
});
},
}),
order: t.prismaField({
type: this.order(),
args: this.builder.generator.findUniqueArgs('Order'),
description: 'Retrieve a single order by its unique identifier.',
args: this.builder.generator.findUniqueArgs("Order"),
description: "Retrieve a single order by its unique identifier.",
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.order.findUnique({
...query,
where: args.where,
})
});
},
}),
completedOrders: t.prismaField({
type: [this.order()],
description: 'Retrieve a list of completed orders',
args: this.builder.generator.findManyArgs('Order'),
description: "Retrieve a list of completed orders",
args: this.builder.generator.findManyArgs("Order"),
resolve: async (query, _root, args, ctx, _info) => {
if (!ctx.me) {
throw new Error('Unauthorized')
throw new Error("Unauthorized");
}
// return orders where user is the one who made the order and status is PAID and schedule.dates is in the past
return await this.prisma.order.findMany({
@@ -191,20 +202,20 @@ export class OrderSchema extends PothosSchema {
},
],
},
})
});
},
}),
completedOrdersForModerator: t.prismaField({
type: [this.order()],
description: 'Retrieve a list of completed orders for moderator',
args: this.builder.generator.findManyArgs('Order'),
description: "Retrieve a list of completed orders for moderator",
args: this.builder.generator.findManyArgs("Order"),
resolve: async (query, _root, args, ctx, _info) => {
if (!ctx.me) {
throw new Error('Unauthorized')
throw new Error("Unauthorized");
}
// only for role moderator
if (ctx.me.role !== Role.MODERATOR) {
throw new Error('Unauthorized')
throw new Error("Unauthorized");
}
// return completed order list where schedule status is COMPLETED
return await this.prisma.order.findMany({
@@ -220,24 +231,24 @@ export class OrderSchema extends PothosSchema {
},
],
},
})
});
},
}),
completedOrdersDetails: t.field({
type: this.orderDetails(),
args: {
orderId: t.arg({
type: 'String',
type: "String",
required: true,
}),
},
description: 'Retrieve a list of completed orders details',
description: "Retrieve a list of completed orders details",
resolve: async (_query, args, ctx, _info) => {
if (!ctx.me) {
throw new Error('Unauthorized')
throw new Error("Unauthorized");
}
if (ctx.me.role !== Role.MODERATOR) {
throw new Error('Unauthorized')
throw new Error("Unauthorized");
}
// get order details
const order = await this.prisma.order.findUnique({
@@ -250,45 +261,45 @@ export class OrderSchema extends PothosSchema {
},
},
},
})
});
if (!order) {
throw new Error('Order not found')
throw new Error("Order not found");
}
// get center details
const center = await this.prisma.center.findUnique({
where: { id: order?.service?.centerId },
})
});
if (!center) {
throw new Error('Center not found')
throw new Error("Center not found");
}
// get mentor id from schedule
const mentorId = order?.schedule?.managedService?.mentorId
const mentorId = order?.schedule?.managedService?.mentorId;
// get center mentor details
const centerMentor = await this.prisma.user.findUnique({
where: {
id: mentorId,
},
})
});
if (!centerMentor) {
throw new Error('Center mentor not found')
throw new Error("Center mentor not found");
}
// get service details
const service = await this.prisma.service.findUnique({
where: { id: order?.serviceId },
})
});
if (!service) {
throw new Error('Service not found')
throw new Error("Service not found");
}
// calculate commission based on service price
const commission = service.price * (service.commission ?? 0.0)
const commission = service.price * (service.commission ?? 0.0);
// calculate total price
const total = service.price - commission
const total = service.price - commission;
// get user details
const user = await this.prisma.user.findUnique({
where: { id: order?.userId },
})
});
if (!user) {
throw new Error('User not found')
throw new Error("User not found");
}
return {
order: {
@@ -330,46 +341,46 @@ export class OrderSchema extends PothosSchema {
total,
commission,
completedAt: order.schedule?.scheduleEnd,
}
};
},
}),
}))
}));
// mutation section
this.builder.mutationFields((t) => ({
createOrder: t.prismaField({
type: this.order(),
description: 'Create a new order.',
description: "Create a new order.",
args: {
data: t.arg({
type: this.builder.generator.getCreateInput('Order', [
'id',
'user',
'paymentId',
'payment',
'refundTicket',
'status',
'total',
'createdAt',
'updatedAt',
'commission',
type: this.builder.generator.getCreateInput("Order", [
"id",
"user",
"paymentId",
"payment",
"refundTicket",
"status",
"total",
"createdAt",
"updatedAt",
"commission",
]),
required: true,
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (!ctx.me) {
throw new Error('Unauthorized')
throw new Error("Unauthorized");
}
if (!args.data.service.connect?.id) {
throw new Error('Service not found')
throw new Error("Service not found");
}
// query service
const service = await this.prisma.service.findUnique({
where: { id: args.data.service.connect.id },
})
});
if (!service) {
throw new Error('Service not found')
throw new Error("Service not found");
}
// check if user has already registered for this service
const userService = await this.prisma.schedule.findFirst({
@@ -385,21 +396,55 @@ export class OrderSchema extends PothosSchema {
},
],
},
})
});
if (userService) {
throw new Error('User has already registered for this service')
throw new Error("User has already registered for this service");
}
// check if user have any scheduledate overlap time with input schedule
const overlapSchedule = await this.prisma.scheduleDate.findFirst({
where: {
AND: [
{
start: {
equals: args.data.schedule.connect?.scheduleStart as Date,
},
end: {
equals: args.data.schedule.connect?.scheduleEnd as Date,
},
},
{
participantIds: {
has: ctx.me?.id ?? "",
},
},
{
status: {
notIn: [
ScheduleDateStatus.NOT_STARTED,
ScheduleDateStatus.IN_PROGRESS,
],
},
},
],
},
});
if (overlapSchedule) {
throw new Error("User has already registered for this service");
}
// check if input schedule has order id then throw error
const schedule = await this.prisma.schedule.findUnique({
where: { id: args.data.schedule.connect?.id ?? '' },
})
where: { id: args.data.schedule.connect?.id ?? "" },
});
if (schedule?.orderId) {
// check if order status is PAID OR PENDING
const order = await this.prisma.order.findUnique({
where: { id: schedule.orderId },
})
if (order?.status === OrderStatus.PAID || order?.status === OrderStatus.PENDING) {
throw new Error('Schedule already has an order')
});
if (
order?.status === OrderStatus.PAID ||
order?.status === OrderStatus.PENDING
) {
throw new Error("Schedule already has an order");
}
}
const order = await this.prisma.order.create({
@@ -407,31 +452,31 @@ export class OrderSchema extends PothosSchema {
data: {
status: OrderStatus.PENDING,
total: service.price,
userId: ctx.me?.id ?? '',
userId: ctx.me?.id ?? "",
serviceId: service.id,
scheduleId: args.data.schedule.connect?.id ?? '',
scheduleId: args.data.schedule.connect?.id ?? "",
commission: service.commission ?? 0.0,
},
})
});
// check if service is valid
if (!args.data.service.connect) {
throw new Error('Service not found')
throw new Error("Service not found");
}
// check if order is free
if (order.total === 0) {
// assign schedule
await this.prisma.schedule.update({
where: { id: args.data.schedule.connect?.id ?? '' },
where: { id: args.data.schedule.connect?.id ?? "" },
data: {
orderId: order.id,
},
})
return order
});
return order;
}
// random integer
const paymentCode = Math.floor(Math.random() * 1000000)
const paymentCode = Math.floor(Math.random() * 1000000);
// create payment
const payment = await this.prisma.payment.create({
data: {
@@ -440,49 +485,57 @@ export class OrderSchema extends PothosSchema {
paymentCode: paymentCode.toString(),
expiredAt: DateTimeUtils.now().plus({ minutes: 15 }).toJSDate(),
},
})
const _name = _.deburr(service.name).slice(0, 10)
Logger.log(`Creating payment for ${_name}`)
});
const _name = _.deburr(service.name).slice(0, 10);
Logger.log(`Creating payment for ${_name}`);
// generate payment url
const paymentData = await this.payosService.createPayment({
orderCode: paymentCode,
amount: service.price,
description: _name,
buyerName: ctx.me?.name ?? '',
buyerEmail: ctx.me?.email ?? '',
returnUrl: `${process.env.PAYOS_RETURN_URL}`.replace('<serviceId>', service.id),
cancelUrl: `${process.env.PAYOS_RETURN_URL}`.replace('<serviceId>', service.id),
expiredAt: DateTimeUtils.now().plus({ minutes: 15 }).toUnixInteger(),
})
buyerName: ctx.me?.name ?? "",
buyerEmail: ctx.me?.email ?? "",
returnUrl: `${process.env.PAYOS_RETURN_URL}`.replace(
"<serviceId>",
service.id
),
cancelUrl: `${process.env.PAYOS_RETURN_URL}`.replace(
"<serviceId>",
service.id
),
expiredAt: DateTimeUtils.now()
.plus({ minutes: 15 })
.toUnixInteger(),
});
// update order payment id
await this.prisma.order.update({
where: { id: order.id },
data: {
paymentId: payment.id,
},
})
});
// update payment url
await this.prisma.payment.update({
where: { id: payment.id },
data: {
paymentCode: paymentData.paymentLinkId,
},
})
});
// refetch order
return await this.prisma.order.findUnique({
where: { id: order.id },
include: {
payment: true,
},
})
});
},
}),
deleteOrder: t.prismaField({
type: this.order(),
description: 'Delete an existing order.',
description: "Delete an existing order.",
args: {
where: t.arg({
type: this.builder.generator.getWhereUnique('Order'),
type: this.builder.generator.getWhereUnique("Order"),
required: true,
}),
},
@@ -490,36 +543,39 @@ export class OrderSchema extends PothosSchema {
return await this.prisma.order.delete({
...query,
where: args.where,
})
});
},
}),
updateOrder: t.prismaField({
type: this.order(),
description: 'Update an existing order.',
description: "Update an existing order.",
args: {
data: t.arg({
type: this.builder.generator.getUpdateInput('Order', ['status', 'total']),
type: this.builder.generator.getUpdateInput("Order", [
"status",
"total",
]),
required: true,
}),
where: t.arg({
type: this.builder.generator.getWhereUnique('Order'),
type: this.builder.generator.getWhereUnique("Order"),
required: true,
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (!ctx.me) {
throw new Error('Unauthorized')
throw new Error("Unauthorized");
}
if (ctx.me.role !== Role.MODERATOR) {
throw new Error('Unauthorized')
throw new Error("Unauthorized");
}
return await this.prisma.order.update({
...query,
data: args.data,
where: args.where,
})
});
},
}),
}))
}));
}
}