ok
This commit is contained in:
@@ -14,6 +14,7 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /auth/ {
|
||||
@@ -21,6 +22,7 @@ server {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location /health {
|
||||
|
||||
@@ -2,11 +2,12 @@ import { useState, useEffect } from 'react'
|
||||
import { AuthPage } from './pages/AuthPage'
|
||||
import { CheckCodesPage } from './pages/CheckCodesPage'
|
||||
import { UploadUpdPage } from './pages/UploadUpdPage'
|
||||
import { ValidateUpdPage } from './pages/ValidateUpdPage'
|
||||
import { PlansPage } from './pages/PlansPage'
|
||||
import { setOnUnauthorized, logout as apiLogout, getEnv, setEnv, getAuthStatus } from './services/api'
|
||||
import type { ApiEnv } from './types'
|
||||
|
||||
type Page = 'auth' | 'check' | 'upload' | 'plans'
|
||||
type Page = 'auth' | 'check' | 'upload' | 'validate' | 'plans'
|
||||
|
||||
export default function App() {
|
||||
const [page, setPage] = useState<Page>('auth')
|
||||
@@ -78,6 +79,11 @@ export default function App() {
|
||||
disabled={!authenticated}>
|
||||
Загрузка УПД
|
||||
</button>
|
||||
<button onClick={() => setPage('validate')}
|
||||
style={navBtnStyle(page === 'validate')}
|
||||
disabled={!authenticated}>
|
||||
Проверка УПД (True API)
|
||||
</button>
|
||||
<button onClick={() => setPage('plans')}
|
||||
style={navBtnStyle(page === 'plans')}>
|
||||
Планы
|
||||
@@ -124,6 +130,7 @@ export default function App() {
|
||||
{page === 'auth' && <AuthPage key={env} onAuth={handleAuth} />}
|
||||
{page === 'check' && <CheckCodesPage key={env} />}
|
||||
{page === 'upload' && <UploadUpdPage key={env} />}
|
||||
{page === 'validate' && <ValidateUpdPage key={env} />}
|
||||
{page === 'plans' && <PlansPage key={env} />}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { getAuthStatus, getAuthKey, signIn, setTokenManually } from '../services/api'
|
||||
import { getAuthStatus, getAuthKey, signIn, setTokenManually, getToken } from '../services/api'
|
||||
import type { RequestError } from '../services/api'
|
||||
import { isCadesPluginAvailable, listCertificates, signWithCadesPlugin } from '../services/cadesplugin'
|
||||
import type { CertInfo } from '../services/cadesplugin'
|
||||
@@ -20,6 +20,8 @@ export function AuthPage({ onAuth }: Props) {
|
||||
const [debugExpanded, setDebugExpanded] = useState(false)
|
||||
const [manualToken, setManualToken] = useState('')
|
||||
const [manualLoading, setManualLoading] = useState(false)
|
||||
const [copying, setCopying] = useState(false)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
getAuthStatus().then(setStatus).catch(() => {})
|
||||
@@ -58,6 +60,33 @@ export function AuthPage({ onAuth }: Props) {
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCopyToken() {
|
||||
setCopying(true)
|
||||
setError('')
|
||||
setCopied(false)
|
||||
try {
|
||||
const { token } = await getToken()
|
||||
if (navigator.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(token)
|
||||
} else {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = token
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.opacity = '0'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
}
|
||||
setCopied(true)
|
||||
} catch (err) {
|
||||
const re = err as RequestError
|
||||
setError(re.message)
|
||||
} finally {
|
||||
setCopying(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSignIn() {
|
||||
setLoading(true)
|
||||
setError('')
|
||||
@@ -112,6 +141,17 @@ export function AuthPage({ onAuth }: Props) {
|
||||
<div style={{ fontSize: 13, marginTop: 4 }}>
|
||||
Токен истекает: {new Date(status.tokenExpiresAt!).toLocaleString()}
|
||||
</div>
|
||||
<button
|
||||
onClick={handleCopyToken}
|
||||
disabled={copying}
|
||||
style={{
|
||||
...btnStyle,
|
||||
background: '#2e7d32',
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
{copying ? 'Копирование...' : copied ? '✅ Токен скопирован' : 'Скопировать токен'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
import { useState, useRef } from 'react'
|
||||
import { validateUpd, getUpdValidationStatus } from '../services/api'
|
||||
import type { RequestError } from '../services/api'
|
||||
import type { UpdValidateResponse, UpdValidationResult } from '../types'
|
||||
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024
|
||||
|
||||
export function ValidateUpdPage() {
|
||||
const [file, setFile] = useState<File | null>(null)
|
||||
const [checkPermits, setCheckPermits] = useState(false)
|
||||
const [response, setResponse] = useState<UpdValidateResponse | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
const [errorDebug, setErrorDebug] = useState<string | null>(null)
|
||||
const [debugExpanded, setDebugExpanded] = useState(false)
|
||||
const [dragging, setDragging] = useState(false)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
function setFileIfValid(f: File | null) {
|
||||
if (!f) { setFile(null); return }
|
||||
if (f.size > MAX_FILE_SIZE) {
|
||||
setError('Файл слишком большой (максимум 10MB)')
|
||||
return
|
||||
}
|
||||
setError('')
|
||||
setFile(f)
|
||||
}
|
||||
|
||||
async function handleValidate() {
|
||||
if (!file) return
|
||||
setLoading(true)
|
||||
setError('')
|
||||
setErrorDebug(null)
|
||||
setDebugExpanded(false)
|
||||
try {
|
||||
const result = await validateUpd(file, { checkPermits })
|
||||
setResponse(result)
|
||||
} catch (err) {
|
||||
const re = err as RequestError
|
||||
setError(re.message)
|
||||
if (re.debugInfo) {
|
||||
setErrorDebug(JSON.stringify(re.debugInfo, null, 2))
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRefreshStatus() {
|
||||
if (!response) return
|
||||
setLoading(true)
|
||||
setError('')
|
||||
setErrorDebug(null)
|
||||
setDebugExpanded(false)
|
||||
try {
|
||||
const status = await getUpdValidationStatus(response.validation.documentId)
|
||||
setResponse({ ...response, validation: status })
|
||||
} catch (err) {
|
||||
const re = err as RequestError
|
||||
setError(re.message)
|
||||
if (re.debugInfo) {
|
||||
setErrorDebug(JSON.stringify(re.debugInfo, null, 2))
|
||||
}
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
function handleDrop(e: React.DragEvent) {
|
||||
e.preventDefault()
|
||||
setDragging(false)
|
||||
setFileIfValid(e.dataTransfer.files[0] || null)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Проверка УПД через True API (предварительная проверка)</h2>
|
||||
<p style={{ fontSize: 13, color: '#546e7a', marginTop: -8 }}>
|
||||
Весь XML-документ отправляется в ГИС МТ ({'<doc/validator/create>'}) — проверяются регистрация
|
||||
продавца/покупателя, корректность УД, коды маркировки, статусы КИ и полномочия продавца.
|
||||
</p>
|
||||
|
||||
<div
|
||||
onDragOver={e => { e.preventDefault(); setDragging(true) }}
|
||||
onDragLeave={() => setDragging(false)}
|
||||
onDrop={handleDrop}
|
||||
onClick={() => inputRef.current?.click()}
|
||||
style={{
|
||||
border: `2px dashed ${dragging ? '#1a237e' : '#ccc'}`,
|
||||
borderRadius: 8,
|
||||
padding: 40,
|
||||
textAlign: 'center',
|
||||
cursor: 'pointer',
|
||||
background: dragging ? '#e8eaf6' : '#fafafa',
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
{file ? (
|
||||
<div>
|
||||
<strong>{file.name}</strong> ({(file.size / 1024).toFixed(1)} KB)
|
||||
<br />
|
||||
<button onClick={e => { e.stopPropagation(); setFile(null) }}
|
||||
style={{ marginTop: 8, ...smallBtnStyle }}>
|
||||
Удалить
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div style={{ fontSize: 32, marginBottom: 8 }}>📄</div>
|
||||
<div>Перетащите XML-файл УПД сюда или нажмите для выбора</div>
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
accept=".xml,.XML"
|
||||
hidden
|
||||
onChange={e => setFileIfValid(e.target.files?.[0] || null)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, marginBottom: 16, cursor: 'pointer' }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checkPermits}
|
||||
onChange={e => setCheckPermits(e.target.checked)}
|
||||
/>
|
||||
Проверять статусы разрешительных документов (предупреждения)
|
||||
</label>
|
||||
|
||||
<button onClick={handleValidate} disabled={loading || !file}
|
||||
style={btnStyle}>
|
||||
{loading ? 'Отправка в ГИС МТ и ожидание результата...' : 'Отправить УПД на проверку в True API'}
|
||||
</button>
|
||||
|
||||
{error && (
|
||||
<div style={{ color: '#c62828', marginTop: 12 }}>Ошибка: {error}</div>
|
||||
)}
|
||||
|
||||
{errorDebug && (
|
||||
<div style={{ marginTop: 12, marginBottom: 16 }}>
|
||||
<button
|
||||
onClick={() => setDebugExpanded(!debugExpanded)}
|
||||
style={{
|
||||
background: 'transparent',
|
||||
border: '1px solid #b0bec5',
|
||||
borderRadius: 4,
|
||||
padding: '6px 12px',
|
||||
cursor: 'pointer',
|
||||
fontSize: 13,
|
||||
color: '#37474f',
|
||||
}}
|
||||
>
|
||||
{debugExpanded ? '▼ Скрыть лог запроса к API маркировки' : '▶ Показать лог запроса к API маркировки'}
|
||||
</button>
|
||||
{debugExpanded && (
|
||||
<pre style={{
|
||||
marginTop: 8,
|
||||
padding: 12,
|
||||
background: '#263238',
|
||||
color: '#e0e0e0',
|
||||
borderRadius: 6,
|
||||
fontSize: 12,
|
||||
lineHeight: 1.5,
|
||||
overflowX: 'auto',
|
||||
whiteSpace: 'pre-wrap',
|
||||
wordBreak: 'break-all',
|
||||
}}>
|
||||
{errorDebug}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{response && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<div style={{
|
||||
padding: 12,
|
||||
background: '#e3f2fd',
|
||||
borderRadius: 6,
|
||||
marginBottom: 12,
|
||||
fontSize: 14,
|
||||
}}>
|
||||
<strong>Документ:</strong>{' '}
|
||||
{response.document.number || '—'} от {response.document.date || '—'}
|
||||
<br />
|
||||
<strong>Продавец:</strong> {response.document.seller || '—'}
|
||||
<br />
|
||||
<strong>Покупатель:</strong> {response.document.buyer || '—'}
|
||||
<br />
|
||||
<strong>Кодов в документе:</strong> {response.codesFound}
|
||||
<br />
|
||||
<strong>ИД файла:</strong> {response.validation.documentId}
|
||||
</div>
|
||||
|
||||
<ValidationStatusCard
|
||||
validation={response.validation}
|
||||
refreshing={loading}
|
||||
onRefresh={handleRefreshStatus}
|
||||
/>
|
||||
|
||||
{response.validation.errors.length > 0 && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<h3 style={{ fontSize: 15, margin: '0 0 8px', color: '#c62828' }}>
|
||||
Ошибки проверки ({response.validation.errors.length})
|
||||
</h3>
|
||||
<ErrorTable errors={response.validation.errors} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{response.validation.preliminaryWarnings.length > 0 && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<h3 style={{ fontSize: 15, margin: '0 0 8px', color: '#e65100' }}>
|
||||
Предупреждения ({response.validation.preliminaryWarnings.length})
|
||||
</h3>
|
||||
<WarningTable warnings={response.validation.preliminaryWarnings} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ValidationStatusCard({
|
||||
validation,
|
||||
refreshing,
|
||||
onRefresh,
|
||||
}: {
|
||||
validation: UpdValidationResult
|
||||
refreshing: boolean
|
||||
onRefresh: () => void
|
||||
}) {
|
||||
const metaMap: Record<string, { label: string; color: string; bg: string }> = {
|
||||
VERIFIED: { label: '✅ Документ прошёл проверку', color: '#2e7d32', bg: '#e8f5e9' },
|
||||
NOT_VALID: { label: '❌ Документ не прошёл проверку', color: '#c62828', bg: '#ffebee' },
|
||||
NOT_FOUND: { label: 'Документ не найден в ГИС МТ', color: '#546e7a', bg: '#eceff1' },
|
||||
IN_PROGRESS: { label: '⏳ Документ в обработке', color: '#e65100', bg: '#fff3e0' },
|
||||
AWAITING: { label: '⏳ Документ ожидает обработки', color: '#e65100', bg: '#fff3e0' },
|
||||
}
|
||||
const meta = metaMap[validation.status] || {
|
||||
label: validation.status,
|
||||
color: '#37474f',
|
||||
bg: '#eceff1',
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
padding: 12,
|
||||
background: meta.bg,
|
||||
color: meta.color,
|
||||
borderRadius: 6,
|
||||
fontSize: 14,
|
||||
fontWeight: 600,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
}}>
|
||||
<span>{meta.label}</span>
|
||||
{validation.description && (
|
||||
<span style={{ fontWeight: 400 }}>{validation.description}</span>
|
||||
)}
|
||||
{validation.pending && (
|
||||
<button
|
||||
onClick={onRefresh}
|
||||
disabled={refreshing}
|
||||
style={{
|
||||
marginLeft: 'auto',
|
||||
background: 'transparent',
|
||||
border: '1px solid currentColor',
|
||||
borderRadius: 4,
|
||||
padding: '6px 12px',
|
||||
cursor: 'pointer',
|
||||
fontSize: 13,
|
||||
color: 'inherit',
|
||||
}}
|
||||
>
|
||||
{refreshing ? 'Проверка...' : '🔄 Обновить статус'}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ErrorTable({ errors }: { errors: UpdValidationResult['errors'] }) {
|
||||
return (
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
|
||||
<thead>
|
||||
<tr style={{ background: '#c62828', color: 'white' }}>
|
||||
<th style={thStyle}>Код ошибки</th>
|
||||
<th style={thStyle}>Описание</th>
|
||||
<th style={thStyle}>КИ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{errors.map((e, i) => (
|
||||
<tr key={i} style={{
|
||||
background: i % 2 === 0 ? '#fff' : '#f5f5f5',
|
||||
borderBottom: '1px solid #e0e0e0',
|
||||
}}>
|
||||
<td style={tdStyle}>{e.code ?? '—'}</td>
|
||||
<td style={tdStyle}>{e.description || '—'}</td>
|
||||
<td style={{ ...tdStyle, wordBreak: 'break-all', fontFamily: 'monospace', fontSize: 12 }}>
|
||||
{e.cis || '—'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function WarningTable({ warnings }: { warnings: UpdValidationResult['preliminaryWarnings'] }) {
|
||||
return (
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
|
||||
<thead>
|
||||
<tr style={{ background: '#e65100', color: 'white' }}>
|
||||
<th style={thStyle}>Код</th>
|
||||
<th style={thStyle}>Описание</th>
|
||||
<th style={thStyle}>КИ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{warnings.map((w, i) => (
|
||||
<tr key={i} style={{
|
||||
background: i % 2 === 0 ? '#fff' : '#f5f5f5',
|
||||
borderBottom: '1px solid #e0e0e0',
|
||||
}}>
|
||||
<td style={tdStyle}>{w.code ?? '—'}</td>
|
||||
<td style={tdStyle}>{w.description || '—'}</td>
|
||||
<td style={{ ...tdStyle, wordBreak: 'break-all', fontFamily: 'monospace', fontSize: 12 }}>
|
||||
{w.cis || '—'}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const btnStyle: React.CSSProperties = {
|
||||
background: '#1565c0',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: 6,
|
||||
padding: '10px 20px',
|
||||
cursor: 'pointer',
|
||||
fontSize: 14,
|
||||
}
|
||||
|
||||
const smallBtnStyle: React.CSSProperties = {
|
||||
background: '#e53935',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: 4,
|
||||
padding: '4px 12px',
|
||||
cursor: 'pointer',
|
||||
fontSize: 12,
|
||||
}
|
||||
|
||||
const thStyle: React.CSSProperties = {
|
||||
padding: '8px 12px',
|
||||
textAlign: 'left',
|
||||
fontWeight: 600,
|
||||
fontSize: 12,
|
||||
textTransform: 'uppercase',
|
||||
}
|
||||
|
||||
const tdStyle: React.CSSProperties = {
|
||||
padding: '8px 12px',
|
||||
verticalAlign: 'top',
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AuthStatus, CheckCodesResponse, UpdUploadResponse, ApiErrorResponse, ApiEnv } from '../types'
|
||||
import type { AuthStatus, CheckCodesResponse, UpdUploadResponse, UpdValidateResponse, UpdValidationResult, ApiErrorResponse, ApiEnv } from '../types'
|
||||
|
||||
const BASE_URL = ''
|
||||
|
||||
@@ -21,8 +21,9 @@ export function setEnv(env: ApiEnv): void {
|
||||
currentEnv = env
|
||||
}
|
||||
|
||||
function qs(env?: ApiEnv): string {
|
||||
return `?env=${env || currentEnv}`
|
||||
function qs(url: string, env?: ApiEnv): string {
|
||||
const separator = url.includes('?') ? '&' : '?'
|
||||
return `${separator}env=${env || currentEnv}`
|
||||
}
|
||||
|
||||
async function handleError(res: Response, env: ApiEnv): Promise<never> {
|
||||
@@ -45,7 +46,7 @@ async function request<T>(url: string, options?: RequestInit & { env?: ApiEnv })
|
||||
Object.assign(headers, options.headers as Record<string, string>)
|
||||
}
|
||||
|
||||
const urlWithEnv = `${BASE_URL}${url}${qs(reqEnv)}`
|
||||
const urlWithEnv = `${BASE_URL}${url}${qs(url, reqEnv)}`
|
||||
const res = await fetch(urlWithEnv, {
|
||||
headers,
|
||||
...options,
|
||||
@@ -71,6 +72,10 @@ export async function getAuthStatus(env?: ApiEnv): Promise<AuthStatus> {
|
||||
return request('/auth/status', { env })
|
||||
}
|
||||
|
||||
export async function getToken(env?: ApiEnv): Promise<{ token: string }> {
|
||||
return request('/auth/token', { env })
|
||||
}
|
||||
|
||||
export async function logout(env?: ApiEnv): Promise<void> {
|
||||
await request('/auth/logout', { method: 'POST', env })
|
||||
}
|
||||
@@ -97,6 +102,21 @@ export async function uploadUpd(file: File, env?: ApiEnv): Promise<UpdUploadResp
|
||||
return request('/api/upload-upd', { method: 'POST', body: formData, env })
|
||||
}
|
||||
|
||||
export async function validateUpd(
|
||||
file: File,
|
||||
opts?: { checkPermits?: boolean },
|
||||
env?: ApiEnv,
|
||||
): Promise<UpdValidateResponse> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const query = opts?.checkPermits ? '?checkPermits=1' : ''
|
||||
return request(`/api/validate-upd${query}`, { method: 'POST', body: formData, env })
|
||||
}
|
||||
|
||||
export async function getUpdValidationStatus(documentId: string, env?: ApiEnv): Promise<UpdValidationResult> {
|
||||
return request(`/api/validate-upd/status?documentId=${encodeURIComponent(documentId)}`, { env })
|
||||
}
|
||||
|
||||
export async function setTokenManually(token: string, env?: ApiEnv): Promise<void> {
|
||||
await request('/auth/setToken', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -60,3 +60,30 @@ export interface UpdUploadResponse {
|
||||
codesFound: number
|
||||
results: CheckCodesResponse
|
||||
}
|
||||
|
||||
export interface UpdValidationError {
|
||||
code?: number
|
||||
description?: string
|
||||
cis?: string
|
||||
}
|
||||
|
||||
export interface UpdValidationWarning {
|
||||
code?: string
|
||||
description?: string
|
||||
cis?: string
|
||||
}
|
||||
|
||||
export interface UpdValidationResult {
|
||||
documentId: string
|
||||
status: string
|
||||
description?: string
|
||||
errors: UpdValidationError[]
|
||||
preliminaryWarnings: UpdValidationWarning[]
|
||||
pending: boolean
|
||||
}
|
||||
|
||||
export interface UpdValidateResponse {
|
||||
document: UpdDocument
|
||||
codesFound: number
|
||||
validation: UpdValidationResult
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user