Files
rmser/rmser-view/src/services/api.ts
SERTY 310a64e3ba редактирование и удаление сопоставлений
список накладных с позициями
2025-12-29 10:46:05 +03:00

254 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import axios from 'axios';
import { notification } from 'antd';
import type {
CatalogItem,
CreateInvoiceRequest,
MatchRequest,
HealthResponse,
InvoiceResponse,
ProductMatch,
Recommendation,
UnmatchedItem,
UserSettings,
InvoiceStats,
ProductGroup,
Store,
Supplier,
DraftInvoice,
DraftItem,
UpdateDraftItemRequest,
CommitDraftRequest,
ProductSearchResult,
AddContainerRequest,
AddContainerResponse,
DictionariesResponse,
UnifiedInvoice,
ServerUser,
UserRole,
InvoiceDetails
} from './types';
// Базовый URL
export const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8080/api';
// Хелпер для получения полного URL картинки (убирает /api если путь статики идет от корня, или добавляет как есть)
// В данном ТЗ сказано просто склеивать.
export const getStaticUrl = (path: string | null | undefined): string => {
if (!path) return '';
if (path.startsWith('http')) return path;
return `${API_BASE_URL}${path}`;
};
// Телеграм объект
const tg = window.Telegram?.WebApp;
// Событие для глобальной обработки 401
export const UNAUTHORIZED_EVENT = 'rms_unauthorized';
const apiClient = axios.create({
baseURL: API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
});
// --- Request Interceptor (Авторизация через initData) ---
apiClient.interceptors.request.use((config) => {
const initData = tg?.initData;
// Если initData пустая — мы не в Telegram. Блокируем запрос.
if (!initData) {
console.error('Запрос заблокирован: приложение запущено вне Telegram.');
return Promise.reject(new Error('MISSING_TELEGRAM_DATA'));
}
// Устанавливаем заголовок согласно новым требованиям
config.headers['Authorization'] = `Bearer ${initData}`;
return config;
});
// --- Response Interceptor (Обработка ошибок и уведомления) ---
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response && error.response.status === 401) {
// Глобальное уведомление об ошибке авторизации
notification.error({
message: 'Ошибка авторизации',
description: 'Ваша сессия в Telegram истекла или данные неверны. Попробуйте перезапустить бота.',
placement: 'top',
});
window.dispatchEvent(new Event(UNAUTHORIZED_EVENT));
}
// Если запрос был отменен нами (нет initData), не выводим стандартную ошибку API
if (error.message === 'MISSING_TELEGRAM_DATA') {
return Promise.reject(error);
}
console.error('API Error:', error);
return Promise.reject(error);
}
);
export const api = {
checkHealth: async (): Promise<HealthResponse> => {
const { data } = await apiClient.get<HealthResponse>('/health');
return data;
},
getRecommendations: async (): Promise<Recommendation[]> => {
const { data } = await apiClient.get<Recommendation[]>('/recommendations');
return data;
},
getCatalogItems: async (): Promise<CatalogItem[]> => {
const { data } = await apiClient.get<CatalogItem[]>('/ocr/catalog');
return data;
},
searchProducts: async (query: string): Promise<ProductSearchResult[]> => {
const { data } = await apiClient.get<ProductSearchResult[]>('/ocr/search', {
params: { q: query }
});
return data;
},
createContainer: async (payload: AddContainerRequest): Promise<AddContainerResponse> => {
const { data } = await apiClient.post<AddContainerResponse>('/drafts/container', payload);
return data;
},
getMatches: async (): Promise<ProductMatch[]> => {
const { data } = await apiClient.get<ProductMatch[]>('/ocr/matches');
return data;
},
getUnmatched: async (): Promise<UnmatchedItem[]> => {
const { data } = await apiClient.get<UnmatchedItem[]>('/ocr/unmatched');
return data;
},
createMatch: async (payload: MatchRequest): Promise<{ status: string }> => {
const { data } = await apiClient.post<{ status: string }>('/ocr/match', payload);
return data;
},
deleteMatch: async (rawName: string): Promise<{ status: string }> => {
const { data } = await apiClient.delete<{ status: string }>('/ocr/match', {
params: { raw_name: rawName }
});
return data;
},
deleteUnmatched: async (rawName: string): Promise<{ status: string }> => {
const { data } = await apiClient.delete<{ status: string }>('/ocr/unmatched', {
params: { raw_name: rawName }
});
return data;
},
createInvoice: async (payload: CreateInvoiceRequest): Promise<InvoiceResponse> => {
const { data } = await apiClient.post<InvoiceResponse>('/invoices/send', payload);
return data;
},
// --- НОВЫЙ МЕТОД: Получение всех справочников ---
getDictionaries: async (): Promise<DictionariesResponse> => {
const { data } = await apiClient.get<DictionariesResponse>('/dictionaries');
return data;
},
// Старые методы оставляем для совместимости, но они могут вызывать getDictionaries внутри или deprecated endpoint
getStores: async (): Promise<Store[]> => {
// Можно использовать новый эндпоинт и возвращать часть данных
const { data } = await apiClient.get<DictionariesResponse>('/dictionaries');
return data.stores;
},
getSuppliers: async (): Promise<Supplier[]> => {
// Реальный запрос вместо мока
const { data } = await apiClient.get<DictionariesResponse>('/dictionaries');
return data.suppliers;
},
// Обновленный метод получения списка накладных с фильтрацией
getDrafts: async (from?: string, to?: string): Promise<UnifiedInvoice[]> => {
const { data } = await apiClient.get<UnifiedInvoice[]>('/drafts', {
params: { from, to }
});
return data;
},
getDraft: async (id: string): Promise<DraftInvoice> => {
const { data } = await apiClient.get<DraftInvoice>(`/drafts/${id}`);
return data;
},
updateDraftItem: async (draftId: string, itemId: string, payload: UpdateDraftItemRequest): Promise<DraftInvoice> => {
const { data } = await apiClient.patch<DraftInvoice>(`/drafts/${draftId}/items/${itemId}`, payload);
return data;
},
addDraftItem: async (draftId: string): Promise<DraftItem> => {
const { data } = await apiClient.post<DraftItem>(`/drafts/${draftId}/items`, {});
return data;
},
deleteDraftItem: async (draftId: string, itemId: string): Promise<void> => {
await apiClient.delete(`/drafts/${draftId}/items/${itemId}`);
},
commitDraft: async (draftId: string, payload: CommitDraftRequest): Promise<{ document_number: string }> => {
const { data } = await apiClient.post<{ document_number: string }>(`/drafts/${draftId}/commit`, payload);
return data;
},
deleteDraft: async (id: string): Promise<void> => {
await apiClient.delete(`/drafts/${id}`);
},
// --- Настройки и Статистика ---
getSettings: async (): Promise<UserSettings> => {
const { data } = await apiClient.get<UserSettings>('/settings');
return data;
},
updateSettings: async (payload: UserSettings): Promise<UserSettings> => {
const { data } = await apiClient.post<UserSettings>('/settings', payload);
return data;
},
getStats: async (): Promise<InvoiceStats> => {
const { data } = await apiClient.get<InvoiceStats>('/stats/invoices');
return data;
},
getProductGroups: async (): Promise<ProductGroup[]> => {
const { data } = await apiClient.get<ProductGroup[]>('/dictionaries/groups');
return data;
},
// --- Управление командой ---
getUsers: async (): Promise<ServerUser[]> => {
const { data } = await apiClient.get<ServerUser[]>('/settings/users');
return data;
},
updateUserRole: async (userId: string, newRole: UserRole): Promise<{ status: string }> => {
const { data } = await apiClient.patch<{ status: string }>(`/settings/users/${userId}`, { new_role: newRole });
return data;
},
removeUser: async (userId: string): Promise<{ status: string }> => {
const { data } = await apiClient.delete<{ status: string }>(`/settings/users/${userId}`);
return data;
},
getInvoice: async (id: string): Promise<InvoiceDetails> => {
const { data } = await apiClient.get<InvoiceDetails>(`/invoices/${id}`);
return data;
},
};