diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx
index fcae0cf..0eb1c98 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/ExtraPositionRow.tsx
@@ -25,7 +25,7 @@ export function ExtraPositionRow({ pos, posIndex, regularPositionsCount, matrix,
isReadOnly, edits, extraPositions, translateUnits, rawValues, deliveryKm, selectedSupplierId, nameColWidth,
suppliersWithOffer, supplierCurrencies, computedOffers, minTotalByPosition,
updateSupplierProductName, updateUnit, updateEdit, updatePriceKzt, handleDeliveryKmChange,
- handleDeleteExtraPosition,
+ handleDeleteExtraPosition, handleExtraPositionUnitChange,
} = matrix;
const ep = extraPositions.find(e => e.id === pos.id);
@@ -47,7 +47,22 @@ export function ExtraPositionRow({ pos, posIndex, regularPositionsCount, matrix,
/>
)}
-
{pos.unit} |
+ {/* Единица строки — своя (у логистики по умолчанию «рейс»: бывает «тонна», «машина», «услуга»). */}
+
+ {!isReadOnly ? (
+
+ handleExtraPositionUnitChange(pos.id, val)}
+ placeholder="ед. изм."
+ borderless
+ ariaLabel={`Ед. изм. строки ${pos.materialName}`}
+ />
+
+ ) : (
+ pos.unit
+ )}
+ |
— |
{suppliersWithOffer.map(s => {
const key = key0(s);
@@ -120,9 +135,9 @@ export function ExtraPositionRow({ pos, posIndex, regularPositionsCount, matrix,
updateEdit(s.id, pos.id, 0, 'quantity', val)}
- placeholder="рейсы"
+ placeholder={pos.unit || 'кол-во'}
borderless
- ariaLabel={`Кол-во рейсов ${s.name} ${pos.materialName}`}
+ ariaLabel={`Кол-во (${pos.unit || 'ед.'}) ${s.name} ${pos.materialName}`}
/>
) : (
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.test.ts b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.test.ts
index b220481..d3d6fdc 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.test.ts
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.test.ts
@@ -1,7 +1,34 @@
-import { applyCreatedIds, buildOfferKey, computeOffer, getSubCols, isMinPositionTotal, round2Str, sumPositionTotals } from './matrixHelpers';
-import { ExtraPosition } from './matrixTypes';
+import { applyCreatedIds, applyPositionUnitChange, buildOfferKey, computeOffer, getSubCols, isMinPositionTotal, round2Str, sumPositionTotals } from './matrixHelpers';
+import { EditableOffers, ExtraPosition } from './matrixTypes';
import { SupplierOffer } from '../types/domain';
+describe('applyPositionUnitChange (единица доп. позиции стала своей вместо «рейс»)', () => {
+ const edits: EditableOffers = {
+ 'sup_a::extra_pos_1': [{ supplierId: 'sup_a', positionId: 'extra_pos_1', itemIndex: 0, unit: 'рейс', pricePerUnit: 50000 }],
+ 'sup_b::extra_pos_1': [{ supplierId: 'sup_b', positionId: 'extra_pos_1', itemIndex: 0, unit: 'машина' }],
+ 'sup_c::extra_pos_1': [{ supplierId: 'sup_c', positionId: 'extra_pos_1', itemIndex: 0 }],
+ 'sup_a::pos_1': [{ supplierId: 'sup_a', positionId: 'pos_1', itemIndex: 0, unit: 'рейс' }],
+ };
+
+ const result = applyPositionUnitChange(edits, 'extra_pos_1', 'рейс', 'тонна');
+
+ it('у поставщика с прежней единицей позиции — новая, цена не теряется', () => {
+ expect(result['sup_a::extra_pos_1'][0]).toMatchObject({ unit: 'тонна', pricePerUnit: 50000 });
+ });
+
+ it('у поставщика без своей единицы — новая', () => {
+ expect(result['sup_c::extra_pos_1'][0].unit).toBe('тонна');
+ });
+
+ it('свою единицу поставщика не трогает', () => {
+ expect(result['sup_b::extra_pos_1'][0].unit).toBe('машина');
+ });
+
+ it('другие позиции не трогает, даже с той же единицей', () => {
+ expect(result['sup_a::pos_1'][0].unit).toBe('рейс');
+ });
+});
+
describe('computeOffer', () => {
it('считает сумму в тенге для КП в тенге', () => {
const offer = computeOffer(
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.ts b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.ts
index 3b0a650..008ee67 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.ts
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.ts
@@ -87,6 +87,25 @@ export function buildOfferKey(supplierId: string, positionId: string): string {
return `${supplierId}::${positionId}`;
}
+// Поменяли единицу доп. позиции (логистика, доп. расходы): у поставщиков, у которых единица та же,
+// что была у позиции (её не трогали), — новая. Свою единицу поставщика не меняем. Ячейки без правок
+// и так показывают единицу позиции.
+export function applyPositionUnitChange(
+ edits: EditableOffers,
+ positionId: string,
+ oldUnit: string,
+ newUnit: string,
+): EditableOffers {
+ const next: EditableOffers = {};
+ Object.keys(edits).forEach(key => {
+ const isThisPosition = key.split('::')[1] === positionId;
+ next[key] = isThisPosition
+ ? edits[key].map(item => (!item.unit || item.unit === oldUnit ? { ...item, unit: newUnit } : item))
+ : edits[key];
+ });
+ return next;
+}
+
export interface ColumnOffersInput {
columns: Array<{ id: string; isExtraSupplier: boolean }>;
positions: PositionRow[];
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/useStep3Matrix.ts b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/useStep3Matrix.ts
index c4cdbd3..9d0435f 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/useStep3Matrix.ts
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/useStep3Matrix.ts
@@ -4,7 +4,7 @@ import { SupplierOffer, CurrencyRate, FormStep, SupplierExclusion } from '../typ
import { CtService } from '../../services/CtService';
import { CTFormData } from '../hooks/useCtForm';
import { ExtraSupplier, ExtraPosition, PositionRow, CellEdit, EditableOffers, CriteriaEntry } from './matrixTypes';
-import { applyCreatedIds, buildOfferKey, computeColumnsOffers, sumPositionTotals } from './matrixHelpers';
+import { applyCreatedIds, applyPositionUnitChange, buildOfferKey, computeColumnsOffers, sumPositionTotals } from './matrixHelpers';
import { buildExcludedRecordsForSave } from '../utils/excludedColumns';
import { parseNumberInput } from '../utils/parseNumberInput';
import { getStep3SaveError } from './step3Validation';
@@ -681,6 +681,17 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
onSave({ step3DeliveryKm: updated });
};
+ // Своя единица доп. позиции вместо заданной при добавлении (у логистики — «рейс»). Поставщики, у
+ // которых единицу не меняли, получают новую. В SP уходит при «Сохранить» (см. handleSave).
+ const handleExtraPositionUnitChange = (positionId: string, unit: string): void => {
+ if (isReadOnly) return;
+ const oldUnit = extraPositions.find(ep => ep.id === positionId)?.unit ?? '';
+ const updated = extraPositions.map(ep => (ep.id === positionId ? { ...ep, unit } : ep));
+ setExtraPositions(updated);
+ setEdits(prev => applyPositionUnitChange(prev, positionId, oldUnit, unit));
+ onSave({ step3ExtraPositions: updated });
+ };
+
const handleDeleteExtraPosition = (positionId: string): void => {
if (isReadOnly) return;
const updated = extraPositions.filter(ep => ep.id !== positionId);
@@ -760,6 +771,9 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
if (ep.spId) {
// Already created in SP on a previous save — reuse to avoid duplicate Позиции СТ rows.
extraPosSpIds[ep.id] = ep.spId;
+ // Единицу могли поменять после создания. Пишем всегда — строк доп. расходов единицы,
+ // а отслеживать «менялась ли» — лишнее состояние.
+ await service.updateCTPositionUnit(ep.spId, ep.unit);
continue;
}
const saved = await service.createCTPositions(ct.id, [{
@@ -992,6 +1006,7 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
handleAddDelivery,
handleDeliveryKmChange,
handleDeleteExtraPosition,
+ handleExtraPositionUnitChange,
validateBeforeSave,
handleSave,
draftSavedAt,
diff --git a/src/webparts/ctFormsPortal/forms/ct/services/CtService.ts b/src/webparts/ctFormsPortal/forms/ct/services/CtService.ts
index 68e900a..0497124 100644
--- a/src/webparts/ctFormsPortal/forms/ct/services/CtService.ts
+++ b/src/webparts/ctFormsPortal/forms/ct/services/CtService.ts
@@ -397,6 +397,21 @@ export class CtService {
}));
}
+ // Единица доп. позиции Шага 3 (логистика, доп. расходы) — её можно поменять после создания.
+ // MERGE на «Позиции СТ» ресивер не блокирует (блокирует только на «Оценка поставщиков»).
+ async updateCTPositionUnit(positionSpId: number, unit: string): Promise {
+ const res = await this.http.fetch(this._liById(LISTS.CT_POSITIONS, positionSpId), SPHttpClient.configurations.v1, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json;odata=nometadata",
+ "IF-MATCH": "*",
+ "X-HTTP-Method": "MERGE",
+ },
+ body: JSON.stringify({ unit }),
+ });
+ if (!res.ok) throw new Error(`Ошибка обновления единицы позиции: ${res.status}`);
+ }
+
// Пакетное создание элементов списка через SharePoint OData $batch: вместо N POST —
// один HTTP-запрос (до 100 операций/changeset, при большем числе бьём на чанки).
// Возвращает Id созданных элементов В ТОМ ЖЕ порядке, что и bodies. Changeset атомарен: