1100 lines
51 KiB
TypeScript
1100 lines
51 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
||
|
||
const { executeAddressMcpQueryMock } = vi.hoisted(() => ({
|
||
executeAddressMcpQueryMock: vi.fn()
|
||
}));
|
||
|
||
vi.mock("../src/services/addressMcpClient", async () => {
|
||
const actual = await vi.importActual<typeof import("../src/services/addressMcpClient")>(
|
||
"../src/services/addressMcpClient"
|
||
);
|
||
return {
|
||
...actual,
|
||
executeAddressMcpQuery: executeAddressMcpQueryMock
|
||
};
|
||
});
|
||
|
||
import { AddressQueryService } from "../src/services/addressQueryService";
|
||
|
||
afterEach(() => {
|
||
executeAddressMcpQueryMock.mockReset();
|
||
vi.restoreAllMocks();
|
||
});
|
||
|
||
describe("inventory selected-object follow-up", () => {
|
||
it("inherits dated stock upper bound for selected-object provenance and then auto-broadens history", async () => {
|
||
executeAddressMcpQueryMock
|
||
.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2021-03-09T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000001 от 09.03.2021 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 442075,
|
||
SubcontoDt1: "Рабочая станция универсального специалиста с угловым элементом.",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "АСТРА",
|
||
SubcontoKt2: "А_03/2021 от 01.03.2021г.",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
})
|
||
.mockResolvedValueOnce({
|
||
fetched_rows: 2,
|
||
matched_rows: 2,
|
||
raw_rows: [
|
||
{
|
||
Period: "2020-02-11T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000077 от 11.02.2020 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 165.83,
|
||
SubcontoDt1: "Кромка с клеем 33 альмандин 137 м",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "Торговый дом \\Союз МСК\\",
|
||
SubcontoKt2: "Договор поставки № 12 от 01.02.2020",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
{
|
||
Period: "2020-02-11T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000077 от 11.02.2020 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 165.83,
|
||
SubcontoDt1: "Кромка с клеем 33 дуб ниагара 137 м",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "Торговый дом \\Союз МСК\\",
|
||
SubcontoKt2: "Договор поставки № 12 от 01.02.2020",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle(
|
||
'По выбранному объекту "Кромка с клеем 33 альмандин 137 м | склад: Основной склад | количество: 1,000 | стоимость: 165,83 ₽ | организация: ООО \\\\Альтернатива Плюс\\\\ | дата строки: 2021-03-31T23:59:59Z": От какого поставщика куплен товар',
|
||
{
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2021-03-31",
|
||
warehouse: "Основной склад",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
}
|
||
);
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Кромка с клеем 33 альмандин 137 м");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2021-03-31");
|
||
expect(result?.debug.extracted_filters?.period_from).toBe("2021-03-01");
|
||
expect(result?.debug.extracted_filters?.period_to).toBe("2021-03-31");
|
||
expect(result?.debug.capability_id).toBe("inventory_inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.capability_route_mode).toBe("exact");
|
||
expect(result?.debug.reasons).toContain("period_window_auto_broadened_to_available_data");
|
||
expect(result?.debug.limitations).toContain("period_window_auto_broadened_to_available_data");
|
||
expect(result?.debug.address_coverage_evidence_v1?.coverage_status).toBe("full");
|
||
expect(result?.debug.address_coverage_evidence_v1?.evidence_basis).toBe("matched_rows");
|
||
expect(result?.debug.address_truth_gate_v1?.truth_gate_status).toBe("limited_temporal_or_contextual");
|
||
expect(result?.debug.address_truth_gate_v1?.carryover_eligibility).toBe("object_only");
|
||
const replyLines = String(result?.reply_text ?? "").split("\n");
|
||
expect(replyLines[0]).toContain("По позиции Кромка с клеем 33 альмандин 137 м");
|
||
expect(replyLines[0]).toContain("до 31.03.2021 подтвержден поставщик");
|
||
expect(replyLines[0]).toContain("Торговый дом \\Союз МСК\\");
|
||
expect(String(result?.reply_text ?? "")).toContain("Для ответа учтены закупочные документы не позже 31.03.2021.");
|
||
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(2);
|
||
});
|
||
|
||
it("uses analysis date hint for canonical item provenance wording without explicit date", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2019-02-12T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000003 от 12.02.2019 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 3724.17,
|
||
SubcontoDt1: "Столешница 600*3050*26 дуб ниагара",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "Торговый дом \\Союз",
|
||
SubcontoKt2: "Договор поставки № 12 от 01.02.2019",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle("От какого поставщика куплен товар Столешница 600*3050*26 дуб ниагара", {
|
||
analysisDateHint: "2019-03-31"
|
||
});
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Столешница 600*3050*26 дуб ниагара");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2019-03-31");
|
||
expect(result?.debug.reasons).toContain("as_of_date_from_analysis_context");
|
||
expect(String(result?.reply_text ?? "")).toContain("Торговый дом \\Союз");
|
||
});
|
||
|
||
it("handles selected-object supplier slang 'кто это поставил нам' as provenance follow-up", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2019-02-11T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000077 от 11.02.2019 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 3724.17,
|
||
SubcontoDt1: "Столешница 600*3050*26 дуб ниагара",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "Торговый дом \\Союз МСК\\",
|
||
SubcontoKt2: "Договор поставки № 12 от 01.02.2019",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle('По выбранному объекту "Столешница 600*3050*26 дуб ниагара": кто это поставил нам', {
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2019-03-31",
|
||
period_from: "2019-03-01",
|
||
period_to: "2019-03-31",
|
||
warehouse: "Основной склад",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
});
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Столешница 600*3050*26 дуб ниагара");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2019-03-31");
|
||
expect(result?.debug.reasons).toContain("as_of_date_from_followup_context");
|
||
expect(String(result?.reply_text ?? "")).toContain("Торговый дом \\Союз МСК\\");
|
||
});
|
||
|
||
it("handles selected-object supplier wording 'кто нам это поставил' as provenance follow-up", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2019-02-12T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000003 от 12.02.2019 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 3690,
|
||
SubcontoDt1: "Столешница 600*3050*26 альмандин",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "Торговый дом \\Союз",
|
||
SubcontoKt2: "Договор поставки № 12 от 01.02.2019",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle(
|
||
'По выбранному объекту "Столешница 600*3050*26 альмандин": кто нам это поставил?',
|
||
{
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2021-03-31",
|
||
warehouse: "Основной склад",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
}
|
||
);
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Столешница 600*3050*26 альмандин");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2021-03-31");
|
||
expect(result?.debug.reasons).toContain("as_of_date_from_followup_context");
|
||
expect(result?.debug.capability_id).toBe("inventory_inventory_purchase_provenance_for_item");
|
||
expect(String(result?.reply_text ?? "")).toContain("Торговый дом \\Союз");
|
||
});
|
||
|
||
it("handles selected-object colloquial supplier wording 'у кого купили' as provenance follow-up", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2016-07-20T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000011 от 20.07.2016 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 695360,
|
||
SubcontoDt1: "Рабочая станция универсального специалиста (индивидуальное изготовление)",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "ООО \\Производство мебели\\",
|
||
SubcontoKt2: "Договор поставки № 7 от 15.07.2016",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle(
|
||
'По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": у кого купили',
|
||
{
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2016-07-31",
|
||
period_from: "2016-07-01",
|
||
period_to: "2016-07-31",
|
||
warehouse: "Основной склад",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
}
|
||
);
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Рабочая станция универсального специалиста (индивидуальное изготовление)");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2016-07-31");
|
||
expect(result?.debug.reasons).toContain("as_of_date_from_followup_context");
|
||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
|
||
});
|
||
|
||
it("handles selected-object colloquial supplier wording 'у кого куплено' as provenance follow-up", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2020-06-18T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000101 от 18.06.2020 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 498472.5,
|
||
SubcontoDt1: "Конструкция трансформер рабочей станции 1300*900*2000",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "ООО \\Гамма-мебель\\",
|
||
SubcontoKt2: "Договор поставки № 11 от 15.06.2020",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle(
|
||
'По выбранному объекту "Конструкция трансформер рабочей станции 1300*900*2000": у кого куплено',
|
||
{
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2020-06-30",
|
||
period_from: "2020-06-01",
|
||
period_to: "2020-06-30",
|
||
warehouse: "Основной склад",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
}
|
||
);
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.selected_recipe).toBe("address_inventory_purchase_provenance_for_item_v1");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Конструкция трансформер рабочей станции 1300*900*2000");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2020-06-30");
|
||
expect(result?.debug.extracted_filters?.period_from).toBeUndefined();
|
||
expect(result?.debug.extracted_filters?.period_to).toBeUndefined();
|
||
expect(result?.debug.reasons).toContain("as_of_date_from_followup_context");
|
||
expect(result?.debug.capability_id).toBe("inventory_inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.capability_route_mode).toBe("exact");
|
||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Гамма-мебель\\");
|
||
});
|
||
|
||
it("handles selected-object wording 'где мы купили это' as provenance follow-up", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2016-05-20T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000009 от 20.05.2016 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 695360,
|
||
SubcontoDt1: "Рабочая станция универсального специалиста (индивидуальное изготовление)",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "ООО \\Производство мебели\\",
|
||
SubcontoKt2: "Договор поставки № 5 от 16.05.2016",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle(
|
||
'По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где мы купили это',
|
||
{
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2016-05-31",
|
||
period_from: "2016-05-01",
|
||
period_to: "2016-05-31",
|
||
warehouse: "Основной склад",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
}
|
||
);
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Рабочая станция универсального специалиста (индивидуальное изготовление)");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2016-05-31");
|
||
expect(result?.debug.reasons).toContain("as_of_date_from_followup_context");
|
||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
|
||
});
|
||
|
||
it("handles selected-object wording 'где куплено!!' as provenance follow-up", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2016-05-20T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000009 от 20.05.2016 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 695360,
|
||
SubcontoDt1: "Рабочая станция универсального специалиста (индивидуальное изготовление)",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "ООО \\Производство мебели\\",
|
||
SubcontoKt2: "Договор поставки № 5 от 16.05.2016",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle(
|
||
'По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где куплено!!',
|
||
{
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2016-05-31",
|
||
period_from: "2016-05-01",
|
||
period_to: "2016-05-31",
|
||
warehouse: "Основной склад",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
}
|
||
);
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Рабочая станция универсального специалиста (индивидуальное изготовление)");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2016-05-31");
|
||
expect(result?.debug.reasons).toContain("as_of_date_from_followup_context");
|
||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
|
||
});
|
||
|
||
it("handles selected-object typo wording 'где куплего' as provenance follow-up", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2016-05-20T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000009 от 20.05.2016 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 695360,
|
||
SubcontoDt1: "Рабочая станция универсального специалиста (индивидуальное изготовление)",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "ООО \\Производство мебели\\",
|
||
SubcontoKt2: "Договор поставки № 5 от 16.05.2016",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle(
|
||
'По выбранному объекту "Рабочая станция универсального специалиста (индивидуальное изготовление)": где куплего',
|
||
{
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2016-05-31",
|
||
period_from: "2016-05-01",
|
||
period_to: "2016-05-31",
|
||
warehouse: "Основной склад",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
}
|
||
);
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.selected_recipe).toBe("address_inventory_purchase_provenance_for_item_v1");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Рабочая станция универсального специалиста (индивидуальное изготовление)");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2016-05-31");
|
||
expect(result?.debug.reasons).toContain("as_of_date_from_followup_context");
|
||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Производство мебели\\");
|
||
});
|
||
|
||
it("handles selected-object purchase-doc slang 'по каким документам это купили' as exact purchase-doc follow-up", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2019-02-11T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000077 от 11.02.2019 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 3724.17,
|
||
SubcontoDt1: "Столешница 600*3050*26 дуб ниагара",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "Торговый дом \\Союз МСК\\",
|
||
SubcontoKt2: "Договор поставки № 12 от 01.02.2019",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle('По выбранному объекту "Столешница 600*3050*26 дуб ниагара": по каким документам это купили', {
|
||
followupContext: {
|
||
previous_intent: "inventory_purchase_provenance_for_item",
|
||
previous_filters: {
|
||
as_of_date: "2019-03-31",
|
||
period_from: "2019-03-01",
|
||
period_to: "2019-03-31",
|
||
item: "Столешница 600*3050*26 дуб ниагара",
|
||
warehouse: "Основной склад"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
});
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_LIST");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_documents_for_item");
|
||
expect(result?.debug.selected_recipe).toBe("address_inventory_purchase_documents_for_item_v1");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Столешница 600*3050*26 дуб ниагара");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2019-03-31");
|
||
expect(result?.debug.reasons).toContain("as_of_date_from_followup_context");
|
||
expect(String(result?.reply_text ?? "")).toContain("Поступление товаров и услуг 00000000077");
|
||
});
|
||
|
||
it("does not let carried counterparty scope steal selected-item document follow-up into open items", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2019-02-12T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000003 от 12.02.2019 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 3690,
|
||
SubcontoDt1: "Столешница 600*3050*26 альмандин",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "Торговый дом \\Союз",
|
||
SubcontoKt2: "Договор поставки № 12 от 01.02.2019",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle("покажи документы по этой позиции", {
|
||
followupContext: {
|
||
previous_intent: "inventory_purchase_provenance_for_item",
|
||
previous_filters: {
|
||
item: "Столешница 600*3050*26 альмандин",
|
||
organization: "ООО \\Альтернатива Плюс\\",
|
||
counterparty: "Альтернатива Плюс, ООО",
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2021-03-31"
|
||
},
|
||
previous_anchor_type: "counterparty",
|
||
previous_anchor_value: "ООО \\Альтернатива Плюс\\",
|
||
root_intent: "inventory_on_hand_as_of_date",
|
||
root_filters: {
|
||
organization: "ООО \\Альтернатива Плюс\\",
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2021-03-31"
|
||
},
|
||
current_frame_kind: "inventory_drilldown"
|
||
}
|
||
});
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_documents_for_item");
|
||
expect(result?.debug.selected_recipe).toBe("address_inventory_purchase_documents_for_item_v1");
|
||
expect(result?.debug.capability_id).toBe("inventory_inventory_purchase_documents_for_item");
|
||
expect(result?.debug.reasons).not.toContain("open_items_from_followup_context");
|
||
expect(String(result?.reply_text ?? "")).toContain("Поступление товаров и услуг 00000000003");
|
||
});
|
||
|
||
it("routes buyer follow-up over the same selected item into sale trace instead of replaying provenance", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2020-04-12T00:00:00Z",
|
||
Registrator: "Реализация товаров и услуг 00000000112 от 12.04.2020 0:00:00",
|
||
AccountDt: "90.02",
|
||
AccountKt: "41.01",
|
||
Amount: 833.33,
|
||
SubcontoKt1: "Четки Пост (84*117)",
|
||
SubcontoKt3: "Основной склад",
|
||
SubcontoDt1: "ИП Покупатель",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle("кому в итоге мы продали этот товар?", {
|
||
followupContext: {
|
||
previous_intent: "inventory_purchase_provenance_for_item",
|
||
previous_filters: {
|
||
as_of_date: "2020-03-31",
|
||
period_from: "2020-03-01",
|
||
period_to: "2020-03-31",
|
||
item: "Четки Пост (84*117)",
|
||
warehouse: "Основной склад"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
});
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_LIST");
|
||
expect(result?.debug.detected_intent).toBe("inventory_sale_trace_for_item");
|
||
expect(result?.debug.selected_recipe).toBe("address_inventory_sale_trace_for_item_v1");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Четки Пост (84*117)");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2020-03-31");
|
||
expect(String(result?.reply_text ?? "").split("\n")[0]).toContain("ИП Покупатель");
|
||
expect(String(result?.reply_text ?? "")).toContain("Документы выбытия");
|
||
});
|
||
|
||
it("routes selected-object wording 'куда мы продали эту позицию' into sale trace instead of replaying stock slice", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2020-06-18T00:00:00Z",
|
||
Registrator: "Реализация товаров и услуг 00000000131 от 18.06.2020 0:00:00",
|
||
AccountDt: "90.02",
|
||
AccountKt: "41.01",
|
||
Amount: 6490,
|
||
SubcontoKt1: "Пуф арий",
|
||
SubcontoKt3: "Основной склад",
|
||
SubcontoDt1: "ООО \\Ромашка\\",
|
||
SubcontoDt2: "Договор реализации № 14 от 17.06.2020",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle('По выбранному объекту "Пуф арий": куда мы продали эту позицию', {
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2020-05-31",
|
||
period_from: "2020-05-01",
|
||
period_to: "2020-05-31"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
});
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_LIST");
|
||
expect(result?.debug.detected_intent).toBe("inventory_sale_trace_for_item");
|
||
expect(result?.debug.selected_recipe).toBe("address_inventory_sale_trace_for_item_v1");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Пуф арий");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2020-05-31");
|
||
expect(result?.debug.reasons).toContain("inventory_selected_object_sale_trace_signal_detected");
|
||
expect(String(result?.reply_text ?? "").split("\n")[0]).toContain("ООО \\Ромашка\\");
|
||
expect(String(result?.reply_text ?? "")).toContain("Документы выбытия");
|
||
});
|
||
|
||
it("promotes short buyer follow-up after provenance answer into sale trace", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2020-04-15T00:00:00Z",
|
||
Registrator: "Реализация товаров и услуг 00000000119 от 15.04.2020 0:00:00",
|
||
AccountDt: "90.02",
|
||
AccountKt: "41.01",
|
||
Amount: 199,
|
||
SubcontoKt1: "Кромка с клеем 33 альмандин 137 м",
|
||
SubcontoDt1: "ООО \\Покупатель\\",
|
||
SubcontoDt2: "Договор реализации № 17 от 14.04.2020",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle("ахуен а кому в итоге продали?", {
|
||
followupContext: {
|
||
previous_intent: "inventory_purchase_provenance_for_item",
|
||
previous_filters: {
|
||
as_of_date: "2020-03-31",
|
||
period_from: "2020-03-01",
|
||
period_to: "2020-03-31",
|
||
item: "Кромка с клеем 33 альмандин 137 м",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "item",
|
||
previous_anchor_value: "Кромка с клеем 33 альмандин 137 м"
|
||
}
|
||
});
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_LIST");
|
||
expect(result?.debug.detected_intent).toBe("inventory_sale_trace_for_item");
|
||
expect(result?.debug.selected_recipe).toBe("address_inventory_sale_trace_for_item_v1");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Кромка с клеем 33 альмандин 137 м");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2020-03-31");
|
||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Покупатель\\");
|
||
});
|
||
|
||
it("detaches snapshot date from execution query during sale-trace history recovery", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2025-10-12T00:00:00Z",
|
||
Registrator: "Реализация товаров и услуг 00000000421 от 12.10.2025 0:00:00",
|
||
AccountDt: "90.02",
|
||
AccountKt: "41.01",
|
||
Amount: 165.83,
|
||
SubcontoKt1: "Кромка с клеем 33 дуб ниагара 137 м",
|
||
SubcontoKt3: "Основной склад",
|
||
SubcontoDt1: "ООО \\Покупатель\\",
|
||
SubcontoDt2: "Договор реализации № 55 от 01.10.2025",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle(
|
||
'По выбранному объекту "Кромка с клеем 33 дуб ниагара 137 м": куда в итоге продали эту позицию?',
|
||
{
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2019-03-31",
|
||
period_from: "2019-03-01",
|
||
period_to: "2019-03-31",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "counterparty",
|
||
previous_anchor_value: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
}
|
||
);
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_LIST");
|
||
expect(result?.debug.detected_intent).toBe("inventory_sale_trace_for_item");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2019-03-31");
|
||
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
|
||
const query = String(executeAddressMcpQueryMock.mock.calls[0]?.[0]?.query ?? "");
|
||
expect(query).not.toContain("2019-03-31");
|
||
expect(query).not.toContain("2019-03-01");
|
||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Покупатель\\");
|
||
});
|
||
|
||
it("matches sale-trace item anchors from subconto fields when the item is not materialized explicitly", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2020-04-12T00:00:00Z",
|
||
Registrator: "Реализация товаров и услуг 00000000112 от 12.04.2020 0:00:00",
|
||
AccountDt: "90.02",
|
||
AccountKt: "41.01",
|
||
Amount: 833.33,
|
||
SubcontoDt1: "Шкаф картотечный 1000*400*2100",
|
||
SubcontoKt1: "ИП Покупатель",
|
||
SubcontoKt2: "Коммерческая структура",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle("Кому был продан товар Шкаф картотечный 1000*400*2100?", {});
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.debug.detected_intent).toBe("inventory_sale_trace_for_item");
|
||
expect(result?.debug.mcp_call_status).toBe("matched_non_empty");
|
||
expect(result?.debug.rows_matched).toBeGreaterThan(0);
|
||
expect(String(result?.reply_text ?? "")).not.toContain("совпадений не нашлось");
|
||
});
|
||
|
||
it("keeps semantic stock period for unresolved supplier-link follow-up while querying purchase history up to as-of date", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2020-06-04T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000012 от 04.06.2020 13:36:29",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 439000,
|
||
SubcontoDt1: "Кресло для посетителей Экокожа/хром Цвет - оранжевый",
|
||
SubcontoDt3: "Основной склад",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle("Какие товары сейчас висят в остатке без понятной привязки к поставщику", {
|
||
analysisDateHint: "2021-09-30",
|
||
followupContext: {
|
||
previous_intent: "inventory_aging_by_purchase_date",
|
||
previous_filters: {
|
||
as_of_date: "2021-09-30",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
root_intent: "inventory_on_hand_as_of_date",
|
||
root_filters: {
|
||
period_from: "2021-09-01",
|
||
period_to: "2021-09-30",
|
||
as_of_date: "2021-09-30",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "unknown",
|
||
previous_anchor_value: null
|
||
}
|
||
});
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.debug.detected_intent).toBe("inventory_supplier_stock_overlap_as_of_date");
|
||
expect(result?.debug.extracted_filters?.period_from).toBe("2021-09-01");
|
||
expect(result?.debug.extracted_filters?.period_to).toBe("2021-09-30");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2021-09-30");
|
||
expect(result?.debug.reasons).toContain("period_window_semantic_from_inventory_snapshot_context");
|
||
const query = String(executeAddressMcpQueryMock.mock.calls[0]?.[0]?.query ?? "");
|
||
expect(query).not.toContain("ДАТАВРЕМЯ(2021, 9, 1");
|
||
expect(query).toContain("ДАТАВРЕМЯ(2021, 9, 30, 23, 59, 59)");
|
||
});
|
||
|
||
it("derives semantic stock month for unresolved supplier-link when dialog continuation starts a new topic", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2020-06-04T00:00:00Z",
|
||
Registrator: "Purchase document 00000000012 from 2020-06-04",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 439000,
|
||
SubcontoDt1: "Office chair",
|
||
SubcontoDt3: "Main warehouse",
|
||
Organization: "OOO Test Org"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const unresolvedSupplierLinkQuestion =
|
||
"\u041a\u0430\u043a\u0438\u0435 \u0442\u043e\u0432\u0430\u0440\u044b \u0441\u0435\u0439\u0447\u0430\u0441 \u0432\u0438\u0441\u044f\u0442 \u0432 \u043e\u0441\u0442\u0430\u0442\u043a\u0435 \u0431\u0435\u0437 \u043f\u043e\u043d\u044f\u0442\u043d\u043e\u0439 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u043a \u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a\u0443";
|
||
const result = await service.tryHandle(unresolvedSupplierLinkQuestion, {
|
||
analysisDateHint: "2021-09-30",
|
||
activeOrganization: "OOO Test Org"
|
||
});
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.debug.detected_intent).toBe("inventory_supplier_stock_overlap_as_of_date");
|
||
expect(result?.debug.extracted_filters?.period_from).toBe("2021-09-01");
|
||
expect(result?.debug.extracted_filters?.period_to).toBe("2021-09-30");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2021-09-30");
|
||
expect(result?.debug.reasons).toContain("period_window_semantic_from_inventory_as_of_month");
|
||
expect(result?.debug.reasons).not.toContain("period_window_semantic_from_inventory_snapshot_context");
|
||
const query = String(executeAddressMcpQueryMock.mock.calls[0]?.[0]?.query ?? "");
|
||
expect(query).not.toContain("(2021, 9, 1");
|
||
expect(query).toContain("(2021, 9, 30, 23, 59, 59)");
|
||
});
|
||
|
||
it("treats supplier in purchase-to-sale chain as verification party, not stale organization scope", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 2,
|
||
matched_rows: 2,
|
||
raw_rows: [
|
||
{
|
||
Период: "2019-12-10T00:00:00Z",
|
||
Регистратор: "Поступление товаров и услуг 00000000111 от 10.12.2019 12:00:01",
|
||
СчетДт: "41.01",
|
||
СчетКт: "",
|
||
Сумма: 855000,
|
||
Номенклатура: "Шкаф картотечный 1000*400*2100",
|
||
Контрагент: "ЭталонМебель",
|
||
Организация: "ООО \\Альтернатива Плюс\\",
|
||
Количество: 15
|
||
},
|
||
{
|
||
Период: "2020-04-01T00:00:00Z",
|
||
Регистратор: "Реализация товаров и услуг 00000000001 от 01.04.2020 0:00:00",
|
||
СчетДт: "",
|
||
СчетКт: "41.01",
|
||
Сумма: 855000,
|
||
Номенклатура: "Шкаф картотечный 1000*400*2100",
|
||
Контрагент: "ЭталонМебель",
|
||
Организация: "ООО \\Альтернатива Плюс\\",
|
||
Количество: 15
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle(
|
||
"Есть ли документально подтвержденная цепочка: поставщик Гамма-мебель, ООО -> товар Шкаф картотечный 1000*400*2100 -> покупатель Департамент капитального ремонта города Москвы",
|
||
{
|
||
activeOrganization: "ООО \\Альтернатива Плюс\\",
|
||
llmSemanticHints: {
|
||
scope_target_kind: "organization",
|
||
scope_target_text: "Гамма-мебель, ООО",
|
||
date_scope_kind: "explicit",
|
||
self_scope_detected: false,
|
||
selected_object_scope_detected: false
|
||
},
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2020-03-31",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "organization",
|
||
previous_anchor_value: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
}
|
||
);
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_to_sale_chain");
|
||
expect(result?.debug.extracted_filters?.organization).toBe("ООО Альтернатива Плюс");
|
||
expect(result?.debug.reasons).toContain("organization_restored_from_inventory_chain_context");
|
||
expect(result?.debug.reasons).toContain("inventory_chain_counterparty_anchor_kept_for_verification");
|
||
expect(String(result?.reply_text ?? "").split("\n")[0]).toContain("полностью не подтверждена");
|
||
expect(String(result?.reply_text ?? "")).toContain("ЭталонМебель");
|
||
});
|
||
|
||
it.skip("keeps the full selected item when sale trace is asked in canonical wording after provenance", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2021-04-15T00:00:00Z",
|
||
Registrator: "Реализация товаров и услуг 00000000201 от 15.04.2021 0:00:00",
|
||
AccountDt: "90.02",
|
||
AccountKt: "41.01",
|
||
Amount: 165.83,
|
||
SubcontoKt1: "Кромка с клеем 33 дуб ниагара 137 м",
|
||
SubcontoDt1: "ООО \\Покупатель\\",
|
||
SubcontoDt2: "Договор реализации № 17 от 14.04.2021",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle(
|
||
"Определить контрагента, которому была продана позиция «Кромка с клеем 33 дуб ниагара 137 м» по выбранному объекту",
|
||
{
|
||
followupContext: {
|
||
previous_intent: "inventory_purchase_provenance_for_item",
|
||
previous_filters: {
|
||
as_of_date: "2021-03-31",
|
||
period_from: "2021-03-01",
|
||
period_to: "2021-03-31",
|
||
item: "Кромка с клеем 33 дуб ниагара 137 м",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "item",
|
||
previous_anchor_value: "Кромка с клеем 33 дуб ниагара 137 м"
|
||
}
|
||
}
|
||
);
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_LIST");
|
||
expect(result?.debug.detected_intent).toBe("inventory_sale_trace_for_item");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Кромка с клеем 33 дуб ниагара 137 м");
|
||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Покупатель\\");
|
||
});
|
||
|
||
it("keeps snapshot date as an upper bound for selected-object provenance after dated stock slice", async () => {
|
||
executeAddressMcpQueryMock.mockResolvedValueOnce({
|
||
fetched_rows: 1,
|
||
matched_rows: 1,
|
||
raw_rows: [
|
||
{
|
||
Period: "2020-06-18T00:00:00Z",
|
||
Registrator: "Поступление товаров и услуг 00000000101 от 18.06.2020 0:00:00",
|
||
AccountDt: "41.01",
|
||
AccountKt: "60.01",
|
||
Amount: 13490,
|
||
SubcontoDt1: "Кресло орион",
|
||
SubcontoDt3: "Основной склад",
|
||
SubcontoKt1: "ООО \\Гамма-мебель\\",
|
||
SubcontoKt2: "Договор поставки № 11 от 15.06.2020",
|
||
Organization: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
],
|
||
rows: [],
|
||
error: null
|
||
});
|
||
|
||
const service = new AddressQueryService();
|
||
const result = await service.tryHandle('По выбранному объекту "Кресло орион": кто поставил это?', {
|
||
followupContext: {
|
||
previous_intent: "inventory_on_hand_as_of_date",
|
||
previous_filters: {
|
||
as_of_date: "2020-03-31",
|
||
period_from: "2020-03-01",
|
||
period_to: "2020-03-31",
|
||
organization: "ООО \\Альтернатива Плюс\\"
|
||
},
|
||
previous_anchor_type: "counterparty",
|
||
previous_anchor_value: "ООО \\Альтернатива Плюс\\"
|
||
}
|
||
});
|
||
|
||
expect(result?.handled).toBe(true);
|
||
expect(result?.response_type).toBe("FACTUAL_SUMMARY");
|
||
expect(result?.debug.detected_intent).toBe("inventory_purchase_provenance_for_item");
|
||
expect(result?.debug.extracted_filters?.item).toBe("Кресло орион");
|
||
expect(result?.debug.extracted_filters?.as_of_date).toBe("2020-03-31");
|
||
expect(result?.debug.extracted_filters?.period_from).toBeUndefined();
|
||
expect(result?.debug.extracted_filters?.period_to).toBeUndefined();
|
||
expect(result?.debug.reasons).toContain("as_of_date_from_followup_context");
|
||
expect(result?.debug.reasons ?? []).not.toContain("lifecycle_execution_detached_from_snapshot_date");
|
||
expect(result?.debug.reasons ?? []).not.toContain("as_of_date_cleared_for_history_recovery");
|
||
expect(result?.debug.limitations ?? []).not.toContain("lifecycle_execution_detached_from_snapshot_date");
|
||
expect(result?.debug.limitations ?? []).not.toContain("as_of_date_cleared_for_history_recovery");
|
||
expect(String(result?.reply_text ?? "")).toContain("ООО \\Гамма-мебель\\");
|
||
expect(executeAddressMcpQueryMock).toHaveBeenCalledTimes(1);
|
||
const query = String(executeAddressMcpQueryMock.mock.calls[0]?.[0]?.query ?? "");
|
||
expect(query).toContain("Документ.ПоступлениеТоваровУслуг.Товары КАК Товары");
|
||
expect(query).toContain("Товары.Номенклатура В (ВЫБРАТЬ Номенклатура.Ссылка");
|
||
expect(query).not.toContain("2020-03-31");
|
||
expect(query).toContain("ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.Организация) КАК Организация");
|
||
});
|
||
});
|