feat: add admin order tags and read state

This commit is contained in:
2026-09-18 16:57:14 +08:00
parent bb5ccb1359
commit 0d4caaba00
3 changed files with 89 additions and 2 deletions

View File

@@ -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[];
}

View File

@@ -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 } } },
});
}
} }

View File

@@ -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("缺少管理员登录态");