feat(server): add booking, payment, and scheduler modules

Booking: reservation with atomic transactions, cancellation with refund
logic based on cancelHoursLimit (23 tests)
Payment: WeChat Pay integration (mock), order lifecycle, membership
creation on payment callback (13 tests)
Scheduler: cron tasks for slot generation, cleanup, membership expiry (8 tests)
109 total tests passing across 9 test suites
This commit is contained in:
richarjiang
2026-04-02 12:33:50 +08:00
parent 593a6e5453
commit 994d1f75d5
15 changed files with 2183 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
import { Injectable, Logger } from '@nestjs/common'
import { Cron } from '@nestjs/schedule'
import { SlotGeneratorService } from '../time-slot/slot-generator.service'
@Injectable()
export class SchedulerService {
private readonly logger = new Logger(SchedulerService.name)
constructor(private readonly slotGenerator: SlotGeneratorService) {}
/** 02:00 daily — generate slots 14 days ahead from week templates */
@Cron('0 2 * * *')
async handleSlotGeneration(): Promise<void> {
try {
const count = await this.slotGenerator.generateSlots(14)
this.logger.log(`[handleSlotGeneration] Created ${count} new time slots`)
} catch (err) {
this.logger.error('[handleSlotGeneration] Failed to generate slots', err)
}
}
/** 02:30 daily — close past OPEN slots */
@Cron('30 2 * * *')
async handleCleanupSlots(): Promise<void> {
try {
const count = await this.slotGenerator.cleanupExpiredSlots()
this.logger.log(`[handleCleanupSlots] Closed ${count} expired slots`)
} catch (err) {
this.logger.error('[handleCleanupSlots] Failed to clean up slots', err)
}
}
/** 03:00 daily — expire memberships past their end date or with 0 sessions */
@Cron('0 3 * * *')
async handleCheckMemberships(): Promise<void> {
try {
const count = await this.slotGenerator.checkExpiredMemberships()
this.logger.log(`[handleCheckMemberships] Updated ${count} memberships`)
} catch (err) {
this.logger.error('[handleCheckMemberships] Failed to check memberships', err)
}
}
/** 22:00 daily — mark past CONFIRMED bookings as COMPLETED */
@Cron('0 22 * * *')
async handleCompleteBookings(): Promise<void> {
try {
const count = await this.slotGenerator.completeBookings()
this.logger.log(`[handleCompleteBookings] Completed ${count} bookings`)
} catch (err) {
this.logger.error('[handleCompleteBookings] Failed to complete bookings', err)
}
}
}