Расширить proof matrix маржинальности follow-up replay
This commit is contained in:
+9
-3
@@ -98,6 +98,10 @@ function asksInventoryMarginFromPaymentOrBank(userMessage) {
|
||||
/(?:товар|номенклатур|inventory|item|sku)/iu.test(text) &&
|
||||
/(?:банк|банковск|выписк|плат[её]ж|оплат|payment|bank|statement)/iu.test(text));
|
||||
}
|
||||
function asksInventoryMarginAccount41Not01(userMessage) {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
return /(?:\b41\b|41\s*сч|сч[её]т[ау]?\s*41)/iu.test(text) && /(?:\b01\b|не\s+ос|основн)/iu.test(text);
|
||||
}
|
||||
function inventoryRowItemLabel(row, deps) {
|
||||
return deps.summarizeInventoryTraceRows([row]).item;
|
||||
}
|
||||
@@ -478,6 +482,8 @@ function composeInventoryReply(intent, rows, options, deps) {
|
||||
const topMarginEntry = highMargin[0] ?? null;
|
||||
const marginBasisRequested = asksForInventoryMarginBasis(options.userMessage);
|
||||
const paymentOrBankFalseSourceRequested = asksInventoryMarginFromPaymentOrBank(options.userMessage);
|
||||
const account41Not01Requested = asksInventoryMarginAccount41Not01(options.userMessage);
|
||||
const withAccountScopePrefix = (line) => account41Not01Requested ? `По счету 41, не по 01/ОС: ${line}` : line;
|
||||
if (paymentOrBankFalseSourceRequested) {
|
||||
const lines = [
|
||||
"По оплатам и банку такой показатель нельзя честно подтвердить: платежи показывают денежный поток и факт оплаты, а не связь реализации с себестоимостью по номенклатуре."
|
||||
@@ -500,9 +506,9 @@ function composeInventoryReply(intent, rows, options, deps) {
|
||||
if (confirmedEntries.length === 0) {
|
||||
const costBaseRowsRequested = asksForInventoryCostBaseRows(options.userMessage);
|
||||
const lines = [
|
||||
costBaseRowsRequested && purchasesWithoutSales.length === 0
|
||||
withAccountScopePrefix(costBaseRowsRequested && purchasesWithoutSales.length === 0
|
||||
? `За период ${periodLabel} подтвержденных строк себестоимостной базы по реализованной номенклатуре не найдено.`
|
||||
: `За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`
|
||||
: `За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`)
|
||||
];
|
||||
const findings = [];
|
||||
if (salesWithoutCost.length > 0) {
|
||||
@@ -549,7 +555,7 @@ function composeInventoryReply(intent, rows, options, deps) {
|
||||
: topMarginEntry
|
||||
? `Самая маржинальная позиция за период ${periodLabel}: ${topMarginEntry.item} — маржа ${formatInventoryPercent(topMarginEntry.marginPct, deps.formatNumberWithDots)}, выручка ${deps.formatMoneyRub(topMarginEntry.revenue)}, себестоимостная база ${deps.formatMoneyRub(topMarginEntry.costProxy)}, валовая разница ${deps.formatMoneyRub(topMarginEntry.spread)}.`
|
||||
: `За период ${periodLabel} не удалось подтвердить рейтинг прибыльности номенклатуры: нужны одновременно строки реализации и закупочного/себестоимостного следа по товарам.`;
|
||||
const lines = [directAnswerLine];
|
||||
const lines = [withAccountScopePrefix(directAnswerLine)];
|
||||
if (marginBasisRequested) {
|
||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "База расчета:", [
|
||||
"выручка: подтвержденные строки реализации по номенклатуре;",
|
||||
|
||||
@@ -188,6 +188,11 @@ function asksInventoryMarginFromPaymentOrBank(userMessage: string | null | undef
|
||||
);
|
||||
}
|
||||
|
||||
function asksInventoryMarginAccount41Not01(userMessage: string | null | undefined): boolean {
|
||||
const text = String(userMessage ?? "").toLowerCase();
|
||||
return /(?:\b41\b|41\s*сч|сч[её]т[ау]?\s*41)/iu.test(text) && /(?:\b01\b|не\s+ос|основн)/iu.test(text);
|
||||
}
|
||||
|
||||
interface InventoryMarginRankingEntry {
|
||||
item: string;
|
||||
revenue: number;
|
||||
@@ -659,6 +664,9 @@ export function composeInventoryReply(
|
||||
const topMarginEntry = highMargin[0] ?? null;
|
||||
const marginBasisRequested = asksForInventoryMarginBasis(options.userMessage);
|
||||
const paymentOrBankFalseSourceRequested = asksInventoryMarginFromPaymentOrBank(options.userMessage);
|
||||
const account41Not01Requested = asksInventoryMarginAccount41Not01(options.userMessage);
|
||||
const withAccountScopePrefix = (line: string): string =>
|
||||
account41Not01Requested ? `По счету 41, не по 01/ОС: ${line}` : line;
|
||||
if (paymentOrBankFalseSourceRequested) {
|
||||
const lines = [
|
||||
"По оплатам и банку такой показатель нельзя честно подтвердить: платежи показывают денежный поток и факт оплаты, а не связь реализации с себестоимостью по номенклатуре."
|
||||
@@ -681,9 +689,11 @@ export function composeInventoryReply(
|
||||
if (confirmedEntries.length === 0) {
|
||||
const costBaseRowsRequested = asksForInventoryCostBaseRows(options.userMessage);
|
||||
const lines: string[] = [
|
||||
costBaseRowsRequested && purchasesWithoutSales.length === 0
|
||||
? `За период ${periodLabel} подтвержденных строк себестоимостной базы по реализованной номенклатуре не найдено.`
|
||||
: `За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`
|
||||
withAccountScopePrefix(
|
||||
costBaseRowsRequested && purchasesWithoutSales.length === 0
|
||||
? `За период ${periodLabel} подтвержденных строк себестоимостной базы по реализованной номенклатуре не найдено.`
|
||||
: `За период ${periodLabel} рейтинг прибыльности номенклатуры построить нельзя.`
|
||||
)
|
||||
];
|
||||
const findings: string[] = [];
|
||||
if (salesWithoutCost.length > 0) {
|
||||
@@ -754,7 +764,7 @@ export function composeInventoryReply(
|
||||
topMarginEntry.costProxy
|
||||
)}, валовая разница ${deps.formatMoneyRub(topMarginEntry.spread)}.`
|
||||
: `За период ${periodLabel} не удалось подтвердить рейтинг прибыльности номенклатуры: нужны одновременно строки реализации и закупочного/себестоимостного следа по товарам.`;
|
||||
const lines: string[] = [directAnswerLine];
|
||||
const lines: string[] = [withAccountScopePrefix(directAnswerLine)];
|
||||
|
||||
if (marginBasisRequested) {
|
||||
appendInventoryBulletSection(lines, "База расчета:", [
|
||||
|
||||
@@ -641,4 +641,64 @@ describe("address reply builders regressions", () => {
|
||||
expect(result?.text).not.toContain("Самая маржинальная позиция");
|
||||
expect(result?.text).not.toMatch(/(?:оплат[аы]|банк|payment_document).{0,80}(?:источник|достаточ|посчитал|марж[ау])/iu);
|
||||
});
|
||||
|
||||
it("acknowledges account 41 guard before repeating margin ranking", () => {
|
||||
const result = composeInventoryReply(
|
||||
"inventory_margin_ranking_for_nomenclature",
|
||||
[
|
||||
{
|
||||
kind: "sale",
|
||||
amount: 10800,
|
||||
quantity: 1,
|
||||
item: "Флаг геральдический",
|
||||
period: "2017-05-20",
|
||||
registrator: "Реализация товаров"
|
||||
} as any,
|
||||
{
|
||||
kind: "purchase",
|
||||
amount: 8520,
|
||||
quantity: 1,
|
||||
item: "Флаг геральдический",
|
||||
period: "2017-01-10",
|
||||
registrator: "Поступление товаров"
|
||||
} as any
|
||||
],
|
||||
{
|
||||
userMessage: "Анализ по 41 счету, а не по 01.",
|
||||
periodFrom: "2017-01-01",
|
||||
periodTo: "2017-12-31"
|
||||
},
|
||||
{
|
||||
resolvePayablesAsOfDate: () => "2017-12-31",
|
||||
buildInventoryOnHandAggregate: () => [],
|
||||
uniqueStrings: (values: string[]) => Array.from(new Set(values)),
|
||||
formatDateRu: (value: string) => value,
|
||||
formatNumberWithDots: (value: number, fractionDigits = 0) => value.toFixed(fractionDigits),
|
||||
formatMoneyRub: (value: number) => `${value} ₽`,
|
||||
isInventoryPurchaseMovement: (row: any) => row.kind === "purchase",
|
||||
summarizeInventoryTraceRows: (rows: any[]) => ({
|
||||
item: rows[0]?.item ?? null,
|
||||
warehouses: [],
|
||||
organizations: [],
|
||||
counterparties: [],
|
||||
documents: [],
|
||||
firstPeriod: null,
|
||||
lastPeriod: null,
|
||||
totalAmount: 0
|
||||
}),
|
||||
formatInventoryTraceRows: () => [],
|
||||
hasInventoryPurchaseDateActionFocus: () => false,
|
||||
inventoryTraceDateLabel: () => "",
|
||||
extractInventoryCounterpartyCandidates: () => [],
|
||||
buildInventoryAgingByItemAggregate: () => [],
|
||||
formatInventoryAgingRows: () => [],
|
||||
isInventorySaleMovement: (row: any) => row.kind === "sale"
|
||||
}
|
||||
);
|
||||
|
||||
const firstLine = result?.text.split("\n")[0] ?? "";
|
||||
expect(firstLine).toContain("По счету 41, не по 01/ОС");
|
||||
expect(firstLine).toContain("Самая маржинальная позиция");
|
||||
expect(result?.text).not.toContain("амортизац");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user