Compare commits
3 Commits
7341774e50
...
ad76e59ca0
| Author | SHA1 | Date | |
|---|---|---|---|
| ad76e59ca0 | |||
| 053e4d4c3e | |||
| 09b1df87c3 |
@@ -7,6 +7,11 @@
|
|||||||
|
|
||||||
## [Не выпущено]
|
## [Не выпущено]
|
||||||
|
|
||||||
|
### Добавлено
|
||||||
|
|
||||||
|
- СТ ОМТС, Шаги 2 и 3: кнопка «Сохранить черновик» — записывает шаг в SharePoint без проверок
|
||||||
|
и оставляет на шаге, чтобы правки не пропали при закрытии вкладки. (#8)
|
||||||
|
|
||||||
### Изменено
|
### Изменено
|
||||||
|
|
||||||
- СТ ОМТС, Шаг 3: крестик у поставщика не удаляет его, а исключает из сравнения с обязательной
|
- СТ ОМТС, Шаг 3: крестик у поставщика не удаляет его, а исключает из сравнения с обязательной
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
@import '../common/theme';
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.savedAt {
|
||||||
|
font-size: 12px;
|
||||||
|
color: $muted;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Button, Spinner } from '@tnsu/ui-kit';
|
||||||
|
import styles from './SaveDraftButton.module.scss';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onSave: () => void;
|
||||||
|
isSaving: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
// Подсказка, почему кнопка недоступна.
|
||||||
|
disabledReason?: string;
|
||||||
|
// Время последнего сохранения черновика в этой вкладке.
|
||||||
|
savedAt?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TIME_FORMAT: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit' };
|
||||||
|
|
||||||
|
// «Сохранить черновик» на шагах СТ: записать недоделанный шаг в SharePoint без проверок и остаться
|
||||||
|
// на шаге — чтобы правки не пропали при закрытии вкладки и были видны на другом компьютере.
|
||||||
|
export const SaveDraftButton: React.FC<Props> = ({ onSave, isSaving, disabled = false, disabledReason, savedAt }) => (
|
||||||
|
<div className={styles.root}>
|
||||||
|
{savedAt && (
|
||||||
|
<span className={styles.savedAt}>
|
||||||
|
Черновик сохранён в {savedAt.toLocaleTimeString('ru-RU', TIME_FORMAT)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={onSave}
|
||||||
|
disabled={disabled || isSaving}
|
||||||
|
title={disabled ? disabledReason : 'Сохранить в SharePoint и остаться на этом шаге'}
|
||||||
|
>
|
||||||
|
{isSaving ? <Spinner size="small" /> : 'Сохранить черновик'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
+22
-4
@@ -13,6 +13,7 @@ import { SuppliersWithoutKp } from './SuppliersWithoutKp';
|
|||||||
import { UploadedKpList } from './UploadedKpList';
|
import { UploadedKpList } from './UploadedKpList';
|
||||||
import { removeKpFileFromComparison } from './kpRemoval';
|
import { removeKpFileFromComparison } from './kpRemoval';
|
||||||
import { applySupplierCurrency, getSupplierCurrency } from './kpCurrency';
|
import { applySupplierCurrency, getSupplierCurrency } from './kpCurrency';
|
||||||
|
import { SaveDraftButton } from '../SaveDraftButton/SaveDraftButton';
|
||||||
import styles from './Step2CommercialOffers.module.scss';
|
import styles from './Step2CommercialOffers.module.scss';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -50,6 +51,7 @@ export const Step2CommercialOffers: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [saveError, setSaveError] = useState<string | undefined>(undefined);
|
const [saveError, setSaveError] = useState<string | undefined>(undefined);
|
||||||
|
const [draftSavedAt, setDraftSavedAt] = useState<Date | undefined>(undefined);
|
||||||
|
|
||||||
const kp = useKpProcessing(service, formData, onSave);
|
const kp = useKpProcessing(service, formData, onSave);
|
||||||
|
|
||||||
@@ -136,12 +138,18 @@ export const Step2CommercialOffers: React.FC<Props> = ({
|
|||||||
kp.handleFileRemove(localId);
|
kp.handleFileRemove(localId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = async (): Promise<void> => {
|
// 'draft' — записать шаг в SP и остаться: распознанные КП попадают в SP только здесь, и без
|
||||||
|
// черновика пропадали при закрытии вкладки.
|
||||||
|
const handleSave = async (mode: 'next' | 'draft' = 'next'): Promise<void> => {
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
setSaveError(undefined);
|
setSaveError(undefined);
|
||||||
try {
|
try {
|
||||||
const patch = await persistStep2({ service, formData, kpFiles: kp.kpFiles, currencyRates, noOfferSuppliers });
|
const patch = await persistStep2({ service, formData, kpFiles: kp.kpFiles, currencyRates, noOfferSuppliers });
|
||||||
onSave(patch);
|
onSave(patch);
|
||||||
|
if (mode === 'draft') {
|
||||||
|
setDraftSavedAt(new Date());
|
||||||
|
return;
|
||||||
|
}
|
||||||
onStepComplete(2);
|
onStepComplete(2);
|
||||||
onNext();
|
onNext();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -212,9 +220,19 @@ export const Step2CommercialOffers: React.FC<Props> = ({
|
|||||||
<div className={styles.footerRight}>
|
<div className={styles.footerRight}>
|
||||||
{saveError && <Alert variant="err">{saveError}</Alert>}
|
{saveError && <Alert variant="err">{saveError}</Alert>}
|
||||||
{canEdit && !isReadOnly && (
|
{canEdit && !isReadOnly && (
|
||||||
<Button variant="accent" onClick={handleSave} disabled={!canProceed}>
|
<>
|
||||||
{isSaving ? <Spinner size="small" /> : 'Сохранить и продолжить'}
|
<SaveDraftButton
|
||||||
</Button>
|
onSave={() => { handleSave('draft').catch(() => undefined /* ошибку показывает handleSave */); }}
|
||||||
|
isSaving={isSaving}
|
||||||
|
// Курсы пишутся в общий список «Курс Валют» — нулевой курс туда писать нельзя.
|
||||||
|
disabled={!canProceed}
|
||||||
|
disabledReason="Укажите курсы всех валют"
|
||||||
|
savedAt={draftSavedAt}
|
||||||
|
/>
|
||||||
|
<Button variant="accent" onClick={() => { handleSave().catch(() => undefined /* ошибку показывает handleSave */); }} disabled={!canProceed}>
|
||||||
|
{isSaving ? <Spinner size="small" /> : 'Сохранить и продолжить'}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+6
-2
@@ -19,6 +19,7 @@ import { useAdataCheck } from './useAdataCheck';
|
|||||||
import { useRecommendedPick } from './useRecommendedPick';
|
import { useRecommendedPick } from './useRecommendedPick';
|
||||||
import { VendorPickerDialog } from './VendorPickerDialog';
|
import { VendorPickerDialog } from './VendorPickerDialog';
|
||||||
import { ExcludedSuppliersList } from './ExcludedSuppliersList';
|
import { ExcludedSuppliersList } from './ExcludedSuppliersList';
|
||||||
|
import { SaveDraftButton } from '../SaveDraftButton/SaveDraftButton';
|
||||||
|
|
||||||
// Исключённых поставщиков в режиме просмотра (согласующий, отправленная СТ) пока не показываем —
|
// Исключённых поставщиков в режиме просмотра (согласующий, отправленная СТ) пока не показываем —
|
||||||
// решение от 2026-10-27 (docs/adr/0001). Данные грузятся и так: включить — поменять на true.
|
// решение от 2026-10-27 (docs/adr/0001). Данные грузятся и так: включить — поменять на true.
|
||||||
@@ -57,7 +58,7 @@ export const Step3ComparisonTable: React.FC<Props> = ({
|
|||||||
const {
|
const {
|
||||||
isReadOnly, newSupplierName, newSupplierCurrency, setNewSupplierName, setNewSupplierCurrency,
|
isReadOnly, newSupplierName, newSupplierCurrency, setNewSupplierName, setNewSupplierCurrency,
|
||||||
handleAddSupplier, handleAddSupplierFromDatabase, translateUnits, setTranslateUnits, isSaving, saveError, setSaveError,
|
handleAddSupplier, handleAddSupplierFromDatabase, translateUnits, setTranslateUnits, isSaving, saveError, setSaveError,
|
||||||
validateBeforeSave, handleSave,
|
validateBeforeSave, handleSave, draftSavedAt,
|
||||||
extraPositions, suppliersWithOffer, columnDisplayNames, allPositions, computedOffers, selectedSupplierId,
|
extraPositions, suppliersWithOffer, columnDisplayNames, allPositions, computedOffers, selectedSupplierId,
|
||||||
excludedColumns, handleRestoreSupplier,
|
excludedColumns, handleRestoreSupplier,
|
||||||
} = matrix;
|
} = matrix;
|
||||||
@@ -226,6 +227,9 @@ export const Step3ComparisonTable: React.FC<Props> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<div className={styles.footerRight}>
|
<div className={styles.footerRight}>
|
||||||
{saveError && <Alert variant="err">{saveError}</Alert>}
|
{saveError && <Alert variant="err">{saveError}</Alert>}
|
||||||
|
{!isReadOnly && (
|
||||||
|
<SaveDraftButton onSave={() => { handleSave('draft').catch(() => undefined /* ошибку показывает handleSave */); }} isSaving={isSaving} savedAt={draftSavedAt} />
|
||||||
|
)}
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={ai.handleGeneratePdfReport}
|
onClick={ai.handleGeneratePdfReport}
|
||||||
@@ -247,7 +251,7 @@ export const Step3ComparisonTable: React.FC<Props> = ({
|
|||||||
// Сначала проверка (победитель, километраж), потом предложение комментария ИИ.
|
// Сначала проверка (победитель, километраж), потом предложение комментария ИИ.
|
||||||
if (!validateBeforeSave()) return;
|
if (!validateBeforeSave()) return;
|
||||||
if (!ai.aiComment && !ai.isAiLoading) ai.setShowAiPromptDialog(true);
|
if (!ai.aiComment && !ai.isAiLoading) ai.setShowAiPromptDialog(true);
|
||||||
else void handleSave();
|
else handleSave().catch(() => undefined /* ошибку показывает handleSave */);
|
||||||
}}
|
}}
|
||||||
disabled={isSaving || isReadOnly}
|
disabled={isSaving || isReadOnly}
|
||||||
>
|
>
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
import { Supplier } from '../types/domain';
|
||||||
|
import { ExtraSupplier } from './matrixTypes';
|
||||||
|
import { buildSuppliersPatch } from './step3SavePatch';
|
||||||
|
|
||||||
|
const kpColumn: Supplier = { id: '282_320', spId: 282, kpFileSpId: 320, name: 'Каспий Дорс', hasOffer: true };
|
||||||
|
const extra: ExtraSupplier = { id: 'extra_sup_1', name: 'ООО Новый', currency: 'KZT' };
|
||||||
|
|
||||||
|
describe('buildSuppliersPatch', () => {
|
||||||
|
it('статусы поставщиков — в данные формы в обоих режимах', () => {
|
||||||
|
for (const mode of ['next', 'draft'] as const) {
|
||||||
|
const patch = buildSuppliersPatch({
|
||||||
|
mode, suppliers: [kpColumn], extraSuppliers: [], statuses: { '282_320': 'Производитель' }, extraSupplierSpIds: {},
|
||||||
|
});
|
||||||
|
expect(patch.suppliers[0].status).toBe('Производитель');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('«Сохранить и продолжить»: поставщик Шага 3 переезжает в suppliers с SP Id, список Шага 3 очищается', () => {
|
||||||
|
const patch = buildSuppliersPatch({
|
||||||
|
mode: 'next', suppliers: [kpColumn], extraSuppliers: [extra], statuses: {}, extraSupplierSpIds: { extra_sup_1: 501 },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(patch.suppliers.map(s => s.id)).toEqual(['282_320', 'extra_sup_1']);
|
||||||
|
expect(patch.suppliers[1]).toMatchObject({ spId: 501, hasOffer: true });
|
||||||
|
expect(patch.step3ExtraSuppliers).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Остаёмся на шаге: таблица держит поставщика в своём списке Шага 3 — перенос задвоил бы колонку.
|
||||||
|
it('черновик: поставщик Шага 3 остаётся в своём списке, suppliers его не получают', () => {
|
||||||
|
const patch = buildSuppliersPatch({
|
||||||
|
mode: 'draft', suppliers: [kpColumn], extraSuppliers: [extra], statuses: {}, extraSupplierSpIds: { extra_sup_1: 501 },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(patch.suppliers.map(s => s.id)).toEqual(['282_320']);
|
||||||
|
expect(patch).not.toHaveProperty('step3ExtraSuppliers');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('повторное «Сохранить и продолжить» не дублирует уже перенесённого поставщика', () => {
|
||||||
|
const alreadyMoved: Supplier = { id: 'extra_sup_1', spId: 501, name: 'ООО Новый', hasOffer: true };
|
||||||
|
|
||||||
|
const patch = buildSuppliersPatch({
|
||||||
|
mode: 'next', suppliers: [kpColumn, alreadyMoved], extraSuppliers: [extra], statuses: {}, extraSupplierSpIds: { extra_sup_1: 501 },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(patch.suppliers.filter(s => s.id === 'extra_sup_1')).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('исключение поставщика Шага 3 переезжает вместе с ним', () => {
|
||||||
|
const patch = buildSuppliersPatch({
|
||||||
|
mode: 'next', suppliers: [], extraSuppliers: [{ ...extra, exclusion: { reason: 'Дорого' } }], statuses: {}, extraSupplierSpIds: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(patch.suppliers[0].exclusion).toEqual({ reason: 'Дорого' });
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { Supplier } from '../types/domain';
|
||||||
|
import { ExtraSupplier } from './matrixTypes';
|
||||||
|
|
||||||
|
// «Сохранить и продолжить» — переход на Шаг 4; «Сохранить черновик» — запись в SP, остаёмся на Шаге 3.
|
||||||
|
export type Step3SaveMode = 'next' | 'draft';
|
||||||
|
|
||||||
|
export interface SuppliersPatchInput {
|
||||||
|
mode: Step3SaveMode;
|
||||||
|
suppliers: Supplier[];
|
||||||
|
extraSuppliers: ExtraSupplier[];
|
||||||
|
statuses: Record<string, string>;
|
||||||
|
// SP Id поставщиков, добавленных на Шаге 3 (созданы при этом сохранении или раньше).
|
||||||
|
extraSupplierSpIds: Record<string, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SuppliersPatch {
|
||||||
|
suppliers: Supplier[];
|
||||||
|
// Есть только при переходе на Шаг 4: поставщики Шага 3 переезжают в suppliers.
|
||||||
|
step3ExtraSuppliers?: ExtraSupplier[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Поставщики в данных формы после сохранения Шага 3.
|
||||||
|
//
|
||||||
|
// При переходе на Шаг 4 поставщики, добавленные на Шаге 3, переезжают в suppliers (с тем же id —
|
||||||
|
// по нему к ним привязаны цены в сессии), а список Шага 3 очищается: таблица размонтируется и
|
||||||
|
// при возврате соберётся заново.
|
||||||
|
//
|
||||||
|
// Черновик оставляет нас на шаге, и таблица по-прежнему держит их в своём списке Шага 3. Перенос
|
||||||
|
// задвоил бы колонку (она в обоих списках), а без списка Шага 3 у такого поставщика пропала бы
|
||||||
|
// подстановка количества из позиции — суммы обнулились бы. Поэтому черновик их не переносит:
|
||||||
|
// SP Id уже запомнены (rememberCreatedExtras), повторное сохранение дублей в SP не создаст.
|
||||||
|
export function buildSuppliersPatch(input: SuppliersPatchInput): SuppliersPatch {
|
||||||
|
const { mode, suppliers, extraSuppliers, statuses, extraSupplierSpIds } = input;
|
||||||
|
const withStatuses = suppliers.map(s => ({ ...s, status: statuses[s.id] ?? s.status }));
|
||||||
|
if (mode === 'draft') return { suppliers: withStatuses };
|
||||||
|
|
||||||
|
const movedExtras: Supplier[] = extraSuppliers
|
||||||
|
// Уже в suppliers после прежнего сохранения — не дублируем колонку.
|
||||||
|
.filter(es => !suppliers.some(s => s.id === es.id))
|
||||||
|
.map(es => ({
|
||||||
|
id: es.id,
|
||||||
|
spId: extraSupplierSpIds[es.id] ?? es.spId,
|
||||||
|
name: es.name,
|
||||||
|
hasOffer: true,
|
||||||
|
status: statuses[es.id] ?? '',
|
||||||
|
exclusion: es.exclusion,
|
||||||
|
}));
|
||||||
|
return { suppliers: [...withStatuses, ...movedExtras], step3ExtraSuppliers: [] };
|
||||||
|
}
|
||||||
+20
-23
@@ -8,6 +8,7 @@ import { applyCreatedIds, buildOfferKey, computeColumnsOffers, sumPositionTotals
|
|||||||
import { buildExcludedRecordsForSave } from '../utils/excludedColumns';
|
import { buildExcludedRecordsForSave } from '../utils/excludedColumns';
|
||||||
import { parseNumberInput } from '../utils/parseNumberInput';
|
import { parseNumberInput } from '../utils/parseNumberInput';
|
||||||
import { getStep3SaveError } from './step3Validation';
|
import { getStep3SaveError } from './step3Validation';
|
||||||
|
import { Step3SaveMode, buildSuppliersPatch } from './step3SavePatch';
|
||||||
import { IVendorRecord } from '../../services/VendorDatabaseService';
|
import { IVendorRecord } from '../../services/VendorDatabaseService';
|
||||||
import { buildColumnDisplayNames, buildColumnLabels, findColumnForRecord } from '../utils/supplierColumns';
|
import { buildColumnDisplayNames, buildColumnLabels, findColumnForRecord } from '../utils/supplierColumns';
|
||||||
import { getNoOfferSuppliers } from '../utils/noOfferSuppliers';
|
import { getNoOfferSuppliers } from '../utils/noOfferSuppliers';
|
||||||
@@ -106,6 +107,8 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
|
|||||||
}, [onSave]);
|
}, [onSave]);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [saveError, setSaveError] = useState<string | undefined>(undefined);
|
const [saveError, setSaveError] = useState<string | undefined>(undefined);
|
||||||
|
// Когда последний раз сохранили черновик — показываем рядом с кнопкой.
|
||||||
|
const [draftSavedAt, setDraftSavedAt] = useState<Date | undefined>(undefined);
|
||||||
// Тумблер «Перевести в ед. изм. заявки»: переводит кол-во и цену поставщика в ед. заявки.
|
// Тумблер «Перевести в ед. изм. заявки»: переводит кол-во и цену поставщика в ед. заявки.
|
||||||
// При включении блоки поставщиков показываются как вычисленные значения (без редактирования).
|
// При включении блоки поставщиков показываются как вычисленные значения (без редактирования).
|
||||||
const [translateUnits, setTranslateUnits] = useState(false);
|
const [translateUnits, setTranslateUnits] = useState(false);
|
||||||
@@ -726,9 +729,11 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
|
|||||||
return error === undefined;
|
return error === undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = async (): Promise<void> => {
|
// Запись Шага 3 в SP. 'next' — с проверками и переходом на Шаг 4; 'draft' — без проверок,
|
||||||
|
// остаёмся на шаге (черновик: сохранить недоделанное, чтобы не потерять при закрытии вкладки).
|
||||||
|
const handleSave = async (mode: Step3SaveMode = 'next'): Promise<void> => {
|
||||||
if (!ct) return;
|
if (!ct) return;
|
||||||
if (!validateBeforeSave()) return;
|
if (mode === 'next' && !validateBeforeSave()) return;
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
setSaveError(undefined);
|
setSaveError(undefined);
|
||||||
|
|
||||||
@@ -880,28 +885,15 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
|
|||||||
|
|
||||||
onSave({
|
onSave({
|
||||||
offers: offersForSession,
|
offers: offersForSession,
|
||||||
suppliers: [
|
// Поставщики Шага 3 переезжают в suppliers только при переходе на Шаг 4 (см. buildSuppliersPatch).
|
||||||
...suppliers.map(s => ({
|
...buildSuppliersPatch({
|
||||||
...s,
|
mode,
|
||||||
status: supplierStatuses[s.id] ?? s.status,
|
suppliers,
|
||||||
})),
|
extraSuppliers,
|
||||||
// Move extras into suppliers (with React ID kept as `id` — required so formData.offers
|
statuses: supplierStatuses,
|
||||||
// entries with React IDs still match sup.id on remount). Skip extras that are already in
|
extraSupplierSpIds: extraSupSpIds,
|
||||||
// formData.suppliers from a prior save (avoids duplicate columns).
|
}),
|
||||||
...extraSuppliers
|
|
||||||
.filter(es => !suppliers.some(s => s.id === es.id))
|
|
||||||
.map(es => ({
|
|
||||||
id: es.id,
|
|
||||||
spId: extraSupSpIds[es.id],
|
|
||||||
name: es.name,
|
|
||||||
hasOffer: true as const,
|
|
||||||
status: supplierStatuses[es.id] ?? '',
|
|
||||||
exclusion: es.exclusion,
|
|
||||||
})),
|
|
||||||
],
|
|
||||||
recommendedSupplierId: selectedSupplierId,
|
recommendedSupplierId: selectedSupplierId,
|
||||||
// Extras moved into `suppliers` — clear extras state so `suppliersWithOffer` doesn't duplicate columns.
|
|
||||||
step3ExtraSuppliers: [],
|
|
||||||
// Positions don't migrate into selectedPositions — keep them in extras state so the
|
// Positions don't migrate into selectedPositions — keep them in extras state so the
|
||||||
// "доп.расходы" rows continue to render after Step 4 → breadcrumb back. Track spId to
|
// "доп.расходы" rows continue to render after Step 4 → breadcrumb back. Track spId to
|
||||||
// skip re-creating SP rows on subsequent saves.
|
// skip re-creating SP rows on subsequent saves.
|
||||||
@@ -918,6 +910,10 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
|
|||||||
step3CriteriaEdits: criteriaMap,
|
step3CriteriaEdits: criteriaMap,
|
||||||
step3CustomCriteria: customCriteria,
|
step3CustomCriteria: customCriteria,
|
||||||
});
|
});
|
||||||
|
if (mode === 'draft') {
|
||||||
|
setDraftSavedAt(new Date());
|
||||||
|
return;
|
||||||
|
}
|
||||||
saveCompletedRef.current = true;
|
saveCompletedRef.current = true;
|
||||||
onStepComplete(3);
|
onStepComplete(3);
|
||||||
onNext();
|
onNext();
|
||||||
@@ -997,6 +993,7 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
|
|||||||
handleDeleteExtraPosition,
|
handleDeleteExtraPosition,
|
||||||
validateBeforeSave,
|
validateBeforeSave,
|
||||||
handleSave,
|
handleSave,
|
||||||
|
draftSavedAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user