feat(records): bulk close — multi-select toolbar + per-record транзакция
Backend POST /api/v1/dictionaries/{name}/records/bulk-close принимает
businessKeys[] (1..500) + reason. BulkRecordService крутит цикл через
inject'ed DictionaryRecordService — каждый close в своей транзакции
(SERIALIZABLE), partial success возможен. Result: closed[] / skipped[]
(already_closed, not_found) / errors[].
Frontend: checkbox column + sticky toolbar когда selection.size > 0.
Header checkbox с indeterminate state для partial select. selectAll
включает все ВИДИМЫЕ ключи (не игнорирует фильтры). При изменении
фильтра — useEffect выкидывает невидимые ключи из selection (WYSIWYG).
Bulk close dialog: count + reason input → confirm → результат панель
"Закрыто N, пропущено M, ошибок K" с детальным списком skipped/errors.
После успеха selection остаётся для skipped/errors — юзер видит что
ещё не сделано.
Лимит 500: защита от случайного огромного селекта или бага UI.
Дедупликация внутри batch'а silent skip — не error.
This commit is contained in:
@@ -113,6 +113,18 @@ export type CreateRecordRequest = {
|
||||
validTo?: string
|
||||
}
|
||||
|
||||
export type BulkCloseRequest = {
|
||||
businessKeys: string[]
|
||||
at?: string
|
||||
reason?: string
|
||||
}
|
||||
|
||||
export type BulkCloseResponse = {
|
||||
closed: string[]
|
||||
skipped: { businessKey: string; reason: string }[]
|
||||
errors: { businessKey: string; reason: string }[]
|
||||
}
|
||||
|
||||
export type AuditAction = 'CREATE' | 'UPDATE' | 'CLOSE'
|
||||
|
||||
export type AuditEntry = {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
apiClient,
|
||||
type BulkCloseRequest,
|
||||
type BulkCloseResponse,
|
||||
type CreateDictionaryRequest,
|
||||
type CreateRecordRequest,
|
||||
type CreateWebhookSubscriptionRequest,
|
||||
@@ -101,6 +103,26 @@ export const useCloseRecord = (dictionaryName: string) => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk close: закрыть N records одним запросом. Backend per-record транзакция,
|
||||
* partial success возможен (already_closed / not_found попадает в skipped).
|
||||
*/
|
||||
export const useBulkCloseRecords = (dictionaryName: string) => {
|
||||
const qc = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: async (req: BulkCloseRequest): Promise<BulkCloseResponse> => {
|
||||
const { data } = await apiClient.post<BulkCloseResponse>(
|
||||
`/dictionaries/${dictionaryName}/records/bulk-close`,
|
||||
req,
|
||||
)
|
||||
return data
|
||||
},
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ['records', dictionaryName] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// === Webhooks (v2) ===
|
||||
|
||||
export const useCreateWebhook = () => {
|
||||
|
||||
Reference in New Issue
Block a user