+
+
+ 技术支持
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
+ setPage(p);
+ setPageSize(ps);
+ },
+ }}
+ />
+
+ setDrawerOpen(false)}
+ extra={drawerExtra}
+ >
+ {detail && (
+
+ {detail.ticketNo}
+
+ {SUPPORT_TICKET_TYPE_LABELS[detail.ticketType] ?? detail.ticketType}
+
+
+
+ {SUPPORT_TICKET_STATUS_LABELS[detail.status] ?? detail.status}
+
+
+ {detail.title}
+
+ {detail.content || '—'}
+
+ {detail.creatorName}
+ {fmtTime(detail.createdAt)}
+ {detail.reviewerName || '—'}
+
+ {detail.reviewedAt ? fmtTime(detail.reviewedAt) : '—'}
+
+ {detail.rejectReason || '—'}
+ {detail.remark || '—'}
+
+ )}
+
+
+ setCreateOpen(false)}
+ onOk={() => void submitCreate()}
+ confirmLoading={creating}
+ destroyOnClose
+ okText="提交"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ setRejectOpen(false)}
+ onOk={() => void submitReject()}
+ confirmLoading={acting}
+ destroyOnClose
+ okText="确认驳回"
+ okButtonProps={{ danger: true }}
+ >
+
+
+
+
+
+
+ );
+}
diff --git a/packages/shared-types/src/hq-permissions.ts b/packages/shared-types/src/hq-permissions.ts
index 774cd6f..8ac023b 100644
--- a/packages/shared-types/src/hq-permissions.ts
+++ b/packages/shared-types/src/hq-permissions.ts
@@ -12,6 +12,7 @@ export const HQ_PERMISSION_CATALOG = [
{ key: 'benefit', label: '好客权益', group: '业务' },
{ key: 'deliveries', label: '配送单', group: '业务' },
{ key: 'tickets', label: '工单中心', group: '业务' },
+ { key: 'tech_support', label: '技术支持', group: '业务' },
{ key: 'invoices', label: '发票管理', group: '业务' },
{ key: 'resources', label: 'OSS 资源库', group: '业务' },
{ key: 'logs', label: '日志', group: '业务' },
@@ -85,6 +86,7 @@ export const HQ_ROLE_DEFAULT_PERMISSIONS: Record = {
'benefit',
'deliveries',
'tickets',
+ 'tech_support',
'invoices',
'resources',
'logs',
@@ -97,9 +99,18 @@ export const HQ_ROLE_DEFAULT_PERMISSIONS: Record = {
'partners',
'finance',
'benefit',
+ 'tech_support',
'invoices',
'logs',
'system_settings_winery_bank',
],
- CUSTOMER_SERVICE: ['dashboard', 'users', 'orders', 'tickets', 'invoices', 'logs'],
+ CUSTOMER_SERVICE: [
+ 'dashboard',
+ 'users',
+ 'orders',
+ 'tickets',
+ 'tech_support',
+ 'invoices',
+ 'logs',
+ ],
};
diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts
index 612c1e1..4cb37c1 100644
--- a/packages/shared-types/src/index.ts
+++ b/packages/shared-types/src/index.ts
@@ -9,6 +9,7 @@ export * from './redeem';
export * from './settlement';
export * from './ops';
export * from './ticket';
+export * from './support-ticket';
export * from './invoice';
export * from './user-log';
diff --git a/packages/shared-types/src/support-ticket.ts b/packages/shared-types/src/support-ticket.ts
new file mode 100644
index 0000000..75aea03
--- /dev/null
+++ b/packages/shared-types/src/support-ticket.ts
@@ -0,0 +1,64 @@
+/** HQ 技术支持工单类型 */
+export type SupportTicketTypeDto = 'BUG' | 'SUGGESTION' | 'OTHER';
+
+export const SUPPORT_TICKET_TYPES = ['BUG', 'SUGGESTION', 'OTHER'] as const;
+
+export const SUPPORT_TICKET_TYPE_LABELS: Record = {
+ BUG: 'BUG',
+ SUGGESTION: '建议',
+ OTHER: '其他',
+};
+
+/** 技术支持工单状态机 */
+export type SupportTicketStatusDto =
+ | 'PENDING_REVIEW'
+ | 'REJECTED'
+ | 'DEVELOPING'
+ | 'TESTING'
+ | 'PASSED';
+
+export const SUPPORT_TICKET_STATUSES = [
+ 'PENDING_REVIEW',
+ 'REJECTED',
+ 'DEVELOPING',
+ 'TESTING',
+ 'PASSED',
+] as const;
+
+export const SUPPORT_TICKET_STATUS_LABELS: Record = {
+ PENDING_REVIEW: '待评审',
+ REJECTED: '已驳回',
+ DEVELOPING: '开发',
+ TESTING: '测试',
+ PASSED: '通过',
+};
+
+export interface SupportTicketDto {
+ id: string;
+ ticketNo: string;
+ ticketType: SupportTicketTypeDto;
+ status: SupportTicketStatusDto;
+ title: string;
+ content?: string | null;
+ rejectReason?: string | null;
+ creatorId: string;
+ creatorName: string;
+ reviewerId?: string | null;
+ reviewerName?: string | null;
+ reviewedAt?: string | null;
+ remark?: string | null;
+ createdAt: string;
+ updatedAt: string;
+ completedAt?: string | null;
+}
+
+export interface CreateSupportTicketRequest {
+ ticketType: SupportTicketTypeDto;
+ title: string;
+ content?: string;
+ remark?: string;
+}
+
+export interface RejectSupportTicketRequest {
+ rejectReason: string;
+}
diff --git a/server/dukang-api/prisma/init_v3.sql b/server/dukang-api/prisma/init_v3.sql
index efd356b..a6bb7c4 100644
--- a/server/dukang-api/prisma/init_v3.sql
+++ b/server/dukang-api/prisma/init_v3.sql
@@ -101,6 +101,31 @@ CREATE TABLE common_ticket (
KEY idx_common_ticket_type_status (ticket_type, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通用工单表';
+DROP TABLE IF EXISTS common_support_ticket;
+CREATE TABLE common_support_ticket (
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+ ticket_no VARCHAR(32) NOT NULL,
+ ticket_type VARCHAR(32) NOT NULL COMMENT 'BUG|SUGGESTION|OTHER',
+ status VARCHAR(32) NOT NULL DEFAULT 'PENDING_REVIEW' COMMENT 'PENDING_REVIEW|REJECTED|DEVELOPING|TESTING|PASSED',
+ title VARCHAR(128) NOT NULL,
+ content TEXT DEFAULT NULL,
+ reject_reason VARCHAR(512) DEFAULT NULL,
+ creator_id BIGINT UNSIGNED NOT NULL,
+ creator_name VARCHAR(64) NOT NULL,
+ reviewer_id BIGINT UNSIGNED DEFAULT NULL,
+ reviewer_name VARCHAR(64) DEFAULT NULL,
+ reviewed_at DATETIME(3) DEFAULT NULL,
+ remark VARCHAR(512) DEFAULT NULL,
+ created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+ updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
+ completed_at DATETIME(3) DEFAULT NULL,
+ PRIMARY KEY (id),
+ UNIQUE KEY uk_common_support_ticket_no (ticket_no),
+ KEY idx_common_support_ticket_status (status, created_at),
+ KEY idx_common_support_ticket_type_status (ticket_type, status),
+ KEY idx_common_support_ticket_creator (creator_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HQ技术支持工单';
+
DROP TABLE IF EXISTS common_product_item;
CREATE TABLE common_product_item (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
diff --git a/server/dukang-api/prisma/schema.prisma b/server/dukang-api/prisma/schema.prisma
index fe0f0f6..0af7265 100644
--- a/server/dukang-api/prisma/schema.prisma
+++ b/server/dukang-api/prisma/schema.prisma
@@ -88,6 +88,20 @@ enum TicketType {
RETURN_REFUND
}
+enum SupportTicketType {
+ BUG
+ SUGGESTION
+ OTHER
+}
+
+enum SupportTicketStatus {
+ PENDING_REVIEW
+ REJECTED
+ DEVELOPING
+ TESTING
+ PASSED
+}
+
enum InvoiceTitleType {
PERSONAL
ENTERPRISE
@@ -440,6 +454,31 @@ model CommonTicket {
@@map("common_ticket")
}
+/// HQ 内部技术支持工单(BUG / 建议 / 其他)
+model CommonSupportTicket {
+ id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
+ ticketNo String @unique @map("ticket_no") @db.VarChar(32)
+ ticketType SupportTicketType @map("ticket_type")
+ status SupportTicketStatus @default(PENDING_REVIEW)
+ title String @db.VarChar(128)
+ content String? @db.Text
+ rejectReason String? @map("reject_reason") @db.VarChar(512)
+ creatorId BigInt @map("creator_id") @db.UnsignedBigInt
+ creatorName String @map("creator_name") @db.VarChar(64)
+ reviewerId BigInt? @map("reviewer_id") @db.UnsignedBigInt
+ reviewerName String? @map("reviewer_name") @db.VarChar(64)
+ reviewedAt DateTime? @map("reviewed_at") @db.DateTime(3)
+ remark String? @db.VarChar(512)
+ createdAt DateTime @default(now()) @map("created_at") @db.DateTime(3)
+ updatedAt DateTime @updatedAt @map("updated_at") @db.DateTime(3)
+ completedAt DateTime? @map("completed_at") @db.DateTime(3)
+
+ @@index([status, createdAt])
+ @@index([ticketType, status])
+ @@index([creatorId])
+ @@map("common_support_ticket")
+}
+
model CommonProductItem {
id BigInt @id @default(autoincrement()) @db.UnsignedBigInt
skuCode String @unique @map("sku_code") @db.VarChar(32)
diff --git a/server/dukang-api/src/common/hq-operation/hq-operation.constants.ts b/server/dukang-api/src/common/hq-operation/hq-operation.constants.ts
index 225320b..99f4672 100644
--- a/server/dukang-api/src/common/hq-operation/hq-operation.constants.ts
+++ b/server/dukang-api/src/common/hq-operation/hq-operation.constants.ts
@@ -46,6 +46,11 @@ export const HqOperationAction = {
TICKET_APPROVE: 'TICKET_APPROVE',
TICKET_REJECT: 'TICKET_REJECT',
TICKET_CREATE: 'TICKET_CREATE',
+ SUPPORT_TICKET_CREATE: 'SUPPORT_TICKET_CREATE',
+ SUPPORT_TICKET_APPROVE: 'SUPPORT_TICKET_APPROVE',
+ SUPPORT_TICKET_REJECT: 'SUPPORT_TICKET_REJECT',
+ SUPPORT_TICKET_START_TESTING: 'SUPPORT_TICKET_START_TESTING',
+ SUPPORT_TICKET_PASS: 'SUPPORT_TICKET_PASS',
INVOICE_CREATE: 'INVOICE_CREATE',
INVOICE_ISSUE: 'INVOICE_ISSUE',
INVOICE_REJECT: 'INVOICE_REJECT',
@@ -124,6 +129,11 @@ export const HQ_OPERATION_ACTION_LABELS: Record = {
[HqOperationAction.TICKET_APPROVE]: '工单通过',
[HqOperationAction.TICKET_REJECT]: '工单驳回',
[HqOperationAction.TICKET_CREATE]: '创建工单',
+ [HqOperationAction.SUPPORT_TICKET_CREATE]: '创建技术支持工单',
+ [HqOperationAction.SUPPORT_TICKET_APPROVE]: '技术支持评审通过',
+ [HqOperationAction.SUPPORT_TICKET_REJECT]: '技术支持评审驳回',
+ [HqOperationAction.SUPPORT_TICKET_START_TESTING]: '技术支持转入测试',
+ [HqOperationAction.SUPPORT_TICKET_PASS]: '技术支持测试通过',
[HqOperationAction.INVOICE_CREATE]: '创建发票申请',
[HqOperationAction.INVOICE_ISSUE]: '开具发票',
[HqOperationAction.INVOICE_REJECT]: '驳回发票',
diff --git a/server/dukang-api/src/modules/common/common.module.ts b/server/dukang-api/src/modules/common/common.module.ts
index d87c5cc..e6b423f 100644
--- a/server/dukang-api/src/modules/common/common.module.ts
+++ b/server/dukang-api/src/modules/common/common.module.ts
@@ -6,6 +6,7 @@ import { SystemConfigModule } from '../../common/system-config/system-config.mod
import { ResourceService } from './resource.service';
import { EventService } from './event.service';
import { TicketService } from './ticket.service';
+import { SupportTicketService } from './support-ticket.service';
import { ThirdPartyLogService } from './third-party-log.service';
import { ResourceController } from './resource.controller';
import { EventController } from './event.controller';
@@ -25,7 +26,14 @@ import { WechatLocationService } from './wechat-location.service';
WechatController,
ClientConfigController,
],
- providers: [ResourceService, EventService, TicketService, ThirdPartyLogService, WechatLocationService],
- exports: [ResourceService, EventService, TicketService],
+ providers: [
+ ResourceService,
+ EventService,
+ TicketService,
+ SupportTicketService,
+ ThirdPartyLogService,
+ WechatLocationService,
+ ],
+ exports: [ResourceService, EventService, TicketService, SupportTicketService],
})
export class CommonModule {}
diff --git a/server/dukang-api/src/modules/common/dto/support-ticket.dto.ts b/server/dukang-api/src/modules/common/dto/support-ticket.dto.ts
new file mode 100644
index 0000000..45b5d0c
--- /dev/null
+++ b/server/dukang-api/src/modules/common/dto/support-ticket.dto.ts
@@ -0,0 +1,58 @@
+import { IsIn, IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator';
+import { Type } from 'class-transformer';
+import { IsInt, Min } from 'class-validator';
+
+export class SupportTicketListQueryDto {
+ @IsOptional()
+ @IsIn(['BUG', 'SUGGESTION', 'OTHER'])
+ ticketType?: string;
+
+ @IsOptional()
+ @IsIn(['PENDING_REVIEW', 'REJECTED', 'DEVELOPING', 'TESTING', 'PASSED'])
+ status?: string;
+
+ @IsOptional()
+ @Type(() => Number)
+ @IsInt()
+ @Min(1)
+ page?: number = 1;
+
+ @IsOptional()
+ @Type(() => Number)
+ @IsInt()
+ @Min(1)
+ pageSize?: number = 20;
+}
+
+export class CreateSupportTicketDto {
+ @IsIn(['BUG', 'SUGGESTION', 'OTHER'])
+ ticketType: string;
+
+ @IsString()
+ @IsNotEmpty()
+ @MaxLength(128)
+ title: string;
+
+ @IsOptional()
+ @IsString()
+ content?: string;
+
+ @IsOptional()
+ @IsString()
+ @MaxLength(512)
+ remark?: string;
+}
+
+export class RejectSupportTicketDto {
+ @IsString()
+ @IsNotEmpty()
+ @MaxLength(512)
+ rejectReason: string;
+}
+
+export class SupportTicketRemarkDto {
+ @IsOptional()
+ @IsString()
+ @MaxLength(512)
+ remark?: string;
+}
diff --git a/server/dukang-api/src/modules/common/support-ticket.service.ts b/server/dukang-api/src/modules/common/support-ticket.service.ts
new file mode 100644
index 0000000..6d369d2
--- /dev/null
+++ b/server/dukang-api/src/modules/common/support-ticket.service.ts
@@ -0,0 +1,161 @@
+import {
+ BadRequestException,
+ Injectable,
+ NotFoundException,
+} from '@nestjs/common';
+import type { SupportTicketStatus, SupportTicketType } from '@prisma/client';
+import { Prisma } from '@prisma/client';
+import { PrismaService } from '../../common/prisma/prisma.module';
+import { serializeBigInt } from '../../common/decorators/current-user.decorator';
+import type {
+ CreateSupportTicketDto,
+ RejectSupportTicketDto,
+ SupportTicketListQueryDto,
+ SupportTicketRemarkDto,
+} from './dto/support-ticket.dto';
+
+function generateSupportTicketNo() {
+ return `ST${Date.now()}${Math.floor(Math.random() * 900 + 100)}`;
+}
+
+@Injectable()
+export class SupportTicketService {
+ constructor(private readonly prisma: PrismaService) {}
+
+ async create(
+ dto: CreateSupportTicketDto,
+ creator: { id: bigint; name: string },
+ ) {
+ const ticket = await this.prisma.commonSupportTicket.create({
+ data: {
+ ticketNo: generateSupportTicketNo(),
+ ticketType: dto.ticketType as SupportTicketType,
+ status: 'PENDING_REVIEW',
+ title: dto.title.trim(),
+ content: dto.content?.trim() || null,
+ remark: dto.remark?.trim() || null,
+ creatorId: creator.id,
+ creatorName: creator.name,
+ },
+ });
+ return serializeBigInt(ticket);
+ }
+
+ async list(query: SupportTicketListQueryDto) {
+ const page = query.page ?? 1;
+ const pageSize = query.pageSize ?? 20;
+ const where: Prisma.CommonSupportTicketWhereInput = {};
+ if (query.ticketType) {
+ where.ticketType = query.ticketType as SupportTicketType;
+ }
+ if (query.status) {
+ where.status = query.status as SupportTicketStatus;
+ }
+
+ const [items, total] = await Promise.all([
+ this.prisma.commonSupportTicket.findMany({
+ where,
+ orderBy: { createdAt: 'desc' },
+ skip: (page - 1) * pageSize,
+ take: pageSize,
+ }),
+ this.prisma.commonSupportTicket.count({ where }),
+ ]);
+ return serializeBigInt({ items, total, page, pageSize });
+ }
+
+ async detail(id: bigint) {
+ const ticket = await this.prisma.commonSupportTicket.findUnique({ where: { id } });
+ if (!ticket) throw new NotFoundException('技术支持工单不存在');
+ return serializeBigInt(ticket);
+ }
+
+ private async getOrThrow(id: bigint) {
+ const ticket = await this.prisma.commonSupportTicket.findUnique({ where: { id } });
+ if (!ticket) throw new NotFoundException('技术支持工单不存在');
+ return ticket;
+ }
+
+ /** 最高管理员评审通过 → 进入开发 */
+ async approve(
+ id: bigint,
+ reviewer: { id: bigint; name: string },
+ dto?: SupportTicketRemarkDto,
+ ) {
+ const ticket = await this.getOrThrow(id);
+ if (ticket.status !== 'PENDING_REVIEW') {
+ throw new BadRequestException('仅待评审工单可通过评审');
+ }
+ const updated = await this.prisma.commonSupportTicket.update({
+ where: { id },
+ data: {
+ status: 'DEVELOPING',
+ reviewerId: reviewer.id,
+ reviewerName: reviewer.name,
+ reviewedAt: new Date(),
+ remark: dto?.remark?.trim() || ticket.remark,
+ },
+ });
+ return serializeBigInt(updated);
+ }
+
+ /** 最高管理员评审驳回 */
+ async reject(
+ id: bigint,
+ reviewer: { id: bigint; name: string },
+ dto: RejectSupportTicketDto,
+ ) {
+ const ticket = await this.getOrThrow(id);
+ if (ticket.status !== 'PENDING_REVIEW') {
+ throw new BadRequestException('仅待评审工单可驳回');
+ }
+ const reason = dto.rejectReason.trim();
+ if (!reason) throw new BadRequestException('请填写驳回理由');
+
+ const updated = await this.prisma.commonSupportTicket.update({
+ where: { id },
+ data: {
+ status: 'REJECTED',
+ rejectReason: reason,
+ reviewerId: reviewer.id,
+ reviewerName: reviewer.name,
+ reviewedAt: new Date(),
+ completedAt: new Date(),
+ },
+ });
+ return serializeBigInt(updated);
+ }
+
+ /** 开发完成 → 测试 */
+ async startTesting(id: bigint, dto?: SupportTicketRemarkDto) {
+ const ticket = await this.getOrThrow(id);
+ if (ticket.status !== 'DEVELOPING') {
+ throw new BadRequestException('仅开发中工单可转入测试');
+ }
+ const updated = await this.prisma.commonSupportTicket.update({
+ where: { id },
+ data: {
+ status: 'TESTING',
+ remark: dto?.remark?.trim() || ticket.remark,
+ },
+ });
+ return serializeBigInt(updated);
+ }
+
+ /** 测试通过 */
+ async pass(id: bigint, dto?: SupportTicketRemarkDto) {
+ const ticket = await this.getOrThrow(id);
+ if (ticket.status !== 'TESTING') {
+ throw new BadRequestException('仅测试中工单可标记通过');
+ }
+ const updated = await this.prisma.commonSupportTicket.update({
+ where: { id },
+ data: {
+ status: 'PASSED',
+ remark: dto?.remark?.trim() || ticket.remark,
+ completedAt: new Date(),
+ },
+ });
+ return serializeBigInt(updated);
+ }
+}
diff --git a/server/dukang-api/src/modules/ops/admin-support-tickets.controller.ts b/server/dukang-api/src/modules/ops/admin-support-tickets.controller.ts
new file mode 100644
index 0000000..e0b73c9
--- /dev/null
+++ b/server/dukang-api/src/modules/ops/admin-support-tickets.controller.ts
@@ -0,0 +1,120 @@
+import {
+ Body,
+ Controller,
+ Get,
+ NotFoundException,
+ Param,
+ Post,
+ Query,
+ UseGuards,
+} from '@nestjs/common';
+import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
+import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
+import { AuthUser } from '../../common/guards/jwt-auth.guard';
+import { CurrentUser } from '../../common/decorators/current-user.decorator';
+import { HqOperation } from '../../common/hq-operation/hq-operation.decorator';
+import { HqOperationAction } from '../../common/hq-operation/hq-operation.constants';
+import { PrismaService } from '../../common/prisma/prisma.module';
+import { SupportTicketService } from '../common/support-ticket.service';
+import {
+ CreateSupportTicketDto,
+ RejectSupportTicketDto,
+ SupportTicketListQueryDto,
+ SupportTicketRemarkDto,
+} from '../common/dto/support-ticket.dto';
+
+@Controller('admin/support-tickets')
+@UseGuards(HqAuthGuard)
+export class AdminSupportTicketsController {
+ constructor(
+ private readonly service: SupportTicketService,
+ private readonly prisma: PrismaService,
+ ) {}
+
+ private async resolveHqAccount(user: AuthUser) {
+ const account = await this.prisma.hqAccount.findUnique({
+ where: { id: user.actorId },
+ select: { id: true, name: true },
+ });
+ if (!account) throw new NotFoundException('HQ 账户不存在');
+ return account;
+ }
+
+ @Get()
+ list(@Query() query: SupportTicketListQueryDto) {
+ return this.service.list(query);
+ }
+
+ @Post()
+ @HqOperation({
+ action: HqOperationAction.SUPPORT_TICKET_CREATE,
+ refType: 'SUPPORT_TICKET',
+ batch: true,
+ includeBody: true,
+ })
+ async create(@CurrentUser() user: AuthUser, @Body() body: CreateSupportTicketDto) {
+ const account = await this.resolveHqAccount(user);
+ return this.service.create(body, account);
+ }
+
+ @Get(':id')
+ detail(@Param('id') id: string) {
+ return this.service.detail(BigInt(id));
+ }
+
+ @Post(':id/approve')
+ @UseGuards(SuperAdminGuard)
+ @HqOperation({
+ action: HqOperationAction.SUPPORT_TICKET_APPROVE,
+ refType: 'SUPPORT_TICKET',
+ refIdParam: 'id',
+ includeBody: true,
+ })
+ async approve(
+ @CurrentUser() user: AuthUser,
+ @Param('id') id: string,
+ @Body() body: SupportTicketRemarkDto,
+ ) {
+ const account = await this.resolveHqAccount(user);
+ return this.service.approve(BigInt(id), account, body);
+ }
+
+ @Post(':id/reject')
+ @UseGuards(SuperAdminGuard)
+ @HqOperation({
+ action: HqOperationAction.SUPPORT_TICKET_REJECT,
+ refType: 'SUPPORT_TICKET',
+ refIdParam: 'id',
+ includeBody: true,
+ })
+ async reject(
+ @CurrentUser() user: AuthUser,
+ @Param('id') id: string,
+ @Body() body: RejectSupportTicketDto,
+ ) {
+ const account = await this.resolveHqAccount(user);
+ return this.service.reject(BigInt(id), account, body);
+ }
+
+ @Post(':id/start-testing')
+ @HqOperation({
+ action: HqOperationAction.SUPPORT_TICKET_START_TESTING,
+ refType: 'SUPPORT_TICKET',
+ refIdParam: 'id',
+ includeBody: true,
+ })
+ startTesting(@Param('id') id: string, @Body() body: SupportTicketRemarkDto) {
+ return this.service.startTesting(BigInt(id), body);
+ }
+
+ @Post(':id/pass')
+ @HqOperation({
+ action: HqOperationAction.SUPPORT_TICKET_PASS,
+ refType: 'SUPPORT_TICKET',
+ refIdParam: 'id',
+ includeBody: true,
+ })
+ pass(@Param('id') id: string, @Body() body: SupportTicketRemarkDto) {
+ return this.service.pass(BigInt(id), body);
+ }
+}
diff --git a/server/dukang-api/src/modules/ops/ops.module.ts b/server/dukang-api/src/modules/ops/ops.module.ts
index 84fe912..952c011 100644
--- a/server/dukang-api/src/modules/ops/ops.module.ts
+++ b/server/dukang-api/src/modules/ops/ops.module.ts
@@ -38,6 +38,7 @@ import { AdminOssLogsController } from './admin-oss-logs.controller';
import { AdminOssLogsService } from './admin-oss-logs.service';
import { AdminTicketsController, PartnerTicketsController } from './admin-tickets.controller';
import { AdminTicketsService } from './admin-tickets.service';
+import { AdminSupportTicketsController } from './admin-support-tickets.controller';
import { AdminInvoicesController } from './admin-invoices.controller';
import { SuperAdminGuard } from '../../common/guards/super-admin.guard';
import { BenefitModule } from '../benefit/benefit.module';
@@ -88,6 +89,7 @@ import { AdminFulfillmentProvidersController } from './admin-fulfillment-provide
AdminHqLogsController,
AdminOssLogsController,
AdminTicketsController,
+ AdminSupportTicketsController,
AdminInvoicesController,
AdminXiaofeixiaController,
AdminProductDetailTemplatesController,