This commit is contained in:
2026-06-30 10:33:56 +08:00
commit 6e047dc0a5
607 changed files with 65966 additions and 0 deletions
@@ -0,0 +1,33 @@
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
import { BenefitService } from './benefit.service';
import { JwtAuthGuard, AuthUser } from '../../common/guards/jwt-auth.guard';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
@Controller('benefit')
@UseGuards(JwtAuthGuard)
export class BenefitController {
constructor(private readonly benefitService: BenefitService) {}
@Get('coupons')
coupons(@CurrentUser() user: AuthUser) {
return this.benefitService.listCoupons(user.actorId);
}
@Get('summary')
summary(@CurrentUser() user: AuthUser) {
return this.benefitService.getSummary(user.actorId);
}
@Get('coupons/:id')
coupon(@CurrentUser() user: AuthUser, @Param('id') id: string) {
return this.benefitService.getCoupon(user.actorId, BigInt(id));
}
@Get('ledger')
ledger(@CurrentUser() user: AuthUser, @Query('couponId') couponId?: string) {
return this.benefitService.getLedger(
user.actorId,
couponId ? BigInt(couponId) : undefined,
);
}
}
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { IamModule } from '../iam/iam.module';
import { BenefitController } from './benefit.controller';
import { BenefitService } from './benefit.service';
@Module({
imports: [IamModule],
controllers: [BenefitController],
providers: [BenefitService],
exports: [BenefitService],
})
export class BenefitModule {}
@@ -0,0 +1,92 @@
import { Injectable } from '@nestjs/common';
import { calcBenefitAmount, calcBenefitSummary, generateCouponNo } from '@dukang/domain';
import { REDEEM_MAX_AMOUNT } from '@dukang/shared-types';
import { PrismaService } from '../../common/prisma/prisma.module';
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
@Injectable()
export class BenefitService {
constructor(private readonly prisma: PrismaService) {}
async grantOnOrderPaid(orderId: bigint) {
const order = await this.prisma.order.findUniqueOrThrow({
where: { id: orderId },
include: { items: true },
});
const item = order.items[0];
if (!item) return null;
const product = await this.prisma.product.findUnique({ where: { id: item.productId } });
const unitBenefit = calcBenefitAmount({
price: Number(item.unitPrice),
benefitAmount: product?.benefitAmount ? Number(product.benefitAmount) : null,
});
const totalBenefit = unitBenefit * item.quantity;
const coupon = await this.prisma.benefitCoupon.create({
data: {
couponNo: generateCouponNo(),
userId: order.userId,
orderId: order.id,
totalAmount: totalBenefit,
balance: totalBenefit,
sourceProduct: item.productName,
},
});
await this.prisma.benefitLedger.create({
data: {
userId: order.userId,
couponId: coupon.id,
type: 'GRANT',
amount: totalBenefit,
balanceAfter: totalBenefit,
refType: 'ORDER',
refId: order.id,
remark: '购酒赠券',
},
});
return serializeBigInt(coupon);
}
async listCoupons(userId: bigint) {
const list = await this.prisma.benefitCoupon.findMany({
where: { userId, status: { in: ['ACTIVE', 'USED_UP'] } },
orderBy: { createdAt: 'desc' },
});
return serializeBigInt(list);
}
async getSummary(userId: bigint) {
const coupons = await this.prisma.benefitCoupon.findMany({
where: { userId, status: 'ACTIVE' },
orderBy: { createdAt: 'asc' },
});
const summary = calcBenefitSummary(
coupons.map((c) => Number(c.balance)),
REDEEM_MAX_AMOUNT,
);
return serializeBigInt(summary);
}
async getLedger(userId: bigint, couponId?: bigint) {
const list = await this.prisma.benefitLedger.findMany({
where: { userId, ...(couponId ? { couponId } : {}) },
orderBy: { createdAt: 'desc' },
});
return serializeBigInt(list);
}
async getCoupon(userId: bigint, couponId: bigint) {
const coupon = await this.prisma.benefitCoupon.findFirst({
where: { id: couponId, userId },
});
if (!coupon) return null;
const ledgers = await this.prisma.benefitLedger.findMany({
where: { couponId },
orderBy: { createdAt: 'desc' },
});
return serializeBigInt({ coupon, ledgers });
}
}