Files
portal/app/resources/js/api/notifications.ts
T
Дмитрий 63a2d53255 fix/projects: смена лимита-региона-дней на защищённом проекте больше не блокируется ложно как смена источника
Симптом: на проекте, по которому уже идут лиды от поставщика, правка только лимита, региона или дней отдавала 422 «Изменить источник можно будет после N» — хотя источник не менялся. Найдено приёмкой 25.06.2026 глазами через Playwright. Дефект на main, то есть живой на боевом liderra.ru.

Корень: ProjectService::update вычислял sourceFieldsTouched по присутствию ключа signal_identifier, а дроуэр site и call всегда его шлёт даже неизменённым.

Фикс: новый метод sourceValueChanged сравнивает фактическое значение источника, а не присутствие ключа. Guard срабатывает только на реальную смену источника.

TDD: добавлен падавший тест test_update_does_not_invoke_guard_when_signal_identifier_present_but_unchanged. Larastan чист, phpstan-baseline обновлён под Mockery-шум. Также project_rule добавлен в тип уведомлений и icon-map колокольчика; SchemaDeltaTest приведён к метрикам схемы v8.55 после 2 новых таблиц.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 03:34:55 +03:00

68 lines
2.0 KiB
TypeScript

import { apiClient, ensureCsrfCookie } from './client';
/**
* In-app уведомления для bell-icon UI (schema v8.10).
*
* Все endpoint'ы под Sanctum SPA auth — для незалогиненных вернётся 401.
* Mutating-вызовы (mark-read/mark-all-read/destroy) делают ensureCsrfCookie().
*/
type NotificationEvent =
| 'new_lead'
| 'low_balance'
| 'zero_balance'
| 'topup_success'
| 'invoice_paid'
| 'new_device_login'
| 'marketing'
| 'project_rule';
export interface ApiInAppNotification {
id: number;
event: NotificationEvent;
title: string;
body: string | null;
deal_id: number | null;
payload: Record<string, unknown>;
read_at: string | null;
created_at: string | null;
}
export interface ListNotificationsResponse {
items: ApiInAppNotification[];
unread_count: number;
total: number;
}
export interface ListNotificationsParams {
unreadOnly?: boolean;
limit?: number;
}
export async function listNotifications(params: ListNotificationsParams = {}): Promise<ListNotificationsResponse> {
const { data } = await apiClient.get<ListNotificationsResponse>('/api/notifications', {
params: {
unread_only: params.unreadOnly ? 1 : undefined,
limit: params.limit,
},
});
return data;
}
export async function markNotificationRead(id: number): Promise<{ id: number; read_at: string | null }> {
await ensureCsrfCookie();
const { data } = await apiClient.patch<{ id: number; read_at: string | null }>(`/api/notifications/${id}/read`);
return data;
}
export async function markAllNotificationsRead(): Promise<{ updated: number }> {
await ensureCsrfCookie();
const { data } = await apiClient.post<{ updated: number }>('/api/notifications/mark-all-read');
return data;
}
export async function deleteNotification(id: number): Promise<void> {
await ensureCsrfCookie();
await apiClient.delete(`/api/notifications/${id}`);
}