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 { CTFormData } from '../hooks/useCtForm';
|
||||||
import { KpFile, FlowPositionResult, POLL_INTERVAL_MS, MAX_POLL_RETRIES } from './kpTypes';
|
import { KpFile, FlowPositionResult, POLL_INTERVAL_MS, MAX_POLL_RETRIES } from './kpTypes';
|
||||||
import { buildKpSuppliersAndOffers } from './step2Persist';
|
import { buildKpSuppliersAndOffers } from './step2Persist';
|
||||||
|
import { buildKpOffersToSave } from './kpOfferRecords';
|
||||||
|
|
||||||
let localIdCounter = 0;
|
let localIdCounter = 0;
|
||||||
|
|
||||||
@@ -91,20 +92,9 @@ export function useKpProcessing(
|
|||||||
try {
|
try {
|
||||||
const result = await service.triggerFlow(fileName, fileSpId, pos.materialName);
|
const result = await service.triggerFlow(fileName, fileSpId, pos.materialName);
|
||||||
collectedResults.push({ positionId: pos.id, positionSpId: pos.spId, flowResult: result });
|
collectedResults.push({ positionId: pos.id, positionSpId: pos.spId, flowResult: result });
|
||||||
if (pos.spId && supplierSpId && result) {
|
} catch (err) {
|
||||||
await service.saveKpOffer({
|
// Сбой одной позиции не останавливает остальные, но и не теряется молча.
|
||||||
positionSpId: pos.spId,
|
console.warn(`[КП] поток не разобрал позицию «${pos.materialName}»:`, err);
|
||||||
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
|
|
||||||
}
|
}
|
||||||
if (mountedRef.current) {
|
if (mountedRef.current) {
|
||||||
setKpFiles(prev => prev.map(f =>
|
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) — берём первый результат из позиций, где он вообще пришёл
|
// БИН поставщика (для Adata) — берём первый результат из позиций, где он вообще пришёл
|
||||||
const supplierBIN = collectedResults.find(r => r.flowResult.supplierBIN)?.flowResult.supplierBIN;
|
const supplierBIN = collectedResults.find(r => r.flowResult.supplierBIN)?.flowResult.supplierBIN;
|
||||||
if (supplierBIN && resolvedSupplierSpId) {
|
if (supplierBIN && resolvedSupplierSpId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user