feat: add admin order tags and read state
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { IsIn, IsOptional, IsString } from "class-validator";
|
import { IsArray, IsIn, IsOptional, IsString } from "class-validator";
|
||||||
|
|
||||||
export class UpdateBookingStatusDto {
|
export class UpdateBookingStatusDto {
|
||||||
@IsIn(["received", "contacting", "confirmed", "completed", "cancelled"])
|
@IsIn(["received", "contacting", "confirmed", "completed", "cancelled"])
|
||||||
@@ -8,3 +8,11 @@ export class UpdateBookingStatusDto {
|
|||||||
@IsString()
|
@IsString()
|
||||||
adminNote?: string;
|
adminNote?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CreateTagDto {
|
||||||
|
@IsString() name!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateBookingTagsDto {
|
||||||
|
@IsArray() @IsString({ each: true }) tagIds!: string[];
|
||||||
|
}
|
||||||
|
|||||||
@@ -137,4 +137,43 @@ export class BookingService {
|
|||||||
data: { status, ...(adminNote !== undefined ? { adminNote } : {}), isRead: true },
|
data: { status, ...(adminNote !== undefined ? { adminNote } : {}), isRead: true },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async markRead(brandId: string, id: string) {
|
||||||
|
const booking = await this.prisma.booking.findFirst({ where: { id, brandId } });
|
||||||
|
if (!booking) throw new NotFoundException("订单不存在");
|
||||||
|
return this.prisma.booking.update({ where: { id }, data: { isRead: true } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async listTags(brandId: string) {
|
||||||
|
return this.prisma.tag.findMany({
|
||||||
|
where: { brandId, isEnabled: true },
|
||||||
|
orderBy: [{ isPreset: "desc" }, { name: "asc" }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createTag(brandId: string, name: string) {
|
||||||
|
const normalized = name.trim();
|
||||||
|
if (!normalized || normalized.length > 24) throw new BadRequestException("标签名称长度无效");
|
||||||
|
return this.prisma.tag.create({ data: { brandId, name: normalized } });
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateTags(brandId: string, bookingId: string, tagIds: string[]) {
|
||||||
|
const booking = await this.prisma.booking.findFirst({ where: { id: bookingId, brandId } });
|
||||||
|
if (!booking) throw new NotFoundException("订单不存在");
|
||||||
|
const tags = await this.prisma.tag.findMany({
|
||||||
|
where: { id: { in: tagIds }, brandId, isEnabled: true },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (tags.length !== new Set(tagIds).size) throw new BadRequestException("存在无效标签");
|
||||||
|
await this.prisma.$transaction([
|
||||||
|
this.prisma.bookingTag.deleteMany({ where: { bookingId } }),
|
||||||
|
this.prisma.bookingTag.createMany({
|
||||||
|
data: tags.map((tag) => ({ bookingId, tagId: tag.id })),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
return this.prisma.booking.findUnique({
|
||||||
|
where: { id: bookingId },
|
||||||
|
include: { tags: { include: { tag: true } } },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { PrismaService } from "./prisma.service.js";
|
|||||||
import { WechatAuthService } from "./wechat-auth.js";
|
import { WechatAuthService } from "./wechat-auth.js";
|
||||||
import { BookingService } from "./booking.service.js";
|
import { BookingService } from "./booking.service.js";
|
||||||
import { CreateBookingDto } from "./booking.dto.js";
|
import { CreateBookingDto } from "./booking.dto.js";
|
||||||
import { UpdateBookingStatusDto } from "./admin.dto.js";
|
import { CreateTagDto, UpdateBookingStatusDto, UpdateBookingTagsDto } from "./admin.dto.js";
|
||||||
import { AdminAuthService } from "./admin-auth.service.js";
|
import { AdminAuthService } from "./admin-auth.service.js";
|
||||||
import { AdminLoginDto } from "./admin-auth.dto.js";
|
import { AdminLoginDto } from "./admin-auth.dto.js";
|
||||||
import type { FastifyRequest } from "fastify";
|
import type { FastifyRequest } from "fastify";
|
||||||
@@ -183,6 +183,46 @@ export class AdminBookingController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post(":id/read")
|
||||||
|
markRead(@Req() request: FastifyRequest, @Param("id") id: string): Promise<unknown> {
|
||||||
|
this.requireAdmin(request);
|
||||||
|
return this.bookings.markRead(
|
||||||
|
process.env.DEFAULT_BRAND_ID ?? "00000000-0000-0000-0000-000000000001",
|
||||||
|
id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(":id/tags")
|
||||||
|
updateTags(
|
||||||
|
@Req() request: FastifyRequest,
|
||||||
|
@Param("id") id: string,
|
||||||
|
@Body() body: UpdateBookingTagsDto,
|
||||||
|
): Promise<unknown> {
|
||||||
|
this.requireAdmin(request);
|
||||||
|
return this.bookings.updateTags(
|
||||||
|
process.env.DEFAULT_BRAND_ID ?? "00000000-0000-0000-0000-000000000001",
|
||||||
|
id,
|
||||||
|
body.tagIds,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("/tags")
|
||||||
|
listTags(@Req() request: FastifyRequest): Promise<unknown> {
|
||||||
|
this.requireAdmin(request);
|
||||||
|
return this.bookings.listTags(
|
||||||
|
process.env.DEFAULT_BRAND_ID ?? "00000000-0000-0000-0000-000000000001",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post("/tags")
|
||||||
|
createTag(@Req() request: FastifyRequest, @Body() body: CreateTagDto): Promise<unknown> {
|
||||||
|
this.requireAdmin(request);
|
||||||
|
return this.bookings.createTag(
|
||||||
|
process.env.DEFAULT_BRAND_ID ?? "00000000-0000-0000-0000-000000000001",
|
||||||
|
body.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private requireAdmin(request: FastifyRequest): void {
|
private requireAdmin(request: FastifyRequest): void {
|
||||||
const token = request.headers.authorization?.replace(/^Bearer\s+/i, "");
|
const token = request.headers.authorization?.replace(/^Bearer\s+/i, "");
|
||||||
if (!token) throw new BadRequestException("缺少管理员登录态");
|
if (!token) throw new BadRequestException("缺少管理员登录态");
|
||||||
|
|||||||
Reference in New Issue
Block a user