|
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 2ab285d..b220481 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.test.ts
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.test.ts
@@ -1,5 +1,6 @@
-import { applyCreatedIds, buildOfferKey, computeOffer, getSubCols, round2Str } from './matrixHelpers';
+import { applyCreatedIds, buildOfferKey, computeOffer, getSubCols, isMinPositionTotal, round2Str, sumPositionTotals } from './matrixHelpers';
import { ExtraPosition } from './matrixTypes';
+import { SupplierOffer } from '../types/domain';
describe('computeOffer', () => {
it('считает сумму в тенге для КП в тенге', () => {
@@ -100,3 +101,34 @@ describe('applyCreatedIds', () => {
expect(extras[0].spId).toBeUndefined();
});
});
+
+describe('подсветка минимальной суммы по позиции (п.13)', () => {
+ const offer = (supplierId: string, positionId: string, total: number): SupplierOffer => ({
+ id: '', supplierId, positionId, pricePerUnit: 0, quantity: 0, currencyCode: 'KZT',
+ pricePerUnitKzt: 0, totalPriceKzt: total, totalWithExtraKzt: total,
+ });
+
+ // Строки комплекта одной позиции у поставщика складываются в одну сумму.
+ it('sumPositionTotals: сумма по поставщику и позиции, строки комплекта складываются', () => {
+ const totals = sumPositionTotals([
+ offer('s1', 'p1', 100), offer('s1', 'p1', 50), offer('s2', 'p1', 120), offer('s1', 'p2', 10),
+ ]);
+
+ expect(totals.get(buildOfferKey('s1', 'p1'))).toBe(150);
+ expect(totals.get(buildOfferKey('s2', 'p1'))).toBe(120);
+ expect(totals.get(buildOfferKey('s1', 'p2'))).toBe(10);
+ });
+
+ // Раньше сравнивали строгим ===, и итог комплекта, посчитанный отдельно, расходился на копейки — подсветка пропадала.
+ it('isMinPositionTotal: совпадение с минимумом с допуском до полкопейки', () => {
+ expect(isMinPositionTotal(114000, 114000)).toBe(true);
+ expect(isMinPositionTotal(103823.48 + 0.000001, 103823.48)).toBe(true);
+ expect(isMinPositionTotal(128800, 114000)).toBe(false);
+ });
+
+ it('isMinPositionTotal: нет цены или нет минимума — не подсвечиваем', () => {
+ expect(isMinPositionTotal(0, 0)).toBe(false);
+ expect(isMinPositionTotal(undefined, 100)).toBe(false);
+ expect(isMinPositionTotal(100, undefined)).toBe(false);
+ });
+});
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.ts b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.ts
index e6acad9..210cdfd 100644
--- a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.ts
+++ b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/matrixHelpers.ts
@@ -96,3 +96,23 @@ export function applyCreatedIds(
): T[] {
return items.map(item => (createdById[item.id] ? { ...item, ...createdById[item.id] } : item));
}
+
+// Допуск сравнения сумм с минимумом — полкопейки: суммы с плавающей точкой расходятся в хвосте.
+const MIN_TOTAL_EPSILON = 0.005;
+
+// Сумма в ₸ по каждой паре (поставщик, позиция): строки комплекта складываются. Один источник и для
+// минимума, и для подсветки ячеек — иначе отдельно посчитанный итог комплекта расходился на копейки.
+export function sumPositionTotals(offers: SupplierOffer[]): Map {
+ const totals = new Map();
+ offers.forEach(o => {
+ const key = buildOfferKey(o.supplierId, o.positionId);
+ totals.set(key, (totals.get(key) ?? 0) + (o.totalWithExtraKzt ?? 0));
+ });
+ return totals;
+}
+
+// Сумма поставщика по позиции — минимальная среди поставщиков (подсветка зелёным по каждой позиции).
+export function isMinPositionTotal(total: number | undefined, minTotal: number | undefined): boolean {
+ if (total === undefined || minTotal === undefined || total <= 0) return false;
+ return Math.abs(total - minTotal) < MIN_TOTAL_EPSILON;
+}
diff --git a/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/useStep3Matrix.ts b/src/webparts/ctFormsPortal/forms/ct/components/Step3ComparisonTable/useStep3Matrix.ts
index db391d5..6ee108a 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 } from '../types/domain';
import { CtService } from '../../services/CtService';
import { CTFormData } from '../hooks/useCtForm';
import { ExtraSupplier, ExtraPosition, PositionRow, CellEdit, EditableOffers, CriteriaEntry } from './matrixTypes';
-import { applyCreatedIds, buildOfferKey, computeOffer } from './matrixHelpers';
+import { applyCreatedIds, buildOfferKey, computeOffer, sumPositionTotals } from './matrixHelpers';
import { IVendorRecord } from '../../services/VendorDatabaseService';
import { buildColumnDisplayNames, buildColumnLabels, findColumnForRecord } from '../utils/supplierColumns';
import { getNoOfferSuppliers } from '../utils/noOfferSuppliers';
@@ -378,17 +378,19 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
React.useEffect(() => { computedOffersRef.current = computedOffers; }, [computedOffers]);
+ // Суммы по (поставщик, позиция) — один источник и для минимума, и для подсветки ячеек (п.13).
+ const positionTotals = useMemo(() => sumPositionTotals(computedOffers), [computedOffers]);
+
const minTotalByPosition = useMemo((): Map => {
const map = new Map();
allPositions.forEach(pos => {
const values = suppliersWithOffer
- .map(s => computedOffers.filter(o => o.supplierId === s.id && o.positionId === pos.id)
- .reduce((sum, o) => sum + (o.totalWithExtraKzt ?? 0), 0))
+ .map(s => positionTotals.get(buildOfferKey(s.id, pos.id)) ?? 0)
.filter(v => v > 0);
if (values.length > 0) map.set(pos.id, Math.min(...values));
});
return map;
- }, [computedOffers, allPositions, suppliersWithOffer]);
+ }, [positionTotals, allPositions, suppliersWithOffer]);
const updateEdit = useCallback(
(supplierId: string, positionId: string, rowIdx: number, field: 'pricePerUnit' | 'quantity', value: string) => {
@@ -899,6 +901,7 @@ export function useStep3Matrix(args: UseStep3MatrixArgs) {
extraPositionsRows,
supplierCurrencies,
computedOffers,
+ positionTotals,
minTotalByPosition,
// handlers
updateEdit,
|