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( "../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"); 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(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(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.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(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(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(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(String(result?.reply_text ?? "")).toContain("Поступление товаров и услуг 00000000077"); }); 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.reasons).toContain("lifecycle_execution_detached_from_snapshot_date"); expect(result?.debug.reasons).toContain("as_of_date_cleared_for_history_recovery"); expect(result?.debug.limitations).toContain("lifecycle_execution_detached_from_snapshot_date"); expect(result?.debug.limitations).toContain("as_of_date_cleared_for_history_recovery"); 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.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 ?? []).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).toContain("Товары.Ссылка.Дата <= ДАТАВРЕМЯ(2020, 3, 31, 23, 59, 59)"); expect(query).toContain("ПРЕДСТАВЛЕНИЕ(Товары.Ссылка.Организация) КАК Организация"); }); });