fix(ct): предложения из КП пишутся в SP сразу при обработке, а не только по «Сохранить»
Предложения писались внутри цикла по позициям и только если поставщика уже определил Syntex. Syntex отключён, поставщика теперь называет поток — узнаём его после разбора всех позиций, поэтому при обработке КП в «Оценка поставщиков» не попадало ничего: закрыл вкладку без «Сохранить» — и разобранные цены пропали. Теперь записи строятся после определения поставщика (buildKpOffersToSave), а сбои потока и записи идут в консоль вместо пустого catch.
This commit is contained in:
+74
@@ -0,0 +1,74 @@
|
||||
import { buildKpOffersToSave } from './kpOfferRecords';
|
||||
import { FlowPositionResult } from './kpTypes';
|
||||
import { Position } from '../types/domain';
|
||||
|
||||
function makePosition(overrides: Partial<Position>): Position {
|
||||
return {
|
||||
id: 'pos_1',
|
||||
spId: 1543,
|
||||
requestId: 'req_1',
|
||||
requestNumber: '051/26-08/0015',
|
||||
materialName: 'Доска пробковая',
|
||||
disciplineCode: '',
|
||||
smrType: '',
|
||||
unit: 'шт',
|
||||
agskCode: '',
|
||||
uniqueCode: '',
|
||||
quantity: 4,
|
||||
positionType: 'ТМЦ',
|
||||
deadline: '',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
const POSITIONS = [
|
||||
makePosition({}),
|
||||
makePosition({ id: 'pos_2', spId: 1544, materialName: 'Флипчарт', quantity: 3 }),
|
||||
];
|
||||
|
||||
const RESULTS: FlowPositionResult[] = [
|
||||
{ positionId: 'pos_1', positionSpId: 1543, flowResult: { position: 'Доска в раме', unit: 'шт.', quantity: 4, price: 28500, cost: 114000 } },
|
||||
{ positionId: 'pos_2', positionSpId: 1544, flowResult: { price: 98000, cost: 294000 } },
|
||||
];
|
||||
|
||||
describe('buildKpOffersToSave', () => {
|
||||
// Syntex отключён: поставщика называет поток, и узнаём его только после разбора всех позиций.
|
||||
// Раньше записи строились внутри цикла и без поставщика от Syntex не строились вовсе.
|
||||
it('строит записи по поставщику, определённому после разбора', () => {
|
||||
const offers = buildKpOffersToSave(RESULTS, POSITIONS, 264, 283);
|
||||
|
||||
expect(offers).toHaveLength(2);
|
||||
expect(offers[0]).toEqual({
|
||||
positionSpId: 1543,
|
||||
supplierSpId: 264,
|
||||
offerSpId: 283,
|
||||
supplierPosition: 'Доска в раме',
|
||||
unit: 'шт.',
|
||||
quantity: 4,
|
||||
price: 28500,
|
||||
summa: 114000,
|
||||
});
|
||||
});
|
||||
|
||||
it('пустые поля потока берёт из позиции СТ', () => {
|
||||
const offers = buildKpOffersToSave(RESULTS, POSITIONS, 264, 283);
|
||||
|
||||
expect(offers[1]).toMatchObject({ supplierPosition: 'Флипчарт', unit: 'шт', quantity: 3, price: 98000 });
|
||||
});
|
||||
|
||||
it('без поставщика записей нет', () => {
|
||||
expect(buildKpOffersToSave(RESULTS, POSITIONS, undefined, 283)).toEqual([]);
|
||||
});
|
||||
|
||||
it('пропускает позицию без SP Id', () => {
|
||||
const results: FlowPositionResult[] = [{ positionId: 'pos_x', flowResult: { price: 1 } }];
|
||||
|
||||
expect(buildKpOffersToSave(results, POSITIONS, 264, 283)).toEqual([]);
|
||||
});
|
||||
|
||||
it('берёт SP Id из позиции, если в результате его нет', () => {
|
||||
const results: FlowPositionResult[] = [{ positionId: 'pos_2', flowResult: { price: 5 } }];
|
||||
|
||||
expect(buildKpOffersToSave(results, POSITIONS, 264, 283)[0].positionSpId).toBe(1544);
|
||||
});
|
||||
});
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { FlowPositionResult } from './kpTypes';
|
||||
import { Position } from '../types/domain';
|
||||
|
||||
// Запись «Оценка поставщиков» по одной позиции из разобранного КП (вход CtService.saveKpOffer).
|
||||
export interface KpOfferToSave {
|
||||
positionSpId: number;
|
||||
supplierSpId: number;
|
||||
offerSpId: number;
|
||||
supplierPosition: string;
|
||||
unit: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
summa: number;
|
||||
}
|
||||
|
||||
// Строит записи после разбора всего КП. Поставщик может прийти из ответа потока (Syntex отключён),
|
||||
// а его узнаём только когда разобраны все позиции — поэтому записи не строим внутри цикла по позициям.
|
||||
// Пустые поля потока добираем из позиции СТ.
|
||||
export function buildKpOffersToSave(
|
||||
results: FlowPositionResult[],
|
||||
positions: Position[],
|
||||
supplierSpId: number | undefined,
|
||||
fileSpId: number,
|
||||
): KpOfferToSave[] {
|
||||
if (!supplierSpId) return [];
|
||||
|
||||
const offers: KpOfferToSave[] = [];
|
||||
for (const { positionId, positionSpId, flowResult } of results) {
|
||||
const position = positions.find(p => p.id === positionId);
|
||||
const spId = positionSpId ?? position?.spId;
|
||||
if (!spId) continue;
|
||||
|
||||
offers.push({
|
||||
positionSpId: spId,
|
||||
supplierSpId,
|
||||
offerSpId: fileSpId,
|
||||
supplierPosition: flowResult.position ?? position?.materialName ?? '',
|
||||
unit: flowResult.unit ?? position?.unit ?? '',
|
||||
quantity: flowResult.quantity ?? position?.quantity ?? 0,
|
||||
price: flowResult.price ?? 0,
|
||||
summa: flowResult.cost ?? 0,
|
||||
});
|
||||
}
|
||||
return offers;
|
||||
}
|
||||
+14
-14
@@ -3,6 +3,7 @@ import { CtService } from '../../services/CtService';
|
||||
import { CTFormData } from '../hooks/useCtForm';
|
||||
import { KpFile, FlowPositionResult, POLL_INTERVAL_MS, MAX_POLL_RETRIES } from './kpTypes';
|
||||
import { buildKpSuppliersAndOffers } from './step2Persist';
|
||||
import { buildKpOffersToSave } from './kpOfferRecords';
|
||||
|
||||
let localIdCounter = 0;
|
||||
|
||||
@@ -91,20 +92,9 @@ export function useKpProcessing(
|
||||
try {
|
||||
const result = await service.triggerFlow(fileName, fileSpId, pos.materialName);
|
||||
collectedResults.push({ positionId: pos.id, positionSpId: pos.spId, flowResult: result });
|
||||
if (pos.spId && supplierSpId && result) {
|
||||
await service.saveKpOffer({
|
||||
positionSpId: pos.spId,
|
||||
supplierSpId,
|
||||
offerSpId: fileSpId,
|
||||
supplierPosition: result.position ?? pos.materialName,
|
||||
unit: result.unit ?? pos.unit,
|
||||
quantity: result.quantity ?? pos.quantity,
|
||||
price: result.price ?? 0,
|
||||
summa: result.cost ?? 0,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// individual position failure doesn't stop others
|
||||
} catch (err) {
|
||||
// Сбой одной позиции не останавливает остальные, но и не теряется молча.
|
||||
console.warn(`[КП] поток не разобрал позицию «${pos.materialName}»:`, err);
|
||||
}
|
||||
if (mountedRef.current) {
|
||||
setKpFiles(prev => prev.map(f =>
|
||||
@@ -128,6 +118,16 @@ export function useKpProcessing(
|
||||
}
|
||||
}
|
||||
|
||||
// Предложения пишем после того, как поставщик определён: Syntex отключён, и поставщика чаще
|
||||
// называет сам поток — внутри цикла по позициям его ещё нет, и записи раньше не создавались вовсе.
|
||||
const offersToSave = buildKpOffersToSave(collectedResults, positions, resolvedSupplierSpId, fileSpId);
|
||||
await Promise.all(offersToSave.map(offer =>
|
||||
service.saveKpOffer(offer).catch(err => {
|
||||
// Не критично: «Сохранить» на Шаге 2/3 перезапишет предложения позиции целиком.
|
||||
console.warn(`[КП] не удалось записать предложение по позиции ${offer.positionSpId}:`, err);
|
||||
})
|
||||
));
|
||||
|
||||
// БИН поставщика (для Adata) — берём первый результат из позиций, где он вообще пришёл
|
||||
const supplierBIN = collectedResults.find(r => r.flowResult.supplierBIN)?.flowResult.supplierBIN;
|
||||
if (supplierBIN && resolvedSupplierSpId) {
|
||||
|
||||
Reference in New Issue
Block a user