Сжать долговые ответы и техничность net-flow
This commit is contained in:
@@ -2723,29 +2723,24 @@ function formatPayablesEvidenceSuffix(item: PayablesConfirmedBalanceAggregate):
|
||||
const suffix = item.documents.length > 1 ? ` (+${item.documents.length - 1})` : "";
|
||||
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(" | ")}` : "";
|
||||
}
|
||||
|
||||
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 =
|
||||
Math.abs(item.netAmount) <= 0.005
|
||||
? "чисто: 0 ₽"
|
||||
: item.netAmount > 0
|
||||
? `чисто к получению: ${formatMoneyRub(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 {
|
||||
return kind === "payables" ? "чистый долг к оплате" : "чистую дебиторку к получению";
|
||||
function debtMirrorCleanScopeLabel(kind: "payables" | "receivables", grammaticalCase: "accusative" | "genitive"): string {
|
||||
if (kind === "payables") {
|
||||
return grammaticalCase === "genitive" ? "чистого долга к оплате" : "чистый долг к оплате";
|
||||
}
|
||||
return grammaticalCase === "genitive" ? "чистой дебиторки к получению" : "чистую дебиторку к получению";
|
||||
}
|
||||
|
||||
function appendDebtMirrorCompactDisclosure(
|
||||
@@ -2758,11 +2753,11 @@ function appendDebtMirrorCompactDisclosure(
|
||||
}
|
||||
lines.push("", "Для сверки:");
|
||||
lines.push(
|
||||
`- Отдельно сверено встречных остатков: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; они не включены в ${debtMirrorCleanScopeLabel(kind)}.`
|
||||
`- Встречная часть: ${formatMoneyRub(snapshot.mirroredOffsetAmount)}; она не включена в ${debtMirrorCleanScopeLabel(kind, "accusative")}.`
|
||||
);
|
||||
const leadingMirror = snapshot.mirrorGroups[0] ?? null;
|
||||
if (leadingMirror) {
|
||||
lines.push(`- Крупнейший встречный хвост: ${formatDebtMirrorGroupLine(leadingMirror)}`);
|
||||
lines.push(`- Крупнейший встречный контрагент: ${formatDebtMirrorGroupLine(leadingMirror)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2777,9 +2772,12 @@ function appendDebtMirrorDisclosure(
|
||||
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(
|
||||
@@ -4341,8 +4339,7 @@ function composeFactualReplyBody(
|
||||
if (leading) {
|
||||
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)} опер.${lastPeriod}).`;
|
||||
return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.).`;
|
||||
})
|
||||
);
|
||||
if (confirmedBalances.length > 5) {
|
||||
@@ -4396,7 +4393,7 @@ function composeFactualReplyBody(
|
||||
if (confirmedBalances.length > 0) {
|
||||
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)}`,
|
||||
""
|
||||
])
|
||||
);
|
||||
@@ -4454,8 +4451,7 @@ function composeFactualReplyBody(
|
||||
if (leading) {
|
||||
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)} опер.${lastPeriod}).`;
|
||||
return `${index + 1}. ${item.name} — ${formatMoneyRub(item.outstandingAmount)} (${formatNumberWithDots(item.operations)} опер.).`;
|
||||
})
|
||||
);
|
||||
if (confirmedBalances.length > 5) {
|
||||
@@ -4509,7 +4505,7 @@ function composeFactualReplyBody(
|
||||
if (confirmedBalances.length > 0) {
|
||||
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)}`,
|
||||
""
|
||||
])
|
||||
);
|
||||
@@ -4548,7 +4544,7 @@ function composeFactualReplyBody(
|
||||
: null;
|
||||
|
||||
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 = (
|
||||
lines: string[],
|
||||
@@ -4665,7 +4661,7 @@ function composeFactualReplyBody(
|
||||
"Блок 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)}`
|
||||
`${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");
|
||||
|
||||
@@ -262,7 +262,7 @@ function localizeLine(value: string): string {
|
||||
return "Сумма исходящих платежей рассчитана только по подтвержденным строкам списаний в 1С.";
|
||||
}
|
||||
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)) {
|
||||
return "Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.";
|
||||
@@ -614,29 +614,6 @@ function businessOverviewInventoryLine(overview: Record<string, unknown>): strin
|
||||
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 {
|
||||
if (direction === "net_outgoing") {
|
||||
return "нетто в сторону контрагента";
|
||||
@@ -678,29 +655,25 @@ function buildCompactBidirectionalValueFlowReply(
|
||||
: "по выбранному контуру";
|
||||
const period = toNonEmptyString(flow.period_scope);
|
||||
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 lines = [
|
||||
`Коротко: ${subjectLead}${periodText} по найденным строкам 1С получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}; расчетное ${netLabel}: ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}.`
|
||||
];
|
||||
|
||||
const basis: string[] = [];
|
||||
if (incomingRows) {
|
||||
basis.push(`входящих строк с суммой ${incomingRows}${incomingDates ? ` (${incomingDates})` : ""}`);
|
||||
if (incomingAmount) {
|
||||
basis.push("входящие платежи");
|
||||
}
|
||||
if (outgoingRows) {
|
||||
basis.push(`исходящих строк с суммой ${outgoingRows}${outgoingDates ? ` (${outgoingDates})` : ""}`);
|
||||
if (outgoingAmount) {
|
||||
basis.push("исходящие платежи");
|
||||
}
|
||||
if (basis.length > 0) {
|
||||
lines.push(`Основа: ${basis.join("; ")}.`);
|
||||
lines.push(`Основа: проверенные ${basis.join(" и ")} по этому контрагенту в 1С.`);
|
||||
}
|
||||
if (flow.coverage_limited_by_probe_limit === true) {
|
||||
lines.push("Важно: часть проверки достигла лимита строк, поэтому это проверенный срез найденных движений, а не гарантия полного периода.");
|
||||
}
|
||||
lines.push("Метод: нетто рассчитано как подтвержденные входящие строки 1С минус подтвержденные исходящие строки; это не полное бухгалтерское сальдо вне проверенного окна.");
|
||||
lines.push("Метод: нетто рассчитано как подтвержденные входящие платежи минус подтвержденные исходящие платежи; это не полное бухгалтерское сальдо вне проверенного окна.");
|
||||
|
||||
const fallbackNextStep = toNonEmptyString(draft.next_step_line);
|
||||
if (fallbackNextStep) {
|
||||
@@ -785,21 +758,17 @@ function buildPreviousCounterpartyValueFlowSummary(
|
||||
`; отдельно по ${counterparty}: получили ${incomingAmount ?? "0 руб."}, заплатили ${outgoingAmount ?? "0 руб."}, ` +
|
||||
`${netLabel} ${sentenceAmount(netAmount) ?? netAmount ?? "0 руб."}`;
|
||||
const basis: string[] = [];
|
||||
const incomingRows = sideRowsText(incoming);
|
||||
const outgoingRows = sideRowsText(outgoing);
|
||||
const incomingDates = sideDateText(incoming);
|
||||
const outgoingDates = sideDateText(outgoing);
|
||||
if (incomingRows) {
|
||||
basis.push(`входящие строки ${incomingRows}${incomingDates ? ` (${incomingDates})` : ""}`);
|
||||
if (incomingAmount) {
|
||||
basis.push("входящие платежи");
|
||||
}
|
||||
if (outgoingRows) {
|
||||
basis.push(`исходящие строки ${outgoingRows}${outgoingDates ? ` (${outgoingDates})` : ""}`);
|
||||
if (outgoingAmount) {
|
||||
basis.push("исходящие платежи");
|
||||
}
|
||||
const documents = previousDocumentSummaryLine(documentBundle, counterparty);
|
||||
if (documents) {
|
||||
basis.push(documents);
|
||||
}
|
||||
const basisText = basis.length > 0 ? ` Основа: ${basis.join("; ")}.` : "";
|
||||
const basisText = basis.length > 0 ? ` Основа: проверенные ${basis.join(" и ")}.` : "";
|
||||
return {
|
||||
lead,
|
||||
line:
|
||||
|
||||
Reference in New Issue
Block a user