feat(store): 门店套餐 v3.4.10 全端实现
新增 StorePackage 与变更审核流,覆盖总部直存、合伙/门店提审、C 端展示与套餐异议工单;同步 PRD/开发文档与 REQ 索引。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
|
||||
export class CreateAfterSaleTicketDto {
|
||||
@IsString()
|
||||
@IsIn(['REFUND', 'RESHIPMENT', 'DAMAGE_RETURN', 'RETURN_REFUND'])
|
||||
@IsIn(['REFUND', 'RESHIPMENT', 'DAMAGE_RETURN', 'RETURN_REFUND', 'PACKAGE_DISPUTE'])
|
||||
ticketType: string;
|
||||
|
||||
@IsOptional()
|
||||
@@ -21,6 +21,21 @@ export class CreateAfterSaleTicketDto {
|
||||
evidenceUrls?: string[];
|
||||
}
|
||||
|
||||
export class CreatePackageDisputeDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
storeId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(512)
|
||||
remark?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
redeemRecordId?: string;
|
||||
}
|
||||
|
||||
export class CreateInvoiceDto {
|
||||
@IsString()
|
||||
@IsIn(['PERSONAL', 'ENTERPRISE'])
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
PartnerProxyOrderPreviewDto,
|
||||
} from './dto/partner-proxy-order.dto';
|
||||
import { ManualShipOrderDto } from '../ops/dto/admin-mutate.dto';
|
||||
import { CreateAfterSaleTicketDto, CreateInvoiceDto } from './dto/after-sale.dto';
|
||||
import { CreateAfterSaleTicketDto, CreateInvoiceDto, CreatePackageDisputeDto } from './dto/after-sale.dto';
|
||||
|
||||
@Controller('trade/orders')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@@ -103,6 +103,17 @@ export class TradeController {
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('trade/package-disputes')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class TradePackageDisputeController {
|
||||
constructor(private readonly tradeService: TradeService) {}
|
||||
|
||||
@Post()
|
||||
create(@CurrentUser() user: AuthUser, @Body() body: CreatePackageDisputeDto) {
|
||||
return this.tradeService.createPackageDispute(user.actorId, body);
|
||||
}
|
||||
}
|
||||
|
||||
@Controller('trade/after-sale-tickets')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class TradeAfterSaleTicketController {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
PartnerReshipmentController,
|
||||
TradeAfterSaleTicketController,
|
||||
TradeInvoiceController,
|
||||
TradePackageDisputeController,
|
||||
} from './trade.controller';
|
||||
import { TradeService } from './trade.service';
|
||||
|
||||
@@ -32,6 +33,7 @@ import { TradeService } from './trade.service';
|
||||
],
|
||||
controllers: [
|
||||
TradeController,
|
||||
TradePackageDisputeController,
|
||||
TradeAfterSaleTicketController,
|
||||
TradeInvoiceController,
|
||||
PartnerOrderController,
|
||||
|
||||
@@ -730,6 +730,40 @@ export class TradeService {
|
||||
return serializeBigInt({ ...ticket, orderNo: order.orderNo, orderStatus: order.status });
|
||||
}
|
||||
|
||||
async createPackageDispute(
|
||||
userId: bigint,
|
||||
body: { storeId: string; remark?: string; redeemRecordId?: string },
|
||||
) {
|
||||
const storeId = BigInt(body.storeId);
|
||||
const store = await this.prisma.store.findFirst({
|
||||
where: { id: storeId, status: 'OPEN' },
|
||||
});
|
||||
if (!store) throw new NotFoundException('门店不存在');
|
||||
|
||||
const pending = await this.prisma.commonTicket.findFirst({
|
||||
where: {
|
||||
ticketType: 'PACKAGE_DISPUTE',
|
||||
refType: 'STORE',
|
||||
refId: storeId,
|
||||
status: { in: ['PENDING', 'OPEN'] },
|
||||
},
|
||||
});
|
||||
if (pending) throw new BadRequestException('该门店套餐异议已在处理中');
|
||||
|
||||
const extraJson: Record<string, unknown> = { userId: userId.toString() };
|
||||
if (body.redeemRecordId) {
|
||||
extraJson.redeemRecordId = body.redeemRecordId;
|
||||
}
|
||||
|
||||
return this.ticketService.create({
|
||||
ticketType: 'PACKAGE_DISPUTE',
|
||||
refType: 'STORE',
|
||||
refId: storeId.toString(),
|
||||
remark: body.remark ?? '用户对门店套餐有异议',
|
||||
extraJson,
|
||||
});
|
||||
}
|
||||
|
||||
private generateInvoiceNo() {
|
||||
return `INV${Date.now()}${Math.floor(Math.random() * 900 + 100)}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user