feat(records): auto-route create/edit через draft когда approvalRequired
This commit is contained in:
@@ -37,7 +37,7 @@ import {
|
||||
useRecords,
|
||||
type RecordsFilter,
|
||||
} from '@/api/queries'
|
||||
import { useBulkCloseRecords, useBulkExportRecords, useCreateRecord, useUpdateRecord, useCloseRecord, useFormIdempotencyKey } from '@/api/mutations'
|
||||
import { useBulkCloseRecords, useBulkExportRecords, useCreateRecord, useUpdateRecord, useCloseRecord, useFormIdempotencyKey, useSubmitDraft } from '@/api/mutations'
|
||||
import { useCanMutate } from '@/auth/useCanMutate'
|
||||
import { cn } from '@/lib/utils'
|
||||
import type { BulkCloseResponse, CreateRecordRequest, DataScope, FlattenedRecord } from '@/api/client'
|
||||
@@ -185,6 +185,12 @@ function DictionaryDetail() {
|
||||
}, [pendingDraftsQuery.data])
|
||||
const createMut = useCreateRecord(name)
|
||||
const updateMut = useUpdateRecord(name)
|
||||
// Approval Workflow v2 (D2=A): когда dict отмечен approvalRequired=true,
|
||||
// backend rejects direct CRUD с 409 draft_required. Используем submit-draft
|
||||
// путь автоматически — UX same form, но POST в /records/drafts вместо
|
||||
// /records. Maker подаёт proposal, reviewer approve'нёт → запись применится.
|
||||
const submitDraftMut = useSubmitDraft(name)
|
||||
const approvalRequired = detailQuery.data?.approvalRequired === true
|
||||
// Per-form idempotency key. Stable между ререндерами для одной сессии edit
|
||||
// modal'а: двойной submit (Enter + click) не создаст 2 record'а — backend
|
||||
// deduplicate'ит по тому же ключу. Reset на onSuccess.
|
||||
@@ -527,6 +533,29 @@ function DictionaryDetail() {
|
||||
setConflict({ payload: submitReq, businessKey: bk })
|
||||
}
|
||||
if (edit.kind === 'create') {
|
||||
if (approvalRequired) {
|
||||
// Approval Workflow v2: POST /records/drafts вместо /records. Backend
|
||||
// создаёт draft со status=PENDING, после approve reviewer'ом запись
|
||||
// материализуется в live table. UX same form, разный endpoint.
|
||||
submitDraftMut.mutate(
|
||||
{
|
||||
businessKey: submitReq.businessKey ?? '',
|
||||
operation: 'CREATE',
|
||||
data: submitReq.data,
|
||||
geometryWkt: submitReq.geometryWkt ?? null,
|
||||
validFrom: submitReq.validFrom ?? null,
|
||||
validTo: submitReq.validTo ?? null,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setEdit({ kind: 'closed' })
|
||||
resetRecordIdempotencyKey()
|
||||
},
|
||||
onError: (err) => handleSaveError(err, submitReq.businessKey ?? ''),
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
createMut.mutate(
|
||||
{ payload: submitReq, idempotencyKey: recordIdempotencyKey },
|
||||
{
|
||||
@@ -544,6 +573,27 @@ function DictionaryDetail() {
|
||||
return
|
||||
}
|
||||
if (edit.kind === 'edit') {
|
||||
if (approvalRequired) {
|
||||
// Approval Workflow v2: update тоже через draft (UPDATE proposal).
|
||||
submitDraftMut.mutate(
|
||||
{
|
||||
businessKey: edit.record.businessKey,
|
||||
operation: 'UPDATE',
|
||||
data: submitReq.data,
|
||||
geometryWkt: submitReq.geometryWkt ?? null,
|
||||
validFrom: submitReq.validFrom ?? null,
|
||||
validTo: submitReq.validTo ?? null,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setEdit({ kind: 'closed' })
|
||||
resetRecordIdempotencyKey()
|
||||
},
|
||||
onError: (err) => handleSaveError(err, edit.record.businessKey),
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
updateMut.mutate(
|
||||
{
|
||||
businessKey: edit.record.businessKey,
|
||||
@@ -596,8 +646,14 @@ function DictionaryDetail() {
|
||||
)
|
||||
}
|
||||
|
||||
const activeMutation =
|
||||
edit.kind === 'create' ? createMut : edit.kind === 'edit' ? updateMut : null
|
||||
// Approval Workflow v2: когда dict approval-required — мы делаем submit
|
||||
// через submitDraftMut, поэтому activeMutation должен указывать на него,
|
||||
// иначе spinner / error из createMut/updateMut выйдут несинхронно.
|
||||
const activeMutation = (() => {
|
||||
if (edit.kind === 'create') return approvalRequired ? submitDraftMut : createMut
|
||||
if (edit.kind === 'edit') return approvalRequired ? submitDraftMut : updateMut
|
||||
return null
|
||||
})()
|
||||
const serverError = serverErrorMessage(activeMutation?.error)
|
||||
|
||||
const totalRecords = recordsResult.data?.length ?? 0
|
||||
@@ -1091,7 +1147,17 @@ function DictionaryDetail() {
|
||||
}}
|
||||
caption={
|
||||
edit.kind === 'edit'
|
||||
? t('drawer.captionEdit', { dict: name })
|
||||
? approvalRequired
|
||||
? t('drawer.captionEditDraft', {
|
||||
dict: name,
|
||||
defaultValue: `${name} — proposal на изменение (через ревью)`,
|
||||
})
|
||||
: t('drawer.captionEdit', { dict: name })
|
||||
: approvalRequired
|
||||
? t('drawer.captionCreateDraft', {
|
||||
dict: name,
|
||||
defaultValue: `${name} — proposal на создание (через ревью)`,
|
||||
})
|
||||
: t('drawer.captionCreate', { dict: name })
|
||||
}
|
||||
recordId={edit.kind === 'edit' ? edit.record.businessKey : undefined}
|
||||
|
||||
Reference in New Issue
Block a user