@@ -35,6 +35,7 @@ import {
IAttachmentInfo
} from '../../shared/attachments/attachments.service' ;
import { effectiveUserEmail } from '../../shared/impersonation' ;
import { getManagerEmail , getEmployeeOrganizations } from '../../shared/dictionary' ;
import { friendlyErrorMessage } from '../../shared/diagnostics' ;
import { IRequestFormProps } from '../../models/types' ;
import {
@@ -44,33 +45,13 @@ import {
dataListTitleForListId
} from '../../shared/listTitles' ;
import { FORM_ID , IFormData , EMPTY_DATA , SetField } from './model' ;
import { saveTicketRequest , setPlannerTaskId , ticket1cApprovalService } from './submit' ;
import { saveTicketRequest , ticket1cApprovalService } from './submit' ;
import { loadTicketRequest } from './load' ;
import { PlannerTicketService } from './services/plannerTicket.service' ;
import { getApplicantProfile } from './services/profile.service' ;
import { PLANNER_PLAN_ID , PLANNER_BUCKET_NAME } from './config' ;
import { RequestSection } from './sections/RequestSection' ;
import { ContactSection } from './sections/ContactSection' ;
import { useStableCallback } from '../../shared/useStableCallback' ;
/** Текст карточки Planner из данных заявки (соответствует старому buildPlannerNotes). */
function buildPlannerNotes ( data : IFormData , code : string ) : string {
const lines : string [ ] = [ ] ;
if ( code ) lines . push ( ` Код заявки: ${ code } ` ) ;
lines . push ( ` Организация: ${ data . Organization } ` ) ;
lines . push ( ` ФИО: ${ data . FIO } ` ) ;
lines . push ( ` Email: ${ data . Email } ` ) ;
lines . push ( ` Телефон: ${ data . Phone } ` ) ;
lines . push ( ` Отдел: ${ data . Department } ` ) ;
lines . push ( ` Должность: ${ data . Position } ` ) ;
lines . push ( ` База 1С : ${ data . DB1C } ` ) ;
lines . push ( ` Виды работ: ${ data . WorkType } ` ) ;
lines . push ( '' ) ;
lines . push ( 'Вопрос:' ) ;
lines . push ( data . Question ) ;
return lines . join ( '\n' ) ;
}
const Ticket1CForm : React.FC < IRequestFormProps > = ( {
requestType ,
context ,
@@ -145,6 +126,23 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
} ) ) ;
} )
. catch ( ( ) = > undefined ) ;
// Независимо от профиля Graph — непосредственный руководитель (тот же
// источник, что у Dismissal/Vacation, см. shared/dictionary/employeeLookup).
// Ф.И.О. руководителя своего поля не возвращает — доп. запрос в тот же
// Employee/ByEmail (см. getEmployeeOrganizations), которым форма уже
// резолвит ФИО/организацию заявителя.
getManagerEmail ( context , context . pageContext . user . email , 'chief' )
. then ( ( email ) = > {
if ( ! alive || ! email ) return ;
setData ( ( prev ) = > ( { . . . prev , Manager : prev.Manager || email } ) ) ;
getEmployeeOrganizations ( context , email )
. then ( ( records ) = > {
const fio = records [ 0 ] ? . fio ;
if ( alive && fio ) setData ( ( prev ) = > ( { . . . prev , Manager : ` ${ fio } · ${ email } ` } ) ) ;
} )
. catch ( ( ) = > undefined ) ;
} )
. catch ( ( ) = > undefined ) ;
return ( ) = > {
alive = false ;
} ;
@@ -229,14 +227,12 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
if ( ! rtId ) {
throw new Error ( 'Тип заявки не определён — обновите страницу и повторите.' ) ;
}
// Пустой маршрут — согласование не требуется по правилам этого вида работ.
const hasApprovers = matrix . rows . length > 0 ;
const finalStatus =
intendedStatus === REQUEST_STATUS . draft
? REQUEST_STATUS.draft
: hasApprovers
? REQUEST_STATUS.onApproval
: REQUEST_STATUS.approved ;
// Маршрут запрашивается для ЛЮБОГО вида работ и используется как есть —
// без авто-approve, если админ ещё не настроил шаги для конкретного вида
// работ (раньше в этом случае заявка сразу становилась «Согласовано» в
// обход согласования; теперь это просто «На согласовании» с пустой
// матрицей — сигнал администратору маршрутов донастроить условие).
const finalStatus = intendedStatus === REQUEST_STATUS . draft ? REQUEST_STATUS.draft : REQUEST_STATUS.onApproval ;
// requestId уже задан → редактируем существующую заявку, пишем в ту же
// партицию (editListId). Иначе — создаём новую, и партицию для записи
@@ -260,18 +256,19 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
setRequestCode ( code ) ;
}
let attachments = existingAttachments ;
if ( files . length > 0 ) {
await uploadAttachments ( writeListId || dataListTitle ( FORM_ID ) , id , files ) ;
setFiles ( [ ] ) ;
attachments = await getAttachments ( writeListId || dataListTitle ( FORM_ID ) , id ) ;
setExistingAttachments ( attachments ) ;
setExistingAttachments ( await getAttachments ( writeListId || dataListTitle ( FORM_ID ) , id ) ) ;
}
if ( onSaved ) {
onSaved ( ) ;
}
// Передача в Planner теперь происходит только после реального решения
// согласующего (см. Ticket1CView.handoffToPlanner) — здесь заявка всегда
// отправляется на согласование, минуя Planner напрямую.
if ( finalStatus === REQUEST_STATUS . onApproval ) {
// Имена списков — той же партиции, куда фактически записали (writeListId),
// не обязательно текущей на момент рендера формы: поток ищет строку
@@ -281,24 +278,13 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
dataListName : writeListId ? dataListTitleForListId ( FORM_ID , writeListId ) : dataListTitle ( FORM_ID ) ,
approvalListName : writeListId ? approvalListTitleForListId ( FORM_ID , writeListId ) : approvalListTitle ( FORM_ID )
} ) ;
} else if ( finalStatus === REQUEST_STATUS . approved ) {
// Согласование не требуется для этого вида работ — сразу передаём в отдел 1С.
const taskId = await new PlannerTicketService ( context ) . createTask ( {
planId : PLANNER_PLAN_ID ,
bucketName : PLANNER_BUCKET_NAME ,
title : data.TaskName ,
notes : buildPlannerNotes ( data , code || String ( id ) ) ,
attachments ,
webAbsoluteUrl : context.pageContext.web.absoluteUrl
} ) ;
await setPlannerTaskId ( id , taskId , writeListId ) ;
}
} ;
// На отправку маршрут должен быть либо пустым (согласование не нужно), либо
// полностью укомплектован. Ошибку резолва маршрута (напр. тип заявки ещё не
// настроен в админке маршрутов) не путаем с «согласование не требуется» —
// иначе заявка молча уйдёт в Planner в обход согласования .
// На отправку матрица не обязана быть непустой (админ мог ещё не настроить
// шаги для этого вида работ — заявка всё равно уходит «На согласовании», см.
// save() выше), но если шаг ЕСТЬ, он должен быть назначен на конкретного
// человека — иначе решать по нему будет некому .
const validateSubmit = ( ) : string | undefined = > {
const formError = validate ( ) ;
if ( formError ) return formError ;
@@ -374,7 +360,7 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
< ApprovalMatrix
editor = { matrix }
search = { ( q ) = > approval . searchUsers ( q ) }
emptyText = "Согласование не требуется для этого вида работ ."
emptyText = "Маршрут для этого вида работ пока не настроен — обратитесь к администратору маршрутов согласования ."
/ >
< SubmitActions