fix: дата подачи — день отправки, а не сохранения черновика

В подборе и реферале черновик хранил дату первого сохранения, и через
несколько дней заявка уходила на согласование со старой датой подачи.
Теперь при отправке дата подачи всегда сегодняшняя, а открытый черновик
сразу показывает сегодня (как уже было в объяснительной по СКУД). В реферале
дату больше нельзя поправить руками. Общая логика — shared/submitDate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-10-02 09:34:51 +05:00
parent 5702939b52
commit c91d2dbc04
9 changed files with 66 additions and 10 deletions
@@ -64,6 +64,7 @@ import { VacancySection } from './sections/VacancySection';
import { HealthTripsSection } from './sections/HealthTripsSection';
import { ConditionsSection } from './sections/ConditionsSection';
import { useStableCallback } from '../../shared/useStableCallback';
import { submitDateOnOpen } from '../../shared/submitDate';
const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({
requestType,
@@ -105,7 +106,7 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({
Promise.all([loadSelectionRequest(editId, editListId), getAttachments(editListId || dataListTitle(FORM_ID), editId)])
.then(([loaded, atts]) => {
if (alive) {
setData(loaded.data);
setData({ ...loaded.data, SubmitDate: submitDateOnOpen(loaded.status, loaded.data.SubmitDate) });
setApproverChanges(loaded.approverChanges);
setRequestStatus(loaded.status);
setRequestCode(loaded.code || undefined);
@@ -3,6 +3,7 @@ import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute }
import { ISaveItemResult, resolveDataList, saveRequestItem } from '../../shared/dataList';
import { IFieldChange, serializeFieldChanges } from '../../shared/fieldChanges/fieldChanges';
import { buildRequestTitle } from '../../shared/requestTitle';
import { submitDateOnSave } from '../../shared/submitDate';
import { FORM_ID, IFormData } from './model';
import { APPROVER_CHANGES_FIELD } from './changeTracking';
import { cleanVacancyReasons, legacyDetailsText } from './vacancyReasons';
@@ -146,6 +147,7 @@ export async function saveSelectionRequest(
): Promise<ISaveResult> {
const item = buildScalarBody(data);
item.RequestStatus = status;
item.SubmitDate = submitDateOnSave(status, data.SubmitDate) || null;
await applyPeople(item, data);
const result = await saveRequestItem(FORM_ID, existingId, listId, requestTypeId, item);
@@ -59,6 +59,7 @@ import { VacancySection } from './sections/VacancySection';
import { CandidateInfoSection } from './sections/CandidateInfoSection';
import { AssessmentSection } from './sections/AssessmentSection';
import { useStableCallback } from '../../shared/useStableCallback';
import { submitDateOnOpen } from '../../shared/submitDate';
const ReferralForm: React.FC<IRequestFormProps> = ({
requestType,
@@ -111,7 +112,7 @@ const ReferralForm: React.FC<IRequestFormProps> = ({
Promise.all([loadReferralRequest(editId, editListId), getAttachments(editListId || dataListTitle(FORM_ID), editId)])
.then(([loaded, atts]) => {
if (alive) {
setData(loaded.data);
setData({ ...loaded.data, SubmitDate: submitDateOnOpen(loaded.status, loaded.data.SubmitDate) });
setRequestStatus(loaded.status);
setRequestCode(loaded.code || undefined);
if (loaded.code) notifyCode(loaded.code);
@@ -141,7 +142,7 @@ const ReferralForm: React.FC<IRequestFormProps> = ({
setData((prev) => ({
...prev,
Referrer: prev.Referrer || user.email,
SubmitDate: prev.SubmitDate || todayIso()
SubmitDate: todayIso()
}));
approval
.getProfileFields(user.loginName)
@@ -5,8 +5,9 @@ import { ISectionProps, REFERRER_PLACEHOLDER } from '../model';
/** Секция 1: кто рекомендует — дата подачи, сам рекомендатель и его контакты. */
export const ReferrerSection: React.FC<ISectionProps> = ({ data, set, context, onReferrerChange, printOrgIndex }) => (
<CollapseSection title="1. Рекомендатель" icon="people">
{/* Дата подачи — всегда сегодняшняя (ставит форма, при отправке — заново), руками не меняется. */}
<FormField label="1.1 Дата подачи" htmlFor="SubmitDate" required>
<TextInput id="SubmitDate" type="date" value={data.SubmitDate} onChange={(v) => set('SubmitDate', v)} />
<TextInput id="SubmitDate" type="date" value={data.SubmitDate} onChange={(v) => set('SubmitDate', v)} readOnly />
</FormField>
<FormField label="1.2 Ф.И.О. рекомендателя" htmlFor="Referrer" required full>
<PeopleField
@@ -1,6 +1,7 @@
import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval';
import { ISaveItemResult, saveRequestItem } from '../../shared/dataList';
import { ensureEmployee } from '../../shared/employee';
import { submitDateOnSave } from '../../shared/submitDate';
import { FORM_ID, IFormData, OTHER_VALUE } from './model';
/**
@@ -28,7 +29,7 @@ function buildItem(data: IFormData, status: string, referrerId: number): Record<
return {
Title: buildTitle(data.CandidateName, data.VacancyTitle),
RequestStatus: status,
SubmitDate: data.SubmitDate || null,
SubmitDate: submitDateOnSave(status, data.SubmitDate) || null,
ReferrerId: referrerId,
ReferrerPosition: data.ReferrerPosition || null,
ReferrerDepartment: data.ReferrerDepartment || null,
@@ -35,6 +35,7 @@ import {
dataListTitleForListId
} from '../../shared/listTitles';
import { useStableCallback } from '../../shared/useStableCallback';
import { submitDateOnOpen } from '../../shared/submitDate';
import { FORM_ID, FORM_TITLE, IFormData, EMPTY_DATA, SetField } from './model';
import { validateSkudExplanation } from './validation';
import { saveSkudExplanationRequest, skudExplanationApprovalService } from './submit';
@@ -83,10 +84,7 @@ const SkudExplanationForm: React.FC<IRequestFormProps> = ({
loadSkudExplanationRequest(editId, editListId)
.then((loaded) => {
if (!alive) return;
// Черновик/доработку подают заново — дата подачи становится сегодняшней;
// у отправленной заявки остаётся та, с которой её подали.
const submitDate = isEditableStatus(loaded.status) ? todayDateKey() : loaded.data.SubmitDate;
setData({ ...loaded.data, SubmitDate: submitDate });
setData({ ...loaded.data, SubmitDate: submitDateOnOpen(loaded.status, loaded.data.SubmitDate) });
setRequestStatus(loaded.status);
setRequestCode(loaded.code || undefined);
if (loaded.code) notifyCode(loaded.code);
@@ -1,6 +1,7 @@
import { ApprovalListService, IApproverToWrite, approvalServiceFor, writeRoute } from '../../shared/approval';
import { ISaveItemResult, saveRequestItem } from '../../shared/dataList';
import { ensureEmployee } from '../../shared/employee';
import { submitDateOnSave } from '../../shared/submitDate';
import { FORM_ID, IFormData, OTHER_REASON } from './model';
import { formatMissedDaysSummary, formatShortDate } from './noteText';
import { todayDateKey } from './skud/skudDays';
@@ -19,7 +20,7 @@ function buildItem(data: IFormData, status: string, employeeId: number, employee
return {
Title: buildTitle(employeeName),
RequestStatus: status,
SubmitDate: data.SubmitDate || null,
SubmitDate: submitDateOnSave(status, data.SubmitDate) || null,
EmployeeId: employeeId,
SkudEmployeeFio: data.EmployeeFio || null,
Position: data.Position || null,
@@ -0,0 +1,21 @@
import { REQUEST_STATUS } from './approval/models/statuses';
import { submitDateOnOpen, submitDateOnSave, todaySubmitDate } from './submitDate';
const OLD_DATE = '2026-09-01';
describe('submitDate', () => {
it('отправка на согласование — сегодня, даже если черновик сохранён раньше', () => {
expect(submitDateOnSave(REQUEST_STATUS.onApproval, OLD_DATE)).toBe(todaySubmitDate());
expect(submitDateOnSave(REQUEST_STATUS.queued, OLD_DATE)).toBe(todaySubmitDate());
});
it('черновик хранит дату с экрана', () => {
expect(submitDateOnSave(REQUEST_STATUS.draft, OLD_DATE)).toBe(OLD_DATE);
});
it('открыли черновик или доработку — сегодня, отправленную — как подали', () => {
expect(submitDateOnOpen(REQUEST_STATUS.draft, OLD_DATE)).toBe(todaySubmitDate());
expect(submitDateOnOpen(REQUEST_STATUS.returned, OLD_DATE)).toBe(todaySubmitDate());
expect(submitDateOnOpen(REQUEST_STATUS.onApproval, OLD_DATE)).toBe(OLD_DATE);
});
});
@@ -0,0 +1,30 @@
import { REQUEST_STATUS, isEditableStatus } from './approval/models/statuses';
function pad2(n: number): string {
return n < 10 ? `0${n}` : `${n}`;
}
/** Сегодняшняя дата по местному времени, YYYY-MM-DD (формат input type=date). */
export function todaySubmitDate(): string {
const now = new Date();
return `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())}`;
}
/**
* «Дата подачи» для формы, открытой на редактирование. Черновик и доработку
* подают заново — на экране сегодняшняя дата, а не дата первого сохранения;
* у уже отправленной заявки остаётся та, с которой её подали.
*/
export function submitDateOnOpen(status: string, savedDate: string): string {
return isEditableStatus(status) ? todaySubmitDate() : savedDate;
}
/**
* «Дата подачи» для записи в SharePoint из формы подачи. Любое сохранение кроме
* черновика — это отправка («На согласовании» или «Ждёт очереди», см.
* initialSubmitStatus): дата всегда сегодняшняя — форму могли открыть вчера, а
* черновик сохранить неделю назад. Черновик хранит то, что на экране.
*/
export function submitDateOnSave(status: string, currentDate: string): string {
return status === REQUEST_STATUS.draft ? currentDate : todaySubmitDate();
}