Сжать долговые ответы и техничность net-flow
This commit is contained in:
parent
151f2a26de
commit
1a94e72381
|
|
@ -2093,36 +2093,31 @@ function formatPayablesEvidenceSuffix(item) {
|
||||||
const suffix = item.documents.length > 1 ? ` (+${item.documents.length - 1})` : "";
|
const suffix = item.documents.length > 1 ? ` (+${item.documents.length - 1})` : "";
|
||||||
parts.push(`документ: ${item.documents[0]}${suffix}`);
|
parts.push(`документ: ${item.documents[0]}${suffix}`);
|
||||||
}
|
}
|
||||||
if (item.sourceRefs.length > 0) {
|
|
||||||
parts.push(`source refs: ${item.sourceRefs.slice(0, 2).join("; ")}`);
|
|
||||||
}
|
|
||||||
return parts.length > 0 ? ` | ${parts.join(" | ")}` : "";
|
return parts.length > 0 ? ` | ${parts.join(" | ")}` : "";
|
||||||
}
|
}
|
||||||
function formatDebtMirrorGroupLine(item) {
|
function formatDebtMirrorGroupLine(item) {
|
||||||
const details = [
|
|
||||||
item.account ? `счет ${item.account}` : null,
|
|
||||||
item.contract ? `договор/аналитика: ${item.contract}` : null,
|
|
||||||
item.organization ? `организация: ${item.organization}` : null
|
|
||||||
].filter((part) => Boolean(part));
|
|
||||||
const netText = Math.abs(item.netAmount) <= 0.005
|
const netText = Math.abs(item.netAmount) <= 0.005
|
||||||
? "чисто: 0 ₽"
|
? "чисто: 0 ₽"
|
||||||
: item.netAmount > 0
|
: item.netAmount > 0
|
||||||
? `чисто к получению: ${formatMoneyRub(item.netAmount)}`
|
? `чисто к получению: ${formatMoneyRub(item.netAmount)}`
|
||||||
: `чисто к оплате: ${formatMoneyRub(Math.abs(item.netAmount))}`;
|
: `чисто к оплате: ${formatMoneyRub(Math.abs(item.netAmount))}`;
|
||||||
return `${item.name}${details.length > 0 ? ` (${details.join(", ")})` : ""}: дебет ${formatMoneyRub(item.debitAmount)} / кредит ${formatMoneyRub(item.creditAmount)}, ${netText}.`;
|
return `${item.name}: встречная часть ${formatMoneyRub(Math.min(item.debitAmount, item.creditAmount))}, ${netText}.`;
|
||||||
}
|
}
|
||||||
function debtMirrorCleanScopeLabel(kind) {
|
function debtMirrorCleanScopeLabel(kind, grammaticalCase) {
|
||||||
return kind === "payables" ? "чистый долг к оплате" : "чистую дебиторку к получению";
|
if (kind === "payables") {
|
||||||
|
return grammaticalCase === "genitive" ? "чистого долга к оплате" : "чистый долг к оплате";
|
||||||
|
}
|
||||||
|
return grammaticalCase === "genitive" ? "чистой дебиторки к получению" : "чистую дебиторку к получению";
|
||||||
}
|
}
|
||||||
function appendDebtMirrorCompactDisclosure(lines, snapshot, kind) {
|
function appendDebtMirrorCompactDisclosure(lines, snapshot, kind) {
|
||||||
if (snapshot.mirroredOffsetAmount <= 0.005) {
|
if (snapshot.mirroredOffsetAmount <= 0.005) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lines.push("", "Для сверки:");
|
lines.push("", "Для сверки:");
|
||||||
lines.push(`- Отдельно сверено встречных остатков: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; они не включены в ${debtMirrorCleanScopeLabel(kind)}.`);
|
lines.push(`- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она не включена в ${debtMirrorCleanScopeLabel(kind, "accusative")}.`);
|
||||||
const leadingMirror = snapshot.mirrorGroups[0] ?? null;
|
const leadingMirror = snapshot.mirrorGroups[0] ?? null;
|
||||||
if (leadingMirror) {
|
if (leadingMirror) {
|
||||||
lines.push(`- Крупнейший встречный хвост: ${formatDebtMirrorGroupLine(leadingMirror)}`);
|
lines.push(`- Крупнейший встречный контрагент: ${formatDebtMirrorGroupLine(leadingMirror)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function appendDebtMirrorDisclosure(lines, snapshot, kind) {
|
function appendDebtMirrorDisclosure(lines, snapshot, kind) {
|
||||||
|
|
@ -2131,8 +2126,11 @@ function appendDebtMirrorDisclosure(lines, snapshot, kind) {
|
||||||
}
|
}
|
||||||
lines.push("");
|
lines.push("");
|
||||||
lines.push("Встречные остатки к сверке");
|
lines.push("Встречные остатки к сверке");
|
||||||
lines.push(`- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она исключена из ${debtMirrorCleanScopeLabel(kind)}.`);
|
lines.push(`- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она исключена из ${debtMirrorCleanScopeLabel(kind, "genitive")}.`);
|
||||||
lines.push(...snapshot.mirrorGroups.slice(0, 3).map((item, index) => `${index + 1}. ${formatDebtMirrorGroupLine(item)}`));
|
const leadingMirror = snapshot.mirrorGroups[0] ?? null;
|
||||||
|
if (leadingMirror) {
|
||||||
|
lines.push(`- Крупнейший встречный контрагент: ${formatDebtMirrorGroupLine(leadingMirror)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function deriveOperationalYearWindow(yearDocs, yearOps) {
|
function deriveOperationalYearWindow(yearDocs, yearOps) {
|
||||||
const docsSeries = [...yearDocs].sort((a, b) => a.year - b.year);
|
const docsSeries = [...yearDocs].sort((a, b) => a.year - b.year);
|
||||||
|
|
@ -3381,8 +3379,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
|
||||||
: [`Коротко: на ${formatDateRu(payablesAsOfDate)} подтвержденных обязательств к оплате не найдено.`];
|
: [`Коротко: на ${formatDateRu(payablesAsOfDate)} подтвержденных обязательств к оплате не найдено.`];
|
||||||
if (leading) {
|
if (leading) {
|
||||||
compactLines.push(...confirmedBalances.slice(0, 5).map((item, index) => {
|
compactLines.push(...confirmedBalances.slice(0, 5).map((item, index) => {
|
||||||
const lastPeriod = item.lastPeriod ? `, последнее движение: ${item.lastPeriod}` : "";
|
return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.).`;
|
||||||
return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.${lastPeriod}).`;
|
|
||||||
}));
|
}));
|
||||||
if (confirmedBalances.length > 5) {
|
if (confirmedBalances.length > 5) {
|
||||||
compactLines.push(`Показаны первые 5 из ${formatNumberWithDots(confirmedBalances.length)} подтвержденных позиций.`);
|
compactLines.push(`Показаны первые 5 из ${formatNumberWithDots(confirmedBalances.length)} подтвержденных позиций.`);
|
||||||
|
|
@ -3429,7 +3426,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
|
||||||
lines.push("Подтвержденные позиции к оплате");
|
lines.push("Подтвержденные позиции к оплате");
|
||||||
if (confirmedBalances.length > 0) {
|
if (confirmedBalances.length > 0) {
|
||||||
lines.push(...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
lines.push(...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
||||||
`${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
`${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
||||||
""
|
""
|
||||||
]));
|
]));
|
||||||
if (lines[lines.length - 1] === "") {
|
if (lines[lines.length - 1] === "") {
|
||||||
|
|
@ -3477,8 +3474,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
|
||||||
: [`Коротко: на ${formatDateRu(receivablesAsOfDate)} подтвержденной дебиторской задолженности не найдено.`];
|
: [`Коротко: на ${formatDateRu(receivablesAsOfDate)} подтвержденной дебиторской задолженности не найдено.`];
|
||||||
if (leading) {
|
if (leading) {
|
||||||
compactLines.push(...confirmedBalances.slice(0, 5).map((item, index) => {
|
compactLines.push(...confirmedBalances.slice(0, 5).map((item, index) => {
|
||||||
const lastPeriod = item.lastPeriod ? `, последнее движение: ${item.lastPeriod}` : "";
|
return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.).`;
|
||||||
return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.${lastPeriod}).`;
|
|
||||||
}));
|
}));
|
||||||
if (confirmedBalances.length > 5) {
|
if (confirmedBalances.length > 5) {
|
||||||
compactLines.push(`Показаны первые 5 из ${formatNumberWithDots(confirmedBalances.length)} подтвержденных позиций.`);
|
compactLines.push(`Показаны первые 5 из ${formatNumberWithDots(confirmedBalances.length)} подтвержденных позиций.`);
|
||||||
|
|
@ -3525,7 +3521,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
|
||||||
lines.push("Подтвержденные позиции к получению");
|
lines.push("Подтвержденные позиции к получению");
|
||||||
if (confirmedBalances.length > 0) {
|
if (confirmedBalances.length > 0) {
|
||||||
lines.push(...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
lines.push(...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
||||||
`${index + 1}. ${item.name} | категория: ${receivablesCategoryLabel(item.category)} | остаток к получению: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
`${index + 1}. ${item.name} | категория: ${receivablesCategoryLabel(item.category)} | остаток к получению: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
||||||
""
|
""
|
||||||
]));
|
]));
|
||||||
if (lines[lines.length - 1] === "") {
|
if (lines[lines.length - 1] === "") {
|
||||||
|
|
@ -3559,7 +3555,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
|
||||||
const carryoverLine = asOfDate || periodFrom || periodTo
|
const carryoverLine = asOfDate || periodFrom || periodTo
|
||||||
? "- В список могут попадать обязательства, возникшие раньше выбранного периода, если они потенциально оставались открытыми на дату среза."
|
? "- В список могут попадать обязательства, возникшие раньше выбранного периода, если они потенциально оставались открытыми на дату среза."
|
||||||
: null;
|
: null;
|
||||||
const formatHeuristicItem = (item, index) => `${index + 1}. ${item.name} | сумма к проверке: ${formatMoneyRub(item.totalAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`;
|
const formatHeuristicItem = (item, index) => `${index + 1}. ${item.name} | сумма к проверке: ${formatMoneyRub(item.totalAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`;
|
||||||
const pushCategorySlice = (lines, title, items, limit) => {
|
const pushCategorySlice = (lines, title, items, limit) => {
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -3656,7 +3652,7 @@ function composeFactualReplyBody(intent, rows, options = {}) {
|
||||||
`- ${liabilityCategoryLabel("other")}: ${formatNumberWithDots(categoryCounts.other)}`,
|
`- ${liabilityCategoryLabel("other")}: ${formatNumberWithDots(categoryCounts.other)}`,
|
||||||
"",
|
"",
|
||||||
"Блок 5. Крупнейшие подтвержденные позиции к оплате (по сумме остатка):",
|
"Блок 5. Крупнейшие подтвержденные позиции к оплате (по сумме остатка):",
|
||||||
...confirmedBalances.slice(0, 10).map((item, index) => `${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток к оплате: ${formatMoneyRub(item.outstandingAmount)} | операций в срезе: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`)
|
...confirmedBalances.slice(0, 10).map((item, index) => `${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток к оплате: ${formatMoneyRub(item.outstandingAmount)} | операций в срезе: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`)
|
||||||
];
|
];
|
||||||
appendDebtMirrorDisclosure(lines, balanceSnapshot, "payables");
|
appendDebtMirrorDisclosure(lines, balanceSnapshot, "payables");
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,7 @@ function localizeLine(value) {
|
||||||
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
||||||
}
|
}
|
||||||
if (/^Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows$/i.test(value)) {
|
if (/^Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows$/i.test(value)) {
|
||||||
return "Нетто денежного потока рассчитано только как входящие подтвержденные строки 1С минус исходящие подтвержденные строки 1С.";
|
return "Нетто денежного потока рассчитано как подтвержденные входящие платежи минус подтвержденные исходящие платежи в 1С.";
|
||||||
}
|
}
|
||||||
if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) {
|
if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) {
|
||||||
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
|
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
|
||||||
|
|
@ -531,26 +531,6 @@ function businessOverviewInventoryLine(overview) {
|
||||||
].filter((item) => Boolean(item));
|
].filter((item) => Boolean(item));
|
||||||
return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null;
|
return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null;
|
||||||
}
|
}
|
||||||
function rowCountText(value) {
|
|
||||||
const count = Number(value);
|
|
||||||
return Number.isFinite(count) ? String(count) : null;
|
|
||||||
}
|
|
||||||
function sideRowsText(side) {
|
|
||||||
const rowsWithAmount = rowCountText(side?.rows_with_amount);
|
|
||||||
const rowsMatched = rowCountText(side?.rows_matched);
|
|
||||||
if (rowsWithAmount && rowsMatched) {
|
|
||||||
return `${rowsWithAmount} из ${rowsMatched}`;
|
|
||||||
}
|
|
||||||
return rowsWithAmount ?? rowsMatched;
|
|
||||||
}
|
|
||||||
function sideDateText(side) {
|
|
||||||
const first = toNonEmptyString(side?.first_movement_date);
|
|
||||||
const latest = toNonEmptyString(side?.latest_movement_date);
|
|
||||||
if (first && latest) {
|
|
||||||
return first === latest ? `дата ${first}` : `даты ${first}..${latest}`;
|
|
||||||
}
|
|
||||||
return first ? `первая дата ${first}` : latest ? `последняя дата ${latest}` : null;
|
|
||||||
}
|
|
||||||
function bidirectionalNetLabel(direction) {
|
function bidirectionalNetLabel(direction) {
|
||||||
if (direction === "net_outgoing") {
|
if (direction === "net_outgoing") {
|
||||||
return "нетто в сторону контрагента";
|
return "нетто в сторону контрагента";
|
||||||
|
|
@ -586,28 +566,24 @@ function buildCompactBidirectionalValueFlowReply(entryPoint, draft) {
|
||||||
: "по выбранному контуру";
|
: "по выбранному контуру";
|
||||||
const period = toNonEmptyString(flow.period_scope);
|
const period = toNonEmptyString(flow.period_scope);
|
||||||
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
||||||
const incomingRows = sideRowsText(incoming);
|
|
||||||
const outgoingRows = sideRowsText(outgoing);
|
|
||||||
const incomingDates = sideDateText(incoming);
|
|
||||||
const outgoingDates = sideDateText(outgoing);
|
|
||||||
const netLabel = bidirectionalNetLabel(flow.net_direction);
|
const netLabel = bidirectionalNetLabel(flow.net_direction);
|
||||||
const lines = [
|
const lines = [
|
||||||
`Коротко: ${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
`Коротко: ${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||||
];
|
];
|
||||||
const basis = [];
|
const basis = [];
|
||||||
if (incomingRows) {
|
if (incomingAmount) {
|
||||||
basis.push(`входящих строк с суммой ${incomingRows}${incomingDates ? ` (${incomingDates})` : ""}`);
|
basis.push("входящие платежи");
|
||||||
}
|
}
|
||||||
if (outgoingRows) {
|
if (outgoingAmount) {
|
||||||
basis.push(`исходящих строк с суммой ${outgoingRows}${outgoingDates ? ` (${outgoingDates})` : ""}`);
|
basis.push("исходящие платежи");
|
||||||
}
|
}
|
||||||
if (basis.length > 0) {
|
if (basis.length > 0) {
|
||||||
lines.push(`Основа: ${basis.join("; ")}.`);
|
lines.push(`Основа: проверенные ${basis.join(" и ")} по этому контрагенту в 1С.`);
|
||||||
}
|
}
|
||||||
if (flow.coverage_limited_by_probe_limit === true) {
|
if (flow.coverage_limited_by_probe_limit === true) {
|
||||||
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
||||||
}
|
}
|
||||||
lines.push("Метод: нетто рассчитано как подтвержденные входящие строки 1С минус подтвержденные исходящие строки; это не полное бухгалтерское сальдо вне проверенного окна.");
|
lines.push("Метод: нетто рассчитано как подтвержденные входящие платежи минус подтвержденные исходящие платежи; это не полное бухгалтерское сальдо вне проверенного окна.");
|
||||||
const fallbackNextStep = toNonEmptyString(draft.next_step_line);
|
const fallbackNextStep = toNonEmptyString(draft.next_step_line);
|
||||||
if (fallbackNextStep) {
|
if (fallbackNextStep) {
|
||||||
lines.push(`Следующий шаг: ${localizeLine(fallbackNextStep)}`);
|
lines.push(`Следующий шаг: ${localizeLine(fallbackNextStep)}`);
|
||||||
|
|
@ -673,21 +649,17 @@ function buildPreviousCounterpartyValueFlowSummary(flow, separateSubject, docume
|
||||||
const lead = `; отдельно по ${counterparty}: получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}, ` +
|
const lead = `; отдельно по ${counterparty}: получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}, ` +
|
||||||
`${netLabel} ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}`;
|
`${netLabel} ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}`;
|
||||||
const basis = [];
|
const basis = [];
|
||||||
const incomingRows = sideRowsText(incoming);
|
if (incomingAmount) {
|
||||||
const outgoingRows = sideRowsText(outgoing);
|
basis.push("входящие платежи");
|
||||||
const incomingDates = sideDateText(incoming);
|
|
||||||
const outgoingDates = sideDateText(outgoing);
|
|
||||||
if (incomingRows) {
|
|
||||||
basis.push(`входящие строки ${incomingRows}${incomingDates ? ` (${incomingDates})` : ""}`);
|
|
||||||
}
|
}
|
||||||
if (outgoingRows) {
|
if (outgoingAmount) {
|
||||||
basis.push(`исходящие строки ${outgoingRows}${outgoingDates ? ` (${outgoingDates})` : ""}`);
|
basis.push("исходящие платежи");
|
||||||
}
|
}
|
||||||
const documents = previousDocumentSummaryLine(documentBundle, counterparty);
|
const documents = previousDocumentSummaryLine(documentBundle, counterparty);
|
||||||
if (documents) {
|
if (documents) {
|
||||||
basis.push(documents);
|
basis.push(documents);
|
||||||
}
|
}
|
||||||
const basisText = basis.length > 0 ? ` Основа: ${basis.join("; ")}.` : "";
|
const basisText = basis.length > 0 ? ` Основа: проверенные ${basis.join(" и ")}.` : "";
|
||||||
return {
|
return {
|
||||||
lead,
|
lead,
|
||||||
line: `Отдельно по контрагенту ${counterparty}: подтверждено получили ${incomingAmount ?? "0 руб."}, ` +
|
line: `Отдельно по контрагенту ${counterparty}: подтверждено получили ${incomingAmount ?? "0 руб."}, ` +
|
||||||
|
|
|
||||||
|
|
@ -2723,29 +2723,24 @@ function formatPayablesEvidenceSuffix(item: PayablesConfirmedBalanceAggregate):
|
||||||
const suffix = item.documents.length > 1 ? ` (+${item.documents.length - 1})` : "";
|
const suffix = item.documents.length > 1 ? ` (+${item.documents.length - 1})` : "";
|
||||||
parts.push(`документ: ${item.documents[0]}${suffix}`);
|
parts.push(`документ: ${item.documents[0]}${suffix}`);
|
||||||
}
|
}
|
||||||
if (item.sourceRefs.length > 0) {
|
|
||||||
parts.push(`source refs: ${item.sourceRefs.slice(0, 2).join("; ")}`);
|
|
||||||
}
|
|
||||||
return parts.length > 0 ? ` | ${parts.join(" | ")}` : "";
|
return parts.length > 0 ? ` | ${parts.join(" | ")}` : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDebtMirrorGroupLine(item: DebtMirrorBalanceGroup): string {
|
function formatDebtMirrorGroupLine(item: DebtMirrorBalanceGroup): string {
|
||||||
const details = [
|
|
||||||
item.account ? `счет ${item.account}` : null,
|
|
||||||
item.contract ? `договор/аналитика: ${item.contract}` : null,
|
|
||||||
item.organization ? `организация: ${item.organization}` : null
|
|
||||||
].filter((part): part is string => Boolean(part));
|
|
||||||
const netText =
|
const netText =
|
||||||
Math.abs(item.netAmount) <= 0.005
|
Math.abs(item.netAmount) <= 0.005
|
||||||
? "чисто: 0 ₽"
|
? "чисто: 0 ₽"
|
||||||
: item.netAmount > 0
|
: item.netAmount > 0
|
||||||
? `чисто к получению: ${formatMoneyRub(item.netAmount)}`
|
? `чисто к получению: ${formatMoneyRub(item.netAmount)}`
|
||||||
: `чисто к оплате: ${formatMoneyRub(Math.abs(item.netAmount))}`;
|
: `чисто к оплате: ${formatMoneyRub(Math.abs(item.netAmount))}`;
|
||||||
return `${item.name}${details.length > 0 ? ` (${details.join(", ")})` : ""}: дебет ${formatMoneyRub(item.debitAmount)} / кредит ${formatMoneyRub(item.creditAmount)}, ${netText}.`;
|
return `${item.name}: встречная часть ${formatMoneyRub(Math.min(item.debitAmount, item.creditAmount))}, ${netText}.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function debtMirrorCleanScopeLabel(kind: "payables" | "receivables"): string {
|
function debtMirrorCleanScopeLabel(kind: "payables" | "receivables", grammaticalCase: "accusative" | "genitive"): string {
|
||||||
return kind === "payables" ? "чистый долг к оплате" : "чистую дебиторку к получению";
|
if (kind === "payables") {
|
||||||
|
return grammaticalCase === "genitive" ? "чистого долга к оплате" : "чистый долг к оплате";
|
||||||
|
}
|
||||||
|
return grammaticalCase === "genitive" ? "чистой дебиторки к получению" : "чистую дебиторку к получению";
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendDebtMirrorCompactDisclosure(
|
function appendDebtMirrorCompactDisclosure(
|
||||||
|
|
@ -2758,11 +2753,11 @@ function appendDebtMirrorCompactDisclosure(
|
||||||
}
|
}
|
||||||
lines.push("", "Для сверки:");
|
lines.push("", "Для сверки:");
|
||||||
lines.push(
|
lines.push(
|
||||||
`- Отдельно сверено встречных остатков: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; они не включены в ${debtMirrorCleanScopeLabel(kind)}.`
|
`- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она не включена в ${debtMirrorCleanScopeLabel(kind, "accusative")}.`
|
||||||
);
|
);
|
||||||
const leadingMirror = snapshot.mirrorGroups[0] ?? null;
|
const leadingMirror = snapshot.mirrorGroups[0] ?? null;
|
||||||
if (leadingMirror) {
|
if (leadingMirror) {
|
||||||
lines.push(`- Крупнейший встречный хвост: ${formatDebtMirrorGroupLine(leadingMirror)}`);
|
lines.push(`- Крупнейший встречный контрагент: ${formatDebtMirrorGroupLine(leadingMirror)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2777,9 +2772,12 @@ function appendDebtMirrorDisclosure(
|
||||||
lines.push("");
|
lines.push("");
|
||||||
lines.push("Встречные остатки к сверке");
|
lines.push("Встречные остатки к сверке");
|
||||||
lines.push(
|
lines.push(
|
||||||
`- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она исключена из ${debtMirrorCleanScopeLabel(kind)}.`
|
`- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она исключена из ${debtMirrorCleanScopeLabel(kind, "genitive")}.`
|
||||||
);
|
);
|
||||||
lines.push(...snapshot.mirrorGroups.slice(0, 3).map((item, index) => `${index + 1}. ${formatDebtMirrorGroupLine(item)}`));
|
const leadingMirror = snapshot.mirrorGroups[0] ?? null;
|
||||||
|
if (leadingMirror) {
|
||||||
|
lines.push(`- Крупнейший встречный контрагент: ${formatDebtMirrorGroupLine(leadingMirror)}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deriveOperationalYearWindow(
|
function deriveOperationalYearWindow(
|
||||||
|
|
@ -4341,8 +4339,7 @@ function composeFactualReplyBody(
|
||||||
if (leading) {
|
if (leading) {
|
||||||
compactLines.push(
|
compactLines.push(
|
||||||
...confirmedBalances.slice(0, 5).map((item, index) => {
|
...confirmedBalances.slice(0, 5).map((item, index) => {
|
||||||
const lastPeriod = item.lastPeriod ? `, последнее движение: ${item.lastPeriod}` : "";
|
return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.).`;
|
||||||
return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.${lastPeriod}).`;
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (confirmedBalances.length > 5) {
|
if (confirmedBalances.length > 5) {
|
||||||
|
|
@ -4396,7 +4393,7 @@ function composeFactualReplyBody(
|
||||||
if (confirmedBalances.length > 0) {
|
if (confirmedBalances.length > 0) {
|
||||||
lines.push(
|
lines.push(
|
||||||
...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
||||||
`${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
`${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
||||||
""
|
""
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
@ -4454,8 +4451,7 @@ function composeFactualReplyBody(
|
||||||
if (leading) {
|
if (leading) {
|
||||||
compactLines.push(
|
compactLines.push(
|
||||||
...confirmedBalances.slice(0, 5).map((item, index) => {
|
...confirmedBalances.slice(0, 5).map((item, index) => {
|
||||||
const lastPeriod = item.lastPeriod ? `, последнее движение: ${item.lastPeriod}` : "";
|
return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.).`;
|
||||||
return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.${lastPeriod}).`;
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (confirmedBalances.length > 5) {
|
if (confirmedBalances.length > 5) {
|
||||||
|
|
@ -4509,7 +4505,7 @@ function composeFactualReplyBody(
|
||||||
if (confirmedBalances.length > 0) {
|
if (confirmedBalances.length > 0) {
|
||||||
lines.push(
|
lines.push(
|
||||||
...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
...confirmedBalances.slice(0, 10).flatMap((item, index) => [
|
||||||
`${index + 1}. ${item.name} | категория: ${receivablesCategoryLabel(item.category)} | остаток к получению: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
`${index + 1}. ${item.name} | категория: ${receivablesCategoryLabel(item.category)} | остаток к получению: ${formatMoneyRub(item.outstandingAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`,
|
||||||
""
|
""
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
@ -4548,7 +4544,7 @@ function composeFactualReplyBody(
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const formatHeuristicItem = (item: PayablesCounterpartyRiskAggregate, index: number): string =>
|
const formatHeuristicItem = (item: PayablesCounterpartyRiskAggregate, index: number): string =>
|
||||||
`${index + 1}. ${item.name} | сумма к проверке: ${formatMoneyRub(item.totalAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`;
|
`${index + 1}. ${item.name} | сумма к проверке: ${formatMoneyRub(item.totalAmount)} | операций: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}`;
|
||||||
|
|
||||||
const pushCategorySlice = (
|
const pushCategorySlice = (
|
||||||
lines: string[],
|
lines: string[],
|
||||||
|
|
@ -4665,7 +4661,7 @@ function composeFactualReplyBody(
|
||||||
"Блок 5. Крупнейшие подтвержденные позиции к оплате (по сумме остатка):",
|
"Блок 5. Крупнейшие подтвержденные позиции к оплате (по сумме остатка):",
|
||||||
...confirmedBalances.slice(0, 10).map(
|
...confirmedBalances.slice(0, 10).map(
|
||||||
(item, index) =>
|
(item, index) =>
|
||||||
`${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток к оплате: ${formatMoneyRub(item.outstandingAmount)} | операций в срезе: ${formatNumberWithDots(item.operations)}${item.lastPeriod ? ` | последнее движение: ${item.lastPeriod}` : ""}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`
|
`${index + 1}. ${item.name} | категория: ${liabilityCategoryLabel(item.category)} | остаток к оплате: ${formatMoneyRub(item.outstandingAmount)} | операций в срезе: ${formatNumberWithDots(item.operations)}${item.categoryReasons.length > 0 ? ` | основание: ${item.categoryReasons.join(", ")}` : ""}${formatPayablesEvidenceSuffix(item)}`
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
appendDebtMirrorDisclosure(lines, balanceSnapshot, "payables");
|
appendDebtMirrorDisclosure(lines, balanceSnapshot, "payables");
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,7 @@ function localizeLine(value: string): string {
|
||||||
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
||||||
}
|
}
|
||||||
if (/^Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows$/i.test(value)) {
|
if (/^Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows$/i.test(value)) {
|
||||||
return "Нетто денежного потока рассчитано только как входящие подтвержденные строки 1С минус исходящие подтвержденные строки 1С.";
|
return "Нетто денежного потока рассчитано как подтвержденные входящие платежи минус подтвержденные исходящие платежи в 1С.";
|
||||||
}
|
}
|
||||||
if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) {
|
if (/^Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows$/i.test(value)) {
|
||||||
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
|
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
|
||||||
|
|
@ -614,29 +614,6 @@ function businessOverviewInventoryLine(overview: Record<string, unknown>): strin
|
||||||
return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null;
|
return pieces.length > 0 ? `Склад: ${pieces.join(", ")}.` : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function rowCountText(value: unknown): string | null {
|
|
||||||
const count = Number(value);
|
|
||||||
return Number.isFinite(count) ? String(count) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sideRowsText(side: Record<string, unknown> | null): string | null {
|
|
||||||
const rowsWithAmount = rowCountText(side?.rows_with_amount);
|
|
||||||
const rowsMatched = rowCountText(side?.rows_matched);
|
|
||||||
if (rowsWithAmount && rowsMatched) {
|
|
||||||
return `${rowsWithAmount} из ${rowsMatched}`;
|
|
||||||
}
|
|
||||||
return rowsWithAmount ?? rowsMatched;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sideDateText(side: Record<string, unknown> | null): string | null {
|
|
||||||
const first = toNonEmptyString(side?.first_movement_date);
|
|
||||||
const latest = toNonEmptyString(side?.latest_movement_date);
|
|
||||||
if (first && latest) {
|
|
||||||
return first === latest ? `дата ${first}` : `даты ${first}..${latest}`;
|
|
||||||
}
|
|
||||||
return first ? `первая дата ${first}` : latest ? `последняя дата ${latest}` : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function bidirectionalNetLabel(direction: unknown): string {
|
function bidirectionalNetLabel(direction: unknown): string {
|
||||||
if (direction === "net_outgoing") {
|
if (direction === "net_outgoing") {
|
||||||
return "нетто в сторону контрагента";
|
return "нетто в сторону контрагента";
|
||||||
|
|
@ -678,29 +655,25 @@ function buildCompactBidirectionalValueFlowReply(
|
||||||
: "по выбранному контуру";
|
: "по выбранному контуру";
|
||||||
const period = toNonEmptyString(flow.period_scope);
|
const period = toNonEmptyString(flow.period_scope);
|
||||||
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
const periodText = period ? ` за период ${period}` : " в проверенном окне";
|
||||||
const incomingRows = sideRowsText(incoming);
|
|
||||||
const outgoingRows = sideRowsText(outgoing);
|
|
||||||
const incomingDates = sideDateText(incoming);
|
|
||||||
const outgoingDates = sideDateText(outgoing);
|
|
||||||
const netLabel = bidirectionalNetLabel(flow.net_direction);
|
const netLabel = bidirectionalNetLabel(flow.net_direction);
|
||||||
const lines = [
|
const lines = [
|
||||||
`Коротко: ${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
`Коротко: ${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||||
];
|
];
|
||||||
|
|
||||||
const basis: string[] = [];
|
const basis: string[] = [];
|
||||||
if (incomingRows) {
|
if (incomingAmount) {
|
||||||
basis.push(`входящих строк с суммой ${incomingRows}${incomingDates ? ` (${incomingDates})` : ""}`);
|
basis.push("входящие платежи");
|
||||||
}
|
}
|
||||||
if (outgoingRows) {
|
if (outgoingAmount) {
|
||||||
basis.push(`исходящих строк с суммой ${outgoingRows}${outgoingDates ? ` (${outgoingDates})` : ""}`);
|
basis.push("исходящие платежи");
|
||||||
}
|
}
|
||||||
if (basis.length > 0) {
|
if (basis.length > 0) {
|
||||||
lines.push(`Основа: ${basis.join("; ")}.`);
|
lines.push(`Основа: проверенные ${basis.join(" и ")} по этому контрагенту в 1С.`);
|
||||||
}
|
}
|
||||||
if (flow.coverage_limited_by_probe_limit === true) {
|
if (flow.coverage_limited_by_probe_limit === true) {
|
||||||
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
||||||
}
|
}
|
||||||
lines.push("Метод: нетто рассчитано как подтвержденные входящие строки 1С минус подтвержденные исходящие строки; это не полное бухгалтерское сальдо вне проверенного окна.");
|
lines.push("Метод: нетто рассчитано как подтвержденные входящие платежи минус подтвержденные исходящие платежи; это не полное бухгалтерское сальдо вне проверенного окна.");
|
||||||
|
|
||||||
const fallbackNextStep = toNonEmptyString(draft.next_step_line);
|
const fallbackNextStep = toNonEmptyString(draft.next_step_line);
|
||||||
if (fallbackNextStep) {
|
if (fallbackNextStep) {
|
||||||
|
|
@ -785,21 +758,17 @@ function buildPreviousCounterpartyValueFlowSummary(
|
||||||
`; отдельно по ${counterparty}: получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}, ` +
|
`; отдельно по ${counterparty}: получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}, ` +
|
||||||
`${netLabel} ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}`;
|
`${netLabel} ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}`;
|
||||||
const basis: string[] = [];
|
const basis: string[] = [];
|
||||||
const incomingRows = sideRowsText(incoming);
|
if (incomingAmount) {
|
||||||
const outgoingRows = sideRowsText(outgoing);
|
basis.push("входящие платежи");
|
||||||
const incomingDates = sideDateText(incoming);
|
|
||||||
const outgoingDates = sideDateText(outgoing);
|
|
||||||
if (incomingRows) {
|
|
||||||
basis.push(`входящие строки ${incomingRows}${incomingDates ? ` (${incomingDates})` : ""}`);
|
|
||||||
}
|
}
|
||||||
if (outgoingRows) {
|
if (outgoingAmount) {
|
||||||
basis.push(`исходящие строки ${outgoingRows}${outgoingDates ? ` (${outgoingDates})` : ""}`);
|
basis.push("исходящие платежи");
|
||||||
}
|
}
|
||||||
const documents = previousDocumentSummaryLine(documentBundle, counterparty);
|
const documents = previousDocumentSummaryLine(documentBundle, counterparty);
|
||||||
if (documents) {
|
if (documents) {
|
||||||
basis.push(documents);
|
basis.push(documents);
|
||||||
}
|
}
|
||||||
const basisText = basis.length > 0 ? ` Основа: ${basis.join("; ")}.` : "";
|
const basisText = basis.length > 0 ? ` Основа: проверенные ${basis.join(" и ")}.` : "";
|
||||||
return {
|
return {
|
||||||
lead,
|
lead,
|
||||||
line:
|
line:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue