import { resolveCompanyName } from './companies'; import { config } from './config'; import { getAppOnlyToken } from './graphAuth'; const SHAREPOINT_API_URL = 'https://api.tnsu.kz/SharePoint/ListItem'; export type AppealKind = 'complaint' | 'suggestion' | 'other'; const KIND_LABELS: Record = { complaint: 'Обращение', suggestion: 'Предложение', other: 'Другое' }; export interface IFeedbackSubmission { company: string; isAnonymous: boolean; fullName: string; position: string; wantsFeedback: boolean; kind: AppealKind; kindOther: string; situation: string; suggestion: string; } // 5 рабочих дней от текущей даты, выходные (сб/вс) не считаются днём срока — // используется как крайний срок обработки обращения (AppealDueDate). function addBusinessDays(from: Date, days: number): Date { const result = new Date(from); let remaining = days; while (remaining > 0) { result.setDate(result.getDate() + 1); const dayOfWeek = result.getDay(); // 0 = воскресенье, 6 = суббота if (dayOfWeek !== 0 && dayOfWeek !== 6) { remaining -= 1; } } return result; } // Имена полей — internal name столбцов списка SharePoint (Company/Name/Position/ // Type/IssueDescription/SuggestionText/WantsFeedback/AppealDueDate), не // отображаемые названия. async function buildFields(data: IFeedbackSubmission): Promise> { const typeLabel = data.kind === 'other' ? data.kindOther : KIND_LABELS[data.kind]; const name = data.isAnonymous ? '' : data.fullName; const fields: Record = { Title: name ? `${typeLabel} — ${name}` : typeLabel, Company: await resolveCompanyName(data.company), Name: name, Position: data.isAnonymous ? '' : data.position, Type: typeLabel, IssueDescription: data.situation, SuggestionText: data.suggestion, WantsFeedback: data.wantsFeedback, AppealDueDate: addBusinessDays(new Date(), 5).toISOString() }; // Состав полей зависит от вида обращения и анонимности, поэтому часть строк // заведомо пустая (у предложения нет описания ситуации, у анонимного — ФИО). // Такие поля не отправляем вовсе: пустая строка в списке выглядит как // заполненное значение, а не как «поля не было». WantsFeedback при этом // остаётся всегда — false здесь осмысленное значение, а не пустота. return Object.fromEntries( Object.entries(fields) .map(([key, value]) => [key, typeof value === 'string' ? value.trim() : value]) .filter(([, value]) => value !== '') ); } export async function submitFeedback(data: IFeedbackSubmission): Promise { const token = await getAppOnlyToken(); const response = await fetch(SHAREPOINT_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify({ siteUrl: config.sharepointSiteUrl, listName: config.sharepointListName, fields: await buildFields(data) }) }); if (!response.ok) { throw new Error( `api.tnsu.kz вернул ошибку при создании элемента списка: ${response.status} ${await response.text()}` ); } }