fix(ct): повторная обработка КП не оставляет в SP дубли предложений
Каждая обработка КП создаёт записи в «Оценка поставщиков», а прежние удалялись только по «Сохранить». Исправленный КП поставщика или файл, загруженный заново, оставлял рядом старые записи — отсюда дубли в SP (СТ №89, №4). Теперь после создания записи прежние записи того же поставщика по той же позиции уходят в корзину; порядок «создать, потом удалить» — чтобы при сбое не потерять предложение.
This commit is contained in:
@@ -24,7 +24,11 @@ import {
|
||||
getRequestTypeIdByPageUrl,
|
||||
writeRoute,
|
||||
} from "../../../shared/approval";
|
||||
import { keepLatestOfferRecords, parseOfferItemIndex } from "../../../shared/offers/offerRecords";
|
||||
import {
|
||||
findSupersededOfferIds,
|
||||
keepLatestOfferRecords,
|
||||
parseOfferItemIndex,
|
||||
} from "../../../shared/offers/offerRecords";
|
||||
|
||||
// Round monetary values to 2 decimals before persisting to SharePoint (drops float tails).
|
||||
function round2(value: number): number {
|
||||
@@ -1598,6 +1602,46 @@ export class CtService {
|
||||
);
|
||||
if (!res.ok)
|
||||
throw new Error(`Ошибка сохранения предложения: ${res.status}`);
|
||||
|
||||
// Повторная обработка КП (исправленный КП, файл загружен заново) раньше добавляла записи рядом
|
||||
// с прежними — в SP копились дубли, если потом не нажимали «Сохранить». Сначала создаём новую
|
||||
// запись, потом убираем прежние: упади создание — старое предложение останется.
|
||||
const created = (await res.json()) as { Id: number };
|
||||
await this._recycleSupersededOffers({
|
||||
id: created.Id,
|
||||
supplierSpId: params.supplierSpId,
|
||||
positionSpId: params.positionSpId,
|
||||
});
|
||||
}
|
||||
|
||||
private async _recycleSupersededOffers(saved: {
|
||||
id: number;
|
||||
supplierSpId: number;
|
||||
positionSpId: number;
|
||||
}): Promise<void> {
|
||||
const url =
|
||||
`${this._li(LISTS.OFFERS)}?$select=Id,Title,positionId,supplierId` +
|
||||
`&$filter=supplierId eq ${saved.supplierSpId} and positionId eq ${saved.positionSpId}`;
|
||||
const res = await this.http.get(url, SPHttpClient.configurations.v1);
|
||||
// Не критично для обработки КП: оставшиеся дубли отсеивают загрузчики (keepLatestOfferRecords),
|
||||
// а «Сохранить» перезапишет записи позиции целиком. Но молча не глотаем.
|
||||
if (!res.ok) {
|
||||
console.warn(`[CtService] не удалось прочитать прежние предложения: ${res.status}`);
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
const existing = ((data.value ?? []) as Array<Record<string, unknown>>).map((item) => ({
|
||||
id: Number(item.Id),
|
||||
supplierSpId: Number(item.supplierId),
|
||||
positionSpId: Number(item.positionId),
|
||||
itemIndex: parseOfferItemIndex(String(item.Title ?? "")),
|
||||
}));
|
||||
// MERGE на этом списке блокирует ресивер — убираем через recycle(), как в saveOffers.
|
||||
await this._runChunked(findSupersededOfferIds(existing, saved), 20, async (id) => {
|
||||
await this.http
|
||||
.post(`${this._liById(LISTS.OFFERS, id)}/recycle()`, SPHttpClient.configurations.v1, {})
|
||||
.catch(() => undefined /* уже в корзине / не найден — игнорируем */);
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Mapping ──────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IOfferRecordKey, keepLatestOfferRecords, parseOfferItemIndex } from './offerRecords';
|
||||
import { IOfferRecordKey, findSupersededOfferIds, keepLatestOfferRecords, parseOfferItemIndex } from './offerRecords';
|
||||
|
||||
interface ITestRecord extends IOfferRecordKey {
|
||||
price: number;
|
||||
@@ -57,3 +57,40 @@ describe('keepLatestOfferRecords', () => {
|
||||
expect(keepLatestOfferRecords(records)).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findSupersededOfferIds', () => {
|
||||
// Повторная обработка КП поставщика 259: новая запись 1850 заменяет прежнее предложение по позиции.
|
||||
const saved = makeRecord({ id: 1850, positionSpId: 1478 });
|
||||
|
||||
it('возвращает прежние записи того же поставщика по той же позиции', () => {
|
||||
const existing = [makeRecord({ id: 1836, positionSpId: 1478 }), saved];
|
||||
|
||||
expect(findSupersededOfferIds(existing, saved)).toEqual([1836]);
|
||||
});
|
||||
|
||||
it('прежние строки комплекта тоже заменяются новым КП', () => {
|
||||
const existing = [
|
||||
makeRecord({ id: 1836, positionSpId: 1478, itemIndex: 0 }),
|
||||
makeRecord({ id: 1837, positionSpId: 1478, itemIndex: 1 }),
|
||||
saved,
|
||||
];
|
||||
|
||||
expect(findSupersededOfferIds(existing, saved)).toEqual([1836, 1837]);
|
||||
});
|
||||
|
||||
it('не трогает другие позиции и других поставщиков', () => {
|
||||
const existing = [
|
||||
makeRecord({ id: 1836, positionSpId: 1476 }),
|
||||
makeRecord({ id: 1837, positionSpId: 1478, supplierSpId: 233 }),
|
||||
saved,
|
||||
];
|
||||
|
||||
expect(findSupersededOfferIds(existing, saved)).toEqual([]);
|
||||
});
|
||||
|
||||
it('не трогает запись новее сохранённой (параллельная обработка)', () => {
|
||||
const existing = [saved, makeRecord({ id: 1900, positionSpId: 1478 })];
|
||||
|
||||
expect(findSupersededOfferIds(existing, saved)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,3 +30,19 @@ export function keepLatestOfferRecords<T extends IOfferRecordKey>(records: T[]):
|
||||
}
|
||||
return Array.from(latestByKey.values());
|
||||
}
|
||||
|
||||
// Какие записи вытеснила только что сохранённая: прежние записи того же поставщика по той же позиции,
|
||||
// включая строки комплекта — новый КП поставщика заменяет его прежнее предложение целиком.
|
||||
// Записи новее сохранённой не трогаем: их могла создать параллельная обработка.
|
||||
export function findSupersededOfferIds(
|
||||
existing: IOfferRecordKey[],
|
||||
saved: Pick<IOfferRecordKey, 'id' | 'supplierSpId' | 'positionSpId'>,
|
||||
): number[] {
|
||||
return existing
|
||||
.filter(record =>
|
||||
record.supplierSpId === saved.supplierSpId &&
|
||||
record.positionSpId === saved.positionSpId &&
|
||||
record.id < saved.id
|
||||
)
|
||||
.map(record => record.id);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user