Нормализовать НДС follow-up за месяц в налоговый квартал

This commit is contained in:
dctouch 2026-05-25 23:15:54 +03:00
parent d1536ed84b
commit af4b01d954
3 changed files with 71 additions and 2 deletions

View File

@ -211,6 +211,22 @@ function deriveTaxQuarterWindowForDate(value) {
period_to: `${year}-${String(quarterEndMonth).padStart(2, "0")}-${String(quarterEndDay).padStart(2, "0")}` period_to: `${year}-${String(quarterEndMonth).padStart(2, "0")}-${String(quarterEndDay).padStart(2, "0")}`
}; };
} }
function isFullCalendarMonthWindow(periodFrom, periodTo) {
const fromIso = normalizeIsoDateForQuery(periodFrom);
const toIso = normalizeIsoDateForQuery(periodTo);
if (!fromIso || !toIso) {
return false;
}
const fromMatch = fromIso.match(/^(\d{4})-(\d{2})-(\d{2})$/);
const toMatch = toIso.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!fromMatch || !toMatch || fromMatch[1] !== toMatch[1] || fromMatch[2] !== toMatch[2] || fromMatch[3] !== "01") {
return false;
}
const year = Number(fromMatch[1]);
const month = Number(fromMatch[2]);
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
return toMatch[3] === String(lastDay).padStart(2, "0");
}
function deriveMonthWindowForDate(value) { function deriveMonthWindowForDate(value) {
const isoDate = normalizeIsoDateForQuery(value); const isoDate = normalizeIsoDateForQuery(value);
if (!isoDate) { if (!isoDate) {
@ -3003,8 +3019,12 @@ class AddressQueryService {
}) })
}); });
} }
const vatTaxPeriodFromCarriedMonth = intent.intent === "vat_liability_confirmed_for_tax_period" &&
(filters.warnings.includes("period_from_from_followup_context") ||
filters.warnings.includes("period_to_from_followup_context")) &&
isFullCalendarMonthWindow(filters.extracted_filters.period_from, filters.extracted_filters.period_to);
if (intent.intent === "vat_liability_confirmed_for_tax_period" && if (intent.intent === "vat_liability_confirmed_for_tax_period" &&
filters.warnings.includes("period_derived_from_month_phrase")) { (filters.warnings.includes("period_derived_from_month_phrase") || vatTaxPeriodFromCarriedMonth)) {
const taxQuarterWindow = deriveTaxQuarterWindowForDate(filters.extracted_filters.period_to); const taxQuarterWindow = deriveTaxQuarterWindowForDate(filters.extracted_filters.period_to);
if (taxQuarterWindow) { if (taxQuarterWindow) {
filters.extracted_filters = { filters.extracted_filters = {

View File

@ -355,6 +355,23 @@ function deriveTaxQuarterWindowForDate(value: unknown): { period_from: string; p
}; };
} }
function isFullCalendarMonthWindow(periodFrom: unknown, periodTo: unknown): boolean {
const fromIso = normalizeIsoDateForQuery(periodFrom);
const toIso = normalizeIsoDateForQuery(periodTo);
if (!fromIso || !toIso) {
return false;
}
const fromMatch = fromIso.match(/^(\d{4})-(\d{2})-(\d{2})$/);
const toMatch = toIso.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (!fromMatch || !toMatch || fromMatch[1] !== toMatch[1] || fromMatch[2] !== toMatch[2] || fromMatch[3] !== "01") {
return false;
}
const year = Number(fromMatch[1]);
const month = Number(fromMatch[2]);
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
return toMatch[3] === String(lastDay).padStart(2, "0");
}
function deriveMonthWindowForDate(value: unknown): { period_from: string; period_to: string } | null { function deriveMonthWindowForDate(value: unknown): { period_from: string; period_to: string } | null {
const isoDate = normalizeIsoDateForQuery(value); const isoDate = normalizeIsoDateForQuery(value);
if (!isoDate) { if (!isoDate) {
@ -3716,9 +3733,14 @@ export class AddressQueryService {
}) })
}); });
} }
const vatTaxPeriodFromCarriedMonth =
intent.intent === "vat_liability_confirmed_for_tax_period" &&
(filters.warnings.includes("period_from_from_followup_context") ||
filters.warnings.includes("period_to_from_followup_context")) &&
isFullCalendarMonthWindow(filters.extracted_filters.period_from, filters.extracted_filters.period_to);
if ( if (
intent.intent === "vat_liability_confirmed_for_tax_period" && intent.intent === "vat_liability_confirmed_for_tax_period" &&
filters.warnings.includes("period_derived_from_month_phrase") (filters.warnings.includes("period_derived_from_month_phrase") || vatTaxPeriodFromCarriedMonth)
) { ) {
const taxQuarterWindow = deriveTaxQuarterWindowForDate(filters.extracted_filters.period_to); const taxQuarterWindow = deriveTaxQuarterWindowForDate(filters.extracted_filters.period_to);
if (taxQuarterWindow) { if (taxQuarterWindow) {

View File

@ -4102,6 +4102,33 @@ describe("address query limited taxonomy and stage diagnostics", { timeout: 9000
expect(result?.debug.route_expectation_status).toBe("matched"); expect(result?.debug.route_expectation_status).toBe("matched");
}); });
it("normalizes carried monthly debt period into VAT tax quarter for same-period follow-up", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle("а какой ндс мы должны примерно заплатить за этот период?", {
followupContext: {
previous_intent: "receivables_confirmed_as_of_date",
previous_filters: {
organization: 'ООО "Альтернатива Плюс"',
period_from: "2017-05-01",
period_to: "2017-05-31",
as_of_date: "2017-05-31"
},
previous_anchor_type: "organization",
previous_anchor_value: 'ООО "Альтернатива Плюс"',
target_intent: "vat_liability_confirmed_for_tax_period"
}
});
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("vat_liability_confirmed_for_tax_period");
expect(result?.debug.selected_recipe).toBe("address_vat_liability_confirmed_tax_period_v1");
expect(result?.debug.extracted_filters.period_from).toBe("2017-04-01");
expect(result?.debug.extracted_filters.period_to).toBe("2017-06-30");
expect(result?.debug.reasons).toContain("period_from_from_followup_context");
expect(result?.debug.reasons).toContain("period_derived_from_tax_quarter_for_confirmed_vat_liability");
expect(String(result?.reply_text ?? "")).toContain("01.04.2017..30.06.2017");
}, 35000);
it("routes customer lifecycle question into dedicated aggregate recipe", async () => { it("routes customer lifecycle question into dedicated aggregate recipe", async () => {
const service = new AddressQueryService(); const service = new AddressQueryService();
const result = await service.tryHandle("Какие заказчики работали с нами в 2020 году?"); const result = await service.tryHandle("Какие заказчики работали с нами в 2020 году?");