feat: 支持分享邀请好友功能

This commit is contained in:
richarjiang
2026-04-19 14:12:25 +08:00
parent b02f38dcc7
commit 9575210b06
34 changed files with 1101 additions and 8 deletions

View File

@@ -11,6 +11,7 @@ import { PrismaService } from '../../prisma/prisma.service'
import { MembershipService } from '../../membership/membership.service'
import { StudioService } from '../../studio/studio.service'
import { SubscriptionMessageService } from '../../user/subscription-message.service'
import { InviteService } from '../../invite/invite.service'
// ─── Fixtures ──────────────────────────────────────────────────────────────
@@ -153,6 +154,7 @@ describe('BookingService', () => {
let prisma: jest.Mocked<PrismaService>
let studioService: jest.Mocked<StudioService>
let subscriptionMessageService: { sendBookingConfirmedMessage: jest.Mock; sendAdminBookingCreatedMessage: jest.Mock }
let inviteService: { recordQualifiedTrialBooking: jest.Mock }
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
@@ -204,6 +206,12 @@ describe('BookingService', () => {
sendAdminBookingCreatedMessage: jest.fn(),
},
},
{
provide: InviteService,
useValue: {
recordQualifiedTrialBooking: jest.fn(),
},
},
],
}).compile()
@@ -211,6 +219,7 @@ describe('BookingService', () => {
prisma = module.get(PrismaService) as jest.Mocked<PrismaService>
studioService = module.get(StudioService) as jest.Mocked<StudioService>
subscriptionMessageService = module.get(SubscriptionMessageService)
inviteService = module.get(InviteService)
})
afterEach(() => jest.clearAllMocks())
@@ -262,6 +271,44 @@ describe('BookingService', () => {
})
})
describe('completeBooking', () => {
it('records qualified trial booking after completion', async () => {
const tx = buildTxMock({
bookingStatusHistory: { create: jest.fn() },
})
tx.booking.findUnique.mockResolvedValue({
...mockConfirmedBooking,
status: BookingStatus.CONFIRMED,
timeSlot: mockOpenSlot,
})
tx.booking.update.mockResolvedValue({
...mockConfirmedBooking,
status: BookingStatus.COMPLETED,
completedAt: new Date('2099-12-31T11:00:00Z'),
})
;(prisma.$transaction as jest.Mock).mockImplementation((fn) => fn(tx))
;(prisma.booking.findUnique as jest.Mock).mockResolvedValue({
...mockConfirmedBooking,
status: BookingStatus.COMPLETED,
completedAt: new Date('2099-12-31T11:00:00Z'),
timeSlot: mockOpenSlot,
membership: {
...mockActiveMembership,
cardType: {
...mockTimesCardType,
type: CardTypeCategory.TRIAL,
},
},
})
await service.completeBooking(MOCK_BOOKING_ID, 'admin-001')
expect(inviteService.recordQualifiedTrialBooking).toHaveBeenCalledWith(MOCK_BOOKING_ID)
})
})
// ─── createBooking ────────────────────────────────────────────────────────
describe('createBooking', () => {

View File

@@ -4,9 +4,10 @@ import { BookingService } from './booking.service'
import { MembershipModule } from '../membership/membership.module'
import { StudioModule } from '../studio/studio.module'
import { UserModule } from '../user/user.module'
import { InviteModule } from '../invite/invite.module'
@Module({
imports: [MembershipModule, StudioModule, UserModule],
imports: [MembershipModule, StudioModule, UserModule, InviteModule],
controllers: [BookingController],
providers: [BookingService],
exports: [BookingService],

View File

@@ -12,6 +12,7 @@ import { MembershipService } from '../membership/membership.service'
import { StudioService } from '../studio/studio.service'
import { SubscriptionMessageService } from '../user/subscription-message.service'
import { CreateBookingDto } from './dto/create-booking.dto'
import { InviteService } from '../invite/invite.service'
// ─── Types ─────────────────────────────────────────────────────────────────
@@ -50,6 +51,7 @@ export class BookingService {
private readonly membershipService: MembershipService,
private readonly studioService: StudioService,
private readonly subscriptionMessageService: SubscriptionMessageService,
private readonly inviteService: InviteService,
) {}
// ─── Create Booking ──────────────────────────────────────────────────────
@@ -330,7 +332,11 @@ export class BookingService {
return updated
})
return this.fetchBookingWithRelations(booking.id)
const result = await this.fetchBookingWithRelations(booking.id)
if (toStatus === BookingStatus.COMPLETED) {
await this.inviteService.recordQualifiedTrialBooking(result.id)
}
return result
}
// ─── Cancel Booking ──────────────────────────────────────────────────────