fix(oid): повторная обработка КП поставщика заменяет его прежние предложения, а не дублирует
Шаг 2 ОИД писал предложения нового КП рядом с прежними записями поставщика. Шаг 3 всегда грузит предложения из SP, поэтому позиция выглядела «комплектом» из одинаковых строк, а автосохранение Шага 3 закрепляло это в SP. Теперь по повторному КП прежние записи поставщика по тем же позициям уходят в корзину — после записи новых, чтобы при сбое не потерять данные. Дедуп при загрузке, как в СТ, в ОИД не делаем: номер строки комплекта ОИД в Title не хранит, и такой дедуп удалил бы настоящие строки комплекта.
This commit is contained in:
+4
-1
@@ -146,7 +146,10 @@ export function useKpProcessing(service: OidService, formData: OIDFormData) {
|
||||
};
|
||||
});
|
||||
if (offers.length > 0) {
|
||||
service.saveOffers(offers).catch(() => undefined);
|
||||
service.replaceSupplierOffers(supplierSpId, offers).catch((err) => {
|
||||
// Не критично: Шаг 3 при сохранении перезаписывает предложения позиций целиком.
|
||||
console.warn("[КП ОИД] не удалось записать предложения:", err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
getRequestTypeIdByPageUrl,
|
||||
writeRoute,
|
||||
} from "../../../shared/approval";
|
||||
import { findSupplierOfferIds } from "../../../shared/offers/offerRecords";
|
||||
|
||||
const SITE_URL = "https://tnsukz.sharepoint.com/sites/sed";
|
||||
|
||||
@@ -786,6 +787,47 @@ export class OidService {
|
||||
}
|
||||
}
|
||||
|
||||
// Предложения нового КП поставщика (Шаг 2) заменяют его прежние по этим позициям. Раньше повторная
|
||||
// обработка КП добавляла записи рядом с прежними, а Шаг 3 грузит всё из SP — позиция показывалась
|
||||
// «комплектом» из одинаковых строк, и автосохранение Шага 3 закрепляло его в SP.
|
||||
// Порядок «снимок → запись → удаление снимка»: упади запись — прежнее предложение останется.
|
||||
async replaceSupplierOffers(supplierSpId: number, offers: SupplierOffer[]): Promise<void> {
|
||||
if (offers.length === 0) return;
|
||||
const positionSpIds = offers.map((o) => Number(o.positionId));
|
||||
const filter =
|
||||
`supplierId eq ${supplierSpId} and (` +
|
||||
positionSpIds.map((id) => `positionId eq ${id}`).join(" or ") +
|
||||
")";
|
||||
const res = await this.sp.get(
|
||||
`${listUrl(LISTS.OFFERS)}/items?$filter=${encodeURIComponent(filter)}&$select=Id,positionId,supplierId&$top=1000`,
|
||||
SPHttpClient.configurations.v1,
|
||||
);
|
||||
const previous = res.ok
|
||||
? ((await res.json()).value as Array<Record<string, unknown>>).map((item) => ({
|
||||
id: Number(item.Id),
|
||||
supplierSpId: Number(item.supplierId),
|
||||
positionSpId: Number(item.positionId),
|
||||
}))
|
||||
: [];
|
||||
|
||||
await this.saveOffers(offers);
|
||||
|
||||
const staleIds = findSupplierOfferIds(previous, supplierSpId, positionSpIds);
|
||||
if (staleIds.length === 0) return;
|
||||
const results = await this.executeBatch(
|
||||
staleIds.map((id) => ({
|
||||
method: "POST" as const,
|
||||
url: `${listUrl(LISTS.OFFERS)}/items(${id})/recycle()`,
|
||||
})),
|
||||
);
|
||||
for (const r of results) {
|
||||
if (r.ok || r.status === 404) continue;
|
||||
// SP webhook bug: item IS recycled but event receiver throws FormatException
|
||||
if (r.status === 500 && r.body.includes("FormatException")) continue;
|
||||
throw new Error(`Ошибка удаления прежнего предложения: ${r.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async loadOffersForPositions(
|
||||
positionSpIds: number[],
|
||||
): Promise<SupplierOffer[]> {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IOfferRecordKey, findSupersededOfferIds, keepLatestOfferRecords, parseOfferItemIndex } from './offerRecords';
|
||||
import { IOfferRecordKey, findSupersededOfferIds, findSupplierOfferIds, keepLatestOfferRecords, parseOfferItemIndex } from './offerRecords';
|
||||
|
||||
interface ITestRecord extends IOfferRecordKey {
|
||||
price: number;
|
||||
@@ -94,3 +94,26 @@ describe('findSupersededOfferIds', () => {
|
||||
expect(findSupersededOfferIds(existing, saved)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findSupplierOfferIds', () => {
|
||||
// ОИД: перед записью нового КП поставщика запоминаем его прежние записи по этим позициям,
|
||||
// чтобы после записи убрать их. Номера строк комплекта ОИД в Title не хранит — берём все.
|
||||
it('возвращает все записи поставщика по переданным позициям, включая строки комплекта', () => {
|
||||
const records = [
|
||||
makeRecord({ id: 314, supplierSpId: 18, positionSpId: 121 }),
|
||||
makeRecord({ id: 315, supplierSpId: 18, positionSpId: 121 }),
|
||||
makeRecord({ id: 316, supplierSpId: 18, positionSpId: 122 }),
|
||||
];
|
||||
|
||||
expect(findSupplierOfferIds(records, 18, [121, 122])).toEqual([314, 315, 316]);
|
||||
});
|
||||
|
||||
it('не трогает другие позиции и других поставщиков', () => {
|
||||
const records = [
|
||||
makeRecord({ id: 314, supplierSpId: 18, positionSpId: 999 }),
|
||||
makeRecord({ id: 315, supplierSpId: 19, positionSpId: 121 }),
|
||||
];
|
||||
|
||||
expect(findSupplierOfferIds(records, 18, [121])).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,6 +31,18 @@ export function keepLatestOfferRecords<T extends IOfferRecordKey>(records: T[]):
|
||||
return Array.from(latestByKey.values());
|
||||
}
|
||||
|
||||
// Все записи поставщика по указанным позициям — снимок «до» записи нового КП, чтобы после записи
|
||||
// убрать прежние. Для форм, где SP Id новой записи заранее не известен (ОИД пишет пакетом).
|
||||
export function findSupplierOfferIds(
|
||||
records: Array<Pick<IOfferRecordKey, 'id' | 'supplierSpId' | 'positionSpId'>>,
|
||||
supplierSpId: number,
|
||||
positionSpIds: number[],
|
||||
): number[] {
|
||||
return records
|
||||
.filter(record => record.supplierSpId === supplierSpId && positionSpIds.includes(record.positionSpId))
|
||||
.map(record => record.id);
|
||||
}
|
||||
|
||||
// Какие записи вытеснила только что сохранённая: прежние записи того же поставщика по той же позиции,
|
||||
// включая строки комплекта — новый КП поставщика заменяет его прежнее предложение целиком.
|
||||
// Записи новее сохранённой не трогаем: их могла создать параллельная обработка.
|
||||
|
||||
Reference in New Issue
Block a user