feat: 支持任务监控系统

This commit is contained in:
richarjiang
2026-07-15 18:50:12 +08:00
parent f8c8c688a1
commit 4b465843a8
16 changed files with 1389 additions and 29 deletions

View File

@@ -53,15 +53,14 @@ describe('范思伯特福中福响应映射', () => {
})
vi.stubGlobal('fetch', fetchMock)
await fansiboteFuzhongAdapter.getAvailability(query, {
courtId: query.courtId
})
await fansiboteFuzhongAdapter.getAvailability(query)
expect(fetchMock).toHaveBeenCalledOnce()
const request = fetchMock.mock.calls[0]?.[1] as RequestInit
expect(request.headers).toEqual(expect.objectContaining({
STOREID: '6038652'
}))
expect(request.headers).not.toHaveProperty('PSPLVISITORID')
expect(JSON.parse(String(request.body))).toEqual({
dateTime: '2026-07-16',
userId: 6038652,
@@ -283,6 +282,13 @@ describe('范思伯特福中福响应映射', () => {
},
expect.objectContaining({ id: '1784027073124705081' })
])
expect(result.slots.map(({ status, unavailableLabel }) => ({
status,
unavailableLabel
}))).toEqual([
{ status: 'booked', unavailableLabel: '已订' },
{ status: 'booked', unavailableLabel: '已订' }
])
})
it('接口字段变化时明确失败,不静默丢时段', () => {

View File

@@ -228,19 +228,21 @@ function parseSlot(value: unknown, enrollments: ParsedEnrollment[]): BookingSlot
const slotBeginDatetime = slot.beginDatetime
const slotEndDatetime = slot.endDatetime
const status: BookingSlot['status'] = canBook
? 'available'
: slot.status === 1
? 'booked'
: 'blocked'
const bookedBy = status === 'booked' && appointment
? parseBookingContacts(appointment)
: []
const enrollment = enrollments.find((candidate) =>
candidate.classroomUid === slot.txtClassroomUid &&
slotBeginDatetime <= candidate.endDatetime &&
slotEndDatetime >= candidate.beginDatetime
)?.activity
const status: BookingSlot['status'] = enrollment
? 'booked'
: canBook
? 'available'
: slot.status === 1
? 'booked'
: 'blocked'
const bookedBy = status === 'booked' && appointment
? parseBookingContacts(appointment)
: []
return {
id: `${slot.txtClassroomUid}:${slotBeginDatetime}`,
@@ -298,13 +300,11 @@ export function buildAvailabilityRequestBody(date: string) {
}
async function getAvailability(
query: AvailabilityQuery,
settings: CourtRuntimeSettings
query: AvailabilityQuery
): Promise<AvailabilityDay> {
if (query.courtId !== fansiboteFuzhongCourt.id) {
throw new Error('球场标识与适配器不匹配')
}
if (settings.courtId !== query.courtId) throw new Error('球场配置与适配器不匹配')
if (!DATE_PATTERN.test(query.date)) throw new Error('预约日期格式错误')
const response = await fetch(AVAILABILITY_ENDPOINT, {

View File

@@ -32,11 +32,7 @@ export class AdapterRegistry {
async getAvailability(query: AvailabilityQuery) {
const adapter = this.getAdapter(query.courtId)
const settings = await this.settingsStore.get(
adapter.court.id,
adapter.defaultSettings
)
return adapter.getAvailability(query, settings)
return adapter.getAvailability(query)
}
async getCourtSettings(courtId: string) {
@@ -125,7 +121,7 @@ export class AdapterRegistry {
const availability = await adapter.getAvailability({
courtId: parsedInput.courtId,
date: parsedInput.date
}, settings)
})
const slot = availability.slots.find((candidate) => candidate.id === parsedInput.slotId)
if (!slot) throw new Error('目标时段已不存在,请刷新后重试')
if (slot.status !== 'available' && slot.status !== 'limited') {

View File

@@ -25,10 +25,7 @@ export class ProviderMutationError extends Error {
export interface CourtAdapter {
readonly court: CourtSummary
readonly defaultSettings: CourtRuntimeSettings
getAvailability(
query: AvailabilityQuery,
settings: CourtRuntimeSettings
): Promise<AvailabilityDay>
getAvailability(query: AvailabilityQuery): Promise<AvailabilityDay>
preflightWriteSettings?(settings: CourtRuntimeSettings): void
createBooking?(
slot: BookingSlot,