Files
epess-web-backend/src/Order/order.schema.ts
Ly Tuan Kiet 10e20092ab chore: update configuration and improve schema imports
- Updated biome.json to include "graphql.d.ts" in the ignored files list.
- Updated subproject commit reference in epess-database to the latest version.
- Removed unused script from package.json and streamlined module file extensions in tsconfig.json.
- Consolidated exclude patterns in tsconfig.build.json for clarity.
- Refactored imports across multiple schema files for consistency and improved readability.
- Enhanced various schema files by ensuring proper import order and removing redundant code.
- Improved error handling and data integrity checks in several service and schema files.
2024-12-08 20:49:52 +07:00

324 lines
11 KiB
TypeScript

import { Inject, Injectable, Logger } from '@nestjs/common'
import { OrderStatus, ScheduleDateStatus, ScheduleStatus } from '@prisma/client'
import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
import _ from 'lodash'
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(
@Inject(SchemaBuilderToken) private readonly builder: Builder,
private readonly prisma: PrismaService,
private readonly payosService: PayosService,
) {
super()
}
// Types section
@PothosRef()
order() {
return this.builder.prismaObject('Order', {
description: 'An order in the system.',
fields: (t) => ({
id: t.exposeID('id', {
description: 'The ID of the order.',
}),
userId: t.exposeID('userId', {
description: 'The ID of the user.',
}),
serviceId: t.exposeID('serviceId', {
description: 'The ID of the service.',
}),
status: t.expose('status', {
type: OrderStatus,
description: 'The status of the order.',
}),
total: t.exposeInt('total', {
description: 'The total price of the order.',
}),
scheduleId: t.exposeID('scheduleId', {
description: 'The ID of the schedule.',
}),
schedule: t.relation('schedule', {
description: 'The schedule of the order.',
}),
disbursed: t.exposeBoolean('disbursed', {
description: 'Whether the order has been disbursed.',
}),
chatRoomId: t.exposeID('chatRoomId', {
description: 'The ID of the chat room.',
}),
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.',
}),
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.',
}),
user: t.relation('user', {
description: 'The user who made the order.',
}),
service: t.relation('service', {
description: 'The service for the order.',
}),
refundTicket: t.relation('refundTicket', {
description: 'The refund ticket for the order.',
}),
payment: t.relation('payment', {
description: 'The payment for the order.',
}),
paymentId: t.exposeString('paymentId', {
description: 'The ID of the payment.',
}),
}),
})
}
@Pothos()
init(): void {
// query section
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'),
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.order.findMany({
...query,
take: args.take ?? undefined,
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.',
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'),
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) throw new Error('Orders cannot be retrieved in subscription context')
if (!ctx.http.me) 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({
...query,
where: {
AND: [
...(args.filter ? [args.filter] : []),
{
userId: ctx.http.me.id,
status: OrderStatus.PAID,
schedule: {
OR: [
{
dates: {
every: {
OR: [
{
end: { lte: DateTimeUtils.now().toJSDate() },
},
{
status: ScheduleDateStatus.COMPLETED,
},
],
},
},
},
// or cancelled
{
status: ScheduleStatus.REFUNDED,
},
],
},
},
],
},
})
},
}),
}))
// mutation section
this.builder.mutationFields((t) => ({
createOrder: t.prismaField({
type: this.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',
]),
required: true,
}),
},
resolve: async (query, _root, args, ctx, _info) => {
if (ctx.isSubscription) {
throw new Error('Subscription is not allowed')
}
if (!args.data.service.connect?.id) {
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')
}
// check if input schedule has order id then throw error
const schedule = await this.prisma.schedule.findUnique({
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')
}
}
const order = await this.prisma.order.create({
...query,
data: {
status: OrderStatus.PENDING,
total: service.price,
userId: ctx.http.me?.id ?? '',
serviceId: service.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')
}
// check if order is free
if (order.total === 0) {
// assign schedule
await this.prisma.schedule.update({
where: { id: args.data.schedule.connect?.id ?? '' },
data: {
orderId: order.id,
},
})
return order
}
// random integer
const paymentCode = Math.floor(Math.random() * 1000000)
// create payment
const payment = await this.prisma.payment.create({
data: {
orderId: order.id,
amount: service.price,
paymentCode: paymentCode.toString(),
expiredAt: DateTimeUtils.now().plus({ minutes: 15 }).toJSDate(),
},
})
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.http.me?.name ?? '',
buyerEmail: ctx.http.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.',
args: {
where: t.arg({
type: this.builder.generator.getWhereUnique('Order'),
required: true,
}),
},
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.order.delete({
...query,
where: args.where,
})
},
}),
updateOrder: t.prismaField({
type: this.order(),
description: 'Update an existing order.',
args: {
data: t.arg({
type: this.builder.generator.getUpdateInput('Order', ['status', 'total']),
required: true,
}),
where: t.arg({
type: this.builder.generator.getWhereUnique('Order'),
required: true,
}),
},
resolve: async (query, _root, args, _ctx, _info) => {
return await this.prisma.order.update({
...query,
data: args.data,
where: args.where,
})
},
}),
}))
}
}