feat(slots): снятие брони пачками с прогрессом (симметрично бронированию)
Бэк (slots-service): - DELETE /booking/batch (cancelReqSlots) — снятие выбранных броней пачкой; одиночный DELETE теперь request-aware (нужен requestId), чтобы шаренный между заявками слот рвал только связь своей заявки, а не сносил бронь. - BookedSlotViewDTO (+toViewDTO) с cycle БРОНИ — slotKey совпадает с ответом POST booking/request; GET booking/by-request отдаёт View. Фронт (pcp-tgu-ui-service): - runChunked + ChunkProgress: бронь и снятие идут пачками по 200 с общим прогресс-баром (стиль/позиция как у оверлея «Расчёт слотов…», сверху). - «Снять выбранные», «Снять всё» и ✕ в строке — единый чанковый движок runCancel (а не bulk-запрос), брони слиты в строки таблицы и выбираемы. Тесты: SlotServiceCancelTest (шаринг/единоличная/cycle-регрессия), runChunked, slotsApi.cancelBookedSlots/fetchBookedSlotsByRequest.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import type {
|
||||
BookedSlotDTO,
|
||||
BookedSlotView,
|
||||
RequestSlotsParams,
|
||||
SlotBookingRequest,
|
||||
SlotDTO,
|
||||
@@ -103,13 +104,47 @@ export async function cancelRequestBooking(requestId: string): Promise<number> {
|
||||
return Number.isFinite(value) ? value : 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Снимает выбранные брони заявки по списку bookedSlotId (slots-service: DELETE
|
||||
* /api/slots/booking/batch). Один запрос = один чанк; крупный набор чанкует вызывающий
|
||||
* (runChunked), как при бронировании. Список id передаётся в query (?ids=1&ids=2), а не в теле —
|
||||
* тело у DELETE через gateway ненадёжно. requestId обязателен: снятие request-aware, чтобы
|
||||
* шаренный между заявками слот не сносился у других.
|
||||
* @param requestId Идентификатор заявки.
|
||||
* @param bookedSlotIds Идентификаторы броней (bookedSlotId) одного чанка.
|
||||
*
|
||||
* @returns Сколько броней снято в этом чанке.
|
||||
*/
|
||||
export async function cancelBookedSlots(
|
||||
requestId: string,
|
||||
bookedSlotIds: number[],
|
||||
): Promise<number> {
|
||||
if (bookedSlotIds.length === 0) return 0
|
||||
const query = new URLSearchParams()
|
||||
query.set('requestId', requestId)
|
||||
bookedSlotIds.forEach((id) => query.append('ids', String(id)))
|
||||
|
||||
const response = await apiFetch(`${BASE}/booking/batch?${query}`, {
|
||||
method: 'DELETE',
|
||||
headers: { Accept: 'application/json' },
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(await readErrorMessage(response, 'Не удалось снять бронь'))
|
||||
}
|
||||
|
||||
const value = Number(await response.text())
|
||||
return Number.isFinite(value) ? value : 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Загружает уже забронированные слоты заявки (slots-service: GET /api/slots/booking/by-request/{id}).
|
||||
* @param requestId Идентификатор заявки.
|
||||
*
|
||||
* @returns Список забронированных слотов с контурами.
|
||||
* @returns Список забронированных слотов с контурами и bookedSlotId (для снятия по id);
|
||||
* cycle здесь — цикл брони, поэтому slotKey совпадает с ответом POST booking/request.
|
||||
*/
|
||||
export async function fetchBookedSlotsByRequest(requestId: string): Promise<SlotDTO[]> {
|
||||
export async function fetchBookedSlotsByRequest(requestId: string): Promise<BookedSlotView[]> {
|
||||
const response = await apiFetch(
|
||||
`${BASE}/booking/by-request/${encodeURIComponent(requestId)}`,
|
||||
{ headers: { Accept: 'application/json' } },
|
||||
@@ -119,5 +154,5 @@ export async function fetchBookedSlotsByRequest(requestId: string): Promise<Slot
|
||||
throw new Error(await readErrorMessage(response, 'Не удалось загрузить брони заявки'))
|
||||
}
|
||||
|
||||
return response.json() as Promise<SlotDTO[]>
|
||||
return response.json() as Promise<BookedSlotView[]>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user