Files
portal/app/resources/js/api/deals.ts
T

247 lines
7.1 KiB
TypeScript

import { apiClient, ensureCsrfCookie } from './client';
/**
* API-вызовы для DealController.
*
* На MVP без auth — tenant_id передаётся параметром (на prod возьмётся из
* middleware → backend параметр исчезнет).
*/
export interface CreateDealPayload {
tenant_id: number;
project_name: string;
phone: string;
contact_name?: string;
status?: string;
manager_id?: number;
comment?: string;
}
export interface CreatedDeal {
id: number;
tenant_id: number;
project_id: number;
phone: string;
status: string;
contact_name: string | null;
manager_id: number | null;
received_at: string;
}
export async function createDeal(payload: CreateDealPayload): Promise<CreatedDeal> {
await ensureCsrfCookie();
const { data } = await apiClient.post<{ deal: CreatedDeal; message: string }>('/api/deals', payload);
return data.deal;
}
export interface BulkDeleteDealsPayload {
tenant_id: number;
ids: number[];
}
export interface BulkDeleteDealsResponse {
deleted: number;
requested: number;
}
/** Bulk soft-delete. Возвращает {deleted, requested} — deleted может быть < requested
* если часть id чужие или уже удалены (NO-OP). */
export async function bulkDeleteDeals(payload: BulkDeleteDealsPayload): Promise<BulkDeleteDealsResponse> {
await ensureCsrfCookie();
const { data } = await apiClient.delete<BulkDeleteDealsResponse>('/api/deals', { data: payload });
return data;
}
export interface BulkRestoreDealsPayload {
tenant_id: number;
ids: number[];
}
export interface BulkRestoreDealsResponse {
restored: number;
requested: number;
}
/** Bulk restore soft-deleted сделок. NO-OP idempotent для не-удалённых. */
export async function bulkRestoreDeals(payload: BulkRestoreDealsPayload): Promise<BulkRestoreDealsResponse> {
await ensureCsrfCookie();
const { data } = await apiClient.post<BulkRestoreDealsResponse>('/api/deals/restore', payload);
return data;
}
export interface ExportDealsPayload {
tenant_id: number;
ids: number[];
format?: 'csv' | 'xlsx';
}
export interface UpdateDealPayload {
tenant_id: number;
comment?: string | null;
manager_id?: number | null;
status?: string;
}
export async function updateDeal(id: number, payload: UpdateDealPayload): Promise<ApiDealDetail> {
await ensureCsrfCookie();
const { data } = await apiClient.patch<{ deal: ApiDealDetail }>(`/api/deals/${id}`, payload);
return data.deal;
}
export interface TransitionDealsPayload {
tenant_id: number;
ids: number[];
status: string;
}
export interface TransitionDealsResponse {
updated: number;
requested: number;
status: string;
}
/** Bulk status-update. Возвращает {updated, requested} — updated может быть < requested
* если часть id'шников чужие или уже в этом статусе (NO-OP). */
export async function transitionDeals(payload: TransitionDealsPayload): Promise<TransitionDealsResponse> {
await ensureCsrfCookie();
const { data } = await apiClient.post<TransitionDealsResponse>('/api/deals/transition', payload);
return data;
}
/** CSV-export. Возвращает строку с BOM — caller заворачивает в Blob+download. */
export async function exportDeals(payload: ExportDealsPayload): Promise<string> {
await ensureCsrfCookie();
const { data } = await apiClient.post<string>(
'/api/deals/export',
{ ...payload, format: 'csv' },
{ responseType: 'text' },
);
return data;
}
/** XLSX-export. Возвращает Blob (binary) — caller триггерит download. */
export async function exportDealsXlsx(payload: Omit<ExportDealsPayload, 'format'>): Promise<Blob> {
await ensureCsrfCookie();
const { data } = await apiClient.post<Blob>(
'/api/deals/export',
{ ...payload, format: 'xlsx' },
{ responseType: 'blob' },
);
return data;
}
export interface ApiDeal {
id: number;
tenant_id: number;
project_id: number;
project_name: string | null;
phone: string;
contact_name: string | null;
status: string;
manager_id: number | null;
manager_name: string | null;
manager_initials: string | null;
received_at: string | null;
}
export interface ApiDealEvent {
id: number;
event: string;
context: Record<string, unknown> | null;
created_at: string | null;
actor: { id: number; name: string; initials: string } | null;
}
export interface ApiDealDetail extends ApiDeal {
comment: string | null;
assigned_at: string | null;
}
export interface GetDealResponse {
deal: ApiDealDetail;
events: ApiDealEvent[];
}
export async function getDeal(id: number, tenantId: number): Promise<GetDealResponse> {
const { data } = await apiClient.get<GetDealResponse>(`/api/deals/${id}`, {
params: { tenant_id: tenantId },
});
return data;
}
export interface ListDealsParams {
tenantId: number;
statusIn?: string[];
projectId?: number;
managerId?: number;
search?: string;
limit?: number;
offset?: number;
/** «Корзина» — вернуть ТОЛЬКО soft-deleted сделки. */
onlyDeleted?: boolean;
}
export interface ListDealsResponse {
deals: ApiDeal[];
total: number;
limit: number;
offset: number;
}
export async function listDeals(params: ListDealsParams): Promise<ListDealsResponse> {
const { data } = await apiClient.get<ListDealsResponse>('/api/deals', {
params: {
tenant_id: params.tenantId,
status_in: params.statusIn,
project_id: params.projectId,
manager_id: params.managerId,
search: params.search,
limit: params.limit,
offset: params.offset,
only_deleted: params.onlyDeleted ? 'true' : undefined,
},
});
return data;
}
export interface ApiManager {
id: number;
email: string;
first_name: string | null;
last_name: string | null;
name: string;
initials: string;
}
export async function listManagers(tenantId: number): Promise<ApiManager[]> {
const { data } = await apiClient.get<{ managers: ApiManager[] }>('/api/managers', {
params: { tenant_id: tenantId },
});
return data.managers;
}
export interface ApiProject {
id: number;
name: string;
tag: string | null;
type: string;
}
export async function listProjects(tenantId: number): Promise<ApiProject[]> {
const { data } = await apiClient.get<{ projects: ApiProject[] }>('/api/projects', {
params: { tenant_id: tenantId },
});
return data.projects;
}
/**
* Лёгкий count-only запрос для бейджа «Сделки» в AppSidebar (audit B2).
* Backend пропускает SELECT строк — отдаёт только COUNT(*).
*/
export async function fetchDealsCount(tenantId: number): Promise<number> {
const { data } = await apiClient.get<{ total: number }>('/api/deals', {
params: { tenant_id: tenantId, count_only: 1 },
});
return data.total;
}