客服电话 → 13203801799(CUSTOMER_SERVICE_PHONE,协议文案走常量自动同步)

权益页去掉分享按钮,菜单分享保留
支付成功改 reLaunch 清栈;订单列表返回首页
减数量低于起购时 toast「同城/跨城/现场提货至少购买 N 瓶」
核销成功可评星并 POST /redeem/ratings;HQ 新增「门店评价」页 /store-ratings
成功页「回到首页」改用 .redeem-cancel-btn,不再撑破布局
历史记录改为核销流水:GET /redeem/records;生产库已有 2 笔(用户 13073729990)可验证。权益明细字段映射也一并修了
我的页头像提高点击层级,避免被卡片遮挡
This commit is contained in:
2026-08-03 13:09:59 +08:00
parent 9f67221457
commit 87c4af7364
22 changed files with 506 additions and 55 deletions
@@ -119,7 +119,29 @@ export class BenefitService {
where: benefitLedgerWhere(userId, couponId),
orderBy: { createdAt: 'desc' },
});
return serializeBigInt(list);
return serializeBigInt(
list.map((e) => {
const type = e.param1 || '';
const amount = Number(e.amount1 ?? 0);
const title =
e.remark ||
(type === 'REDEEM'
? '门店核销'
: type === 'GRANT'
? '购酒入账'
: type === 'REFUND_VOID'
? '退款作废'
: '权益变动');
return {
id: e.id,
type,
amount,
title,
createdAt: e.createdAt,
balanceAfter: e.amount2 != null ? Number(e.amount2) : null,
};
}),
);
}
async getCoupon(userId: bigint, couponId: bigint) {
@@ -0,0 +1,15 @@
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { HqAuthGuard } from '../../common/guards/hq-auth.guard';
import { AdminStoreRatingsService } from './admin-store-ratings.service';
import { AdminStoreRatingsQueryDto } from './dto/admin-query.dto';
@Controller('admin/store-ratings')
@UseGuards(HqAuthGuard)
export class AdminStoreRatingsController {
constructor(private readonly service: AdminStoreRatingsService) {}
@Get()
list(@Query() query: AdminStoreRatingsQueryDto) {
return this.service.list(query);
}
}
@@ -0,0 +1,58 @@
import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
import type { AdminStoreRatingsQueryDto } from './dto/admin-query.dto';
@Injectable()
export class AdminStoreRatingsService {
constructor(private readonly prisma: PrismaService) {}
async list(query: AdminStoreRatingsQueryDto) {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const where: Prisma.StoreRatingWhereInput = {};
if (query.storeId) where.storeId = BigInt(query.storeId);
if (query.redeemNo) {
where.redeemRecord = { redeemNo: { contains: query.redeemNo } };
}
const [items, total] = await Promise.all([
this.prisma.storeRating.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (page - 1) * pageSize,
take: pageSize,
include: {
store: { select: { id: true, name: true, cityName: true } },
redeemRecord: {
select: {
id: true,
redeemNo: true,
amount: true,
user: { select: { id: true, userNo: true, phone: true, nickname: true } },
},
},
},
}),
this.prisma.storeRating.count({ where }),
]);
return serializeBigInt({
items: items.map((r) => ({
id: r.id,
serviceScore: r.serviceScore,
envScore: r.envScore,
createdAt: r.createdAt,
store: r.store,
redeemRecordId: r.redeemRecordId,
redeemNo: r.redeemRecord.redeemNo,
redeemAmount: Number(r.redeemRecord.amount),
user: r.redeemRecord.user,
})),
total,
page,
pageSize,
});
}
}
@@ -244,6 +244,16 @@ export class AdminRedeemRecordsQueryDto extends PaginationQueryDto {
userId?: string;
}
export class AdminStoreRatingsQueryDto extends PaginationQueryDto {
@IsOptional()
@IsString()
storeId?: string;
@IsOptional()
@IsString()
redeemNo?: string;
}
export class AdminDeliveriesQueryDto extends PaginationQueryDto {
@IsOptional()
@IsString()
@@ -23,6 +23,8 @@ import { AdminBenefitCouponsController, AdminBenefitLedgersController } from './
import { AdminBenefitService } from './admin-benefit.service';
import { AdminRedeemRecordsController, AdminDeliveriesController } from './admin-redeem.controller';
import { AdminRedeemService, AdminDeliveriesService } from './admin-redeem.service';
import { AdminStoreRatingsController } from './admin-store-ratings.controller';
import { AdminStoreRatingsService } from './admin-store-ratings.service';
import { AdminHqAccountsController } from './admin-hq-accounts.controller';
import { AdminHqAccountsService } from './admin-hq-accounts.service';
import { AdminProductsController } from './admin-products.controller';
@@ -90,6 +92,7 @@ import { AdminFulfillmentProvidersController } from './admin-fulfillment-provide
AdminBenefitCouponsController,
AdminBenefitLedgersController,
AdminRedeemRecordsController,
AdminStoreRatingsController,
AdminDeliveriesController,
AdminHqAccountsController,
AdminProductsController,
@@ -122,6 +125,7 @@ import { AdminFulfillmentProvidersController } from './admin-fulfillment-provide
AdminCitiesService,
AdminBenefitService,
AdminRedeemService,
AdminStoreRatingsService,
AdminDeliveriesService,
AdminHqAccountsService,
AdminProductsService,
@@ -36,6 +36,19 @@ export class UserRedeemController {
rating(@CurrentUser() user: AuthUser, @Body() body: Record<string, unknown>) {
return this.redeemService.submitRating(user.actorId, body as never);
}
@Get('records')
records(
@CurrentUser() user: AuthUser,
@Query('page') page = '1',
@Query('pageSize') pageSize = '20',
) {
return this.redeemService.listUserRecords(
user.actorId,
Number(page),
Number(pageSize),
);
}
}
@Controller('shop/redeem')
@@ -1095,11 +1095,51 @@ export class RedeemService {
});
}
async listUserRecords(userId: bigint, page = 1, pageSize = 20) {
const take = Math.min(Math.max(pageSize, 1), 50);
const skip = (Math.max(page, 1) - 1) * take;
const [list, total] = await Promise.all([
this.prisma.redeemRecord.findMany({
where: { userId },
orderBy: { createdAt: 'desc' },
skip,
take,
include: {
store: { select: { id: true, name: true } },
rating: { select: { serviceScore: true, envScore: true } },
},
}),
this.prisma.redeemRecord.count({ where: { userId } }),
]);
return {
list: serializeBigInt(
list.map((r) => ({
id: r.id,
redeemNo: r.redeemNo,
amount: Number(r.amount),
storeId: r.storeId,
storeName: r.store?.name ?? '门店',
createdAt: r.createdAt,
rating: r.rating
? { serviceScore: r.rating.serviceScore, envScore: r.rating.envScore }
: null,
})),
),
total,
page: Math.max(page, 1),
pageSize: take,
};
}
async submitRating(userId: bigint, body: { redeemRecordId: string; serviceScore: number; envScore: number }) {
const record = await this.prisma.redeemRecord.findFirst({
where: { id: BigInt(body.redeemRecordId), userId },
});
if (!record) throw new NotFoundException('核销记录不存在');
const existing = await this.prisma.storeRating.findUnique({
where: { redeemRecordId: record.id },
});
if (existing) return serializeBigInt(existing);
const rating = await this.prisma.storeRating.create({
data: {
redeemRecordId: record.id,