Open-World: включить маржинальность выбранной номенклатуры
This commit is contained in:
@@ -12,6 +12,7 @@ const COMPUTE_EXACT_INTENTS = new Set([
|
||||
"inventory_purchase_documents_for_item",
|
||||
"inventory_supplier_stock_overlap_as_of_date",
|
||||
"inventory_sale_trace_for_item",
|
||||
"inventory_profitability_for_item",
|
||||
"inventory_purchase_to_sale_chain",
|
||||
"inventory_aging_by_purchase_date",
|
||||
"customer_revenue_and_payments",
|
||||
@@ -67,6 +68,7 @@ function defaultCapabilityId(intent) {
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") {
|
||||
return `inventory_${intent}`;
|
||||
@@ -149,7 +151,14 @@ function resolveCapabilityEnabled(intent) {
|
||||
if (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain") {
|
||||
if (intent === "inventory_profitability_for_item") {
|
||||
return {
|
||||
enabled: true,
|
||||
reason: "inventory_profitability_route_enabled"
|
||||
};
|
||||
}
|
||||
if (intent === "inventory_purchase_to_sale_chain") {
|
||||
return {
|
||||
enabled: true,
|
||||
@@ -240,6 +249,7 @@ function resolveShadowRouteIntent(intent, requestedResultMode) {
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") {
|
||||
return null;
|
||||
|
||||
@@ -101,6 +101,7 @@ function isConfirmedBalanceIntent(intent) {
|
||||
intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "open_contracts_confirmed_as_of_date" ||
|
||||
intent === "payables_confirmed_as_of_date" ||
|
||||
|
||||
@@ -1970,6 +1970,7 @@ function canAutoBroadenPeriodWindow(intent, filters) {
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
@@ -1978,16 +1979,19 @@ function shouldBoostAutoBroadenedLimit(intent) {
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date");
|
||||
}
|
||||
function shouldClearAsOfDateForHistoryRecovery(intent) {
|
||||
return (intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain");
|
||||
}
|
||||
function shouldDetachLifecycleExecutionFromSnapshotContext(intent, reasons) {
|
||||
if (intent !== "inventory_supplier_stock_overlap_as_of_date" &&
|
||||
intent !== "inventory_sale_trace_for_item" &&
|
||||
intent !== "inventory_profitability_for_item" &&
|
||||
intent !== "inventory_purchase_to_sale_chain") {
|
||||
return false;
|
||||
}
|
||||
|
||||
+54
-40
@@ -844,6 +844,17 @@ const BASE_RECIPES = [
|
||||
account_scope_mode: "strict",
|
||||
query_template: "inventory_purchase_to_sale_chain_profile"
|
||||
},
|
||||
{
|
||||
recipe_id: "address_inventory_profitability_for_item_v1",
|
||||
intent: "inventory_profitability_for_item",
|
||||
purpose: "Trace purchase and sale document rows for one inventory item and derive bounded revenue, purchase-cost proxy, spread, and margin",
|
||||
required_filters: ["item"],
|
||||
optional_filters: ["as_of_date", "period_from", "period_to", "organization", "warehouse", "limit", "sort"],
|
||||
default_limit: 600,
|
||||
account_scope: ["41.01"],
|
||||
account_scope_mode: "strict",
|
||||
query_template: "inventory_profitability_profile"
|
||||
},
|
||||
{
|
||||
recipe_id: "address_inventory_aging_by_purchase_date_v1",
|
||||
intent: "inventory_aging_by_purchase_date",
|
||||
@@ -1304,6 +1315,7 @@ function maxLimitForIntent(intent) {
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date" ||
|
||||
intent === "open_contracts_confirmed_as_of_date" ||
|
||||
@@ -1484,31 +1496,15 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
|
||||
: recipe.query_template === "inventory_sale_trace_profile"
|
||||
? buildInventorySaleDocumentQuery(filters, resolvedLimit)
|
||||
: recipe.query_template === "inventory_purchase_to_sale_chain_profile"
|
||||
: recipe.query_template === "inventory_profitability_profile"
|
||||
? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit)
|
||||
: recipe.query_template === "inventory_aging_by_purchase_date_profile"
|
||||
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
|
||||
: recipe.query_template === "contracts_by_counterparty_profile"
|
||||
? CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
: recipe.query_template === "open_contracts_confirmed_as_of_balance_profile"
|
||||
? (() => {
|
||||
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
|
||||
? toDateTimeExpr(filters.as_of_date, true)
|
||||
: null) ??
|
||||
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0
|
||||
? toDateTimeExpr(filters.period_to, true)
|
||||
: null) ??
|
||||
(typeof filters.period_from === "string" && filters.period_from.trim().length > 0
|
||||
? toDateTimeExpr(filters.period_from, true)
|
||||
: null) ??
|
||||
"ТЕКУЩАЯДАТА()";
|
||||
return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE
|
||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
.replaceAll("__AS_OF_EXPR__", asOfExpr)
|
||||
.replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "62", "76"]))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
})()
|
||||
: recipe.query_template === "payables_confirmed_as_of_balance_profile"
|
||||
: recipe.query_template === "inventory_purchase_to_sale_chain_profile"
|
||||
? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit)
|
||||
: recipe.query_template === "inventory_aging_by_purchase_date_profile"
|
||||
? buildInventoryMovementQuery(filters, resolvedLimit, "dt")
|
||||
: recipe.query_template === "contracts_by_counterparty_profile"
|
||||
? CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
: recipe.query_template === "open_contracts_confirmed_as_of_balance_profile"
|
||||
? (() => {
|
||||
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
|
||||
? toDateTimeExpr(filters.as_of_date, true)
|
||||
@@ -1520,13 +1516,13 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||
? toDateTimeExpr(filters.period_from, true)
|
||||
: null) ??
|
||||
"ТЕКУЩАЯДАТА()";
|
||||
return PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
|
||||
return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE
|
||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
.replaceAll("__AS_OF_EXPR__", asOfExpr)
|
||||
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
|
||||
.replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "62", "76"]))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
})()
|
||||
: recipe.query_template === "receivables_confirmed_as_of_balance_profile"
|
||||
: recipe.query_template === "payables_confirmed_as_of_balance_profile"
|
||||
? (() => {
|
||||
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
|
||||
? toDateTimeExpr(filters.as_of_date, true)
|
||||
@@ -1538,23 +1534,41 @@ function buildAddressRecipePlan(recipe, filters) {
|
||||
? toDateTimeExpr(filters.period_from, true)
|
||||
: null) ??
|
||||
"ТЕКУЩАЯДАТА()";
|
||||
return RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
|
||||
return PAYABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
|
||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
.replaceAll("__AS_OF_EXPR__", asOfExpr)
|
||||
.replaceAll("__RECEIVABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
|
||||
.replaceAll("__PAYABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"]))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
})()
|
||||
: MOVEMENTS_QUERY_TEMPLATE
|
||||
.replace("__LIMIT__", String(resolvedLimit))
|
||||
.replace("__WHERE_CLAUSE__", (() => {
|
||||
const extraConditions = [];
|
||||
const accountCondition = buildMovementAccountCondition(filters);
|
||||
if (accountCondition) {
|
||||
extraConditions.push(accountCondition);
|
||||
}
|
||||
return buildWhereClause(filters, "Движения.Период", extraConditions);
|
||||
})())
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
: recipe.query_template === "receivables_confirmed_as_of_balance_profile"
|
||||
? (() => {
|
||||
const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0
|
||||
? toDateTimeExpr(filters.as_of_date, true)
|
||||
: null) ??
|
||||
(typeof filters.period_to === "string" && filters.period_to.trim().length > 0
|
||||
? toDateTimeExpr(filters.period_to, true)
|
||||
: null) ??
|
||||
(typeof filters.period_from === "string" && filters.period_from.trim().length > 0
|
||||
? toDateTimeExpr(filters.period_from, true)
|
||||
: null) ??
|
||||
"ТЕКУЩАЯДАТА()";
|
||||
return RECEIVABLES_CONFIRMED_AS_OF_QUERY_TEMPLATE
|
||||
.replaceAll("__LIMIT__", String(resolvedLimit))
|
||||
.replaceAll("__AS_OF_EXPR__", asOfExpr)
|
||||
.replaceAll("__RECEIVABLE_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"]))
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
})()
|
||||
: MOVEMENTS_QUERY_TEMPLATE
|
||||
.replace("__LIMIT__", String(resolvedLimit))
|
||||
.replace("__WHERE_CLAUSE__", (() => {
|
||||
const extraConditions = [];
|
||||
const accountCondition = buildMovementAccountCondition(filters);
|
||||
if (accountCondition) {
|
||||
extraConditions.push(accountCondition);
|
||||
}
|
||||
return buildWhereClause(filters, "Движения.Период", extraConditions);
|
||||
})())
|
||||
.replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort));
|
||||
return {
|
||||
recipe,
|
||||
query,
|
||||
|
||||
@@ -292,6 +292,7 @@ function isInventoryLifecycleHistoryIntent(intent) {
|
||||
return (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain");
|
||||
}
|
||||
function shouldSuppressInventoryCounterpartyAlias(intent, counterparty, organization) {
|
||||
@@ -1008,6 +1009,7 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_profitability_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date" ||
|
||||
intent === "payables_confirmed_as_of_date" ||
|
||||
@@ -1061,6 +1063,19 @@ function mergeFollowupFilters(current, intent, userMessage, followupContext) {
|
||||
reasons.push("period_from_followup_context");
|
||||
}
|
||||
}
|
||||
if (intent === "inventory_profitability_for_item" &&
|
||||
previousHasPeriod &&
|
||||
hasSelectedObjectInventorySignal(userMessage) &&
|
||||
!hasExplicitPeriodInMessage &&
|
||||
!hasExplicitCurrentDateInMessage) {
|
||||
if (previousPeriodFrom && merged.period_from !== previousPeriodFrom) {
|
||||
merged.period_from = previousPeriodFrom;
|
||||
}
|
||||
if (previousPeriodTo && merged.period_to !== previousPeriodTo) {
|
||||
merged.period_to = previousPeriodTo;
|
||||
}
|
||||
reasons.push("period_from_followup_context");
|
||||
}
|
||||
if (!currentHasPeriod &&
|
||||
previousHasPeriod &&
|
||||
hasFollowupSignal &&
|
||||
|
||||
+89
@@ -56,6 +56,30 @@ function inventoryRequestedPartyMatches(requested, actualParties) {
|
||||
function inventoryPartyListOrUnknown(parties) {
|
||||
return parties.length > 0 ? parties.slice(0, 4).join("; ") : "не выделен отдельным полем";
|
||||
}
|
||||
function sumInventoryRowAmount(rows) {
|
||||
return rows.reduce((sum, row) => sum + (typeof row.amount === "number" && Number.isFinite(row.amount) ? row.amount : 0), 0);
|
||||
}
|
||||
function sumInventoryRowQuantity(rows) {
|
||||
return rows.reduce((sum, row) => sum + (typeof row.quantity === "number" && Number.isFinite(row.quantity) ? row.quantity : 0), 0);
|
||||
}
|
||||
function formatInventoryPercent(value, formatNumberWithDots) {
|
||||
return value === null || !Number.isFinite(value) ? "не подтверждена" : `${formatNumberWithDots(value, 2)}%`;
|
||||
}
|
||||
function inventoryProfitabilityPeriodLabel(options, deps) {
|
||||
const from = typeof options.periodFrom === "string" && options.periodFrom.trim().length > 0 ? options.periodFrom : null;
|
||||
const to = typeof options.periodTo === "string" && options.periodTo.trim().length > 0 ? options.periodTo : null;
|
||||
if (from && to) {
|
||||
return `${deps.formatDateRu(from)} - ${deps.formatDateRu(to)}`;
|
||||
}
|
||||
if (from) {
|
||||
return `с ${deps.formatDateRu(from)}`;
|
||||
}
|
||||
if (to) {
|
||||
return `до ${deps.formatDateRu(to)}`;
|
||||
}
|
||||
const asOfDate = typeof options.asOfDate === "string" && options.asOfDate.trim().length > 0 ? options.asOfDate : null;
|
||||
return asOfDate ? `до ${deps.formatDateRu(asOfDate)}` : "по доступной выборке";
|
||||
}
|
||||
function composeInventoryReply(intent, rows, options, deps) {
|
||||
if (intent === "inventory_on_hand_as_of_date") {
|
||||
const asOfDate = deps.resolvePayablesAsOfDate(options);
|
||||
@@ -353,6 +377,71 @@ function composeInventoryReply(intent, rows, options, deps) {
|
||||
? (0, replyContracts_1.buildFactualListReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)(summary.counterparties.length > 0 ? "strong" : "medium", true))
|
||||
: (0, replyContracts_1.buildFactualSummaryReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)("medium", false));
|
||||
}
|
||||
if (intent === "inventory_profitability_for_item") {
|
||||
const purchaseRows = rows.filter((row) => deps.isInventoryPurchaseMovement(row));
|
||||
const saleRows = rows.filter((row) => deps.isInventorySaleMovement(row));
|
||||
const requestedItemHint = String(options.itemHint ?? "").trim();
|
||||
const excludedCounterpartyTokens = requestedItemHint ? [requestedItemHint] : [];
|
||||
const purchaseSummary = deps.summarizeInventoryTraceRows(purchaseRows, excludedCounterpartyTokens);
|
||||
const saleSummary = deps.summarizeInventoryTraceRows(saleRows, excludedCounterpartyTokens);
|
||||
const itemLabel = requestedItemHint || purchaseSummary.item || saleSummary.item || "товар не определен";
|
||||
const revenue = sumInventoryRowAmount(saleRows);
|
||||
const purchaseCostProxy = sumInventoryRowAmount(purchaseRows);
|
||||
const spread = revenue - purchaseCostProxy;
|
||||
const marginPct = revenue > 0 ? (spread / revenue) * 100 : null;
|
||||
const markupPct = purchaseCostProxy > 0 ? (spread / purchaseCostProxy) * 100 : null;
|
||||
const saleQuantity = sumInventoryRowQuantity(saleRows);
|
||||
const purchaseQuantity = sumInventoryRowQuantity(purchaseRows);
|
||||
const periodLabel = inventoryProfitabilityPeriodLabel(options, deps);
|
||||
const hasSales = saleRows.length > 0;
|
||||
const hasPurchases = purchaseRows.length > 0;
|
||||
const directAnswerLine = hasSales && hasPurchases
|
||||
? `По товару ${itemLabel} за период ${periodLabel} подтверждена выручка продаж ${deps.formatMoneyRub(revenue)} и закупочный след ${deps.formatMoneyRub(purchaseCostProxy)}; расчетный валовый спред по доступным документам: ${deps.formatMoneyRub(spread)}. Маржинальность к выручке: ${formatInventoryPercent(marginPct, deps.formatNumberWithDots)}.`
|
||||
: hasSales
|
||||
? `По товару ${itemLabel} за период ${periodLabel} подтверждена выручка продаж ${deps.formatMoneyRub(revenue)}, но закупочный след в доступных строках не найден; прибыль и маржа не подтверждены.`
|
||||
: hasPurchases
|
||||
? `По товару ${itemLabel} за период ${periodLabel} найден закупочный след ${deps.formatMoneyRub(purchaseCostProxy)}, но продажи в доступных строках не найдены; выручка, прибыль и маржа не подтверждены.`
|
||||
: `По товару ${itemLabel} за период ${periodLabel} не найдено ни продаж, ни закупочного следа в доступных строках 41.01.`;
|
||||
const lines = [directAnswerLine];
|
||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Расчет:", [
|
||||
`Строк продаж со счета 41.01: ${deps.formatNumberWithDots(saleRows.length)}.`,
|
||||
`Строк закупки на счет 41.01: ${deps.formatNumberWithDots(purchaseRows.length)}.`,
|
||||
`Выручка по документам продажи: ${deps.formatMoneyRub(revenue)}.`,
|
||||
`Закупочная сумма по доступным документам: ${deps.formatMoneyRub(purchaseCostProxy)}.`,
|
||||
`Расчетный валовый спред: ${deps.formatMoneyRub(spread)}.`,
|
||||
`Маржинальность к выручке: ${formatInventoryPercent(marginPct, deps.formatNumberWithDots)}.`,
|
||||
`Наценка к закупочному следу: ${formatInventoryPercent(markupPct, deps.formatNumberWithDots)}.`
|
||||
]);
|
||||
if (saleQuantity > 0 || purchaseQuantity > 0) {
|
||||
lines.push(`- Количество в продажах: ${deps.formatNumberWithDots(saleQuantity, 3)}; количество в закупках: ${deps.formatNumberWithDots(purchaseQuantity, 3)}.`);
|
||||
}
|
||||
if (saleSummary.counterparties.length > 0) {
|
||||
lines.push(`- Покупатели в продаже: ${saleSummary.counterparties.slice(0, 4).join("; ")}.`);
|
||||
}
|
||||
if (purchaseSummary.counterparties.length > 0) {
|
||||
lines.push(`- Поставщики в закупочном следе: ${purchaseSummary.counterparties.slice(0, 4).join("; ")}.`);
|
||||
}
|
||||
(0, inventoryReplyPresentation_1.appendInventoryBulletSection)(lines, "Ограничения:", [
|
||||
"Это не чистая прибыль компании и не бухгалтерский финансовый результат.",
|
||||
"Закупочная сумма является proxy по найденным документам поступления; без партионного/управленческого учета нельзя доказать точную себестоимость конкретной продажи.",
|
||||
"Если продажи и закупки попали в разные периоды или разные организации/склады, вывод нужно читать как документальный срез по доступной выборке, а не как полный P&L."
|
||||
]);
|
||||
if (saleRows.length > 0) {
|
||||
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Продажи:", [
|
||||
`- Первая дата продажи: ${deps.inventoryTraceDateLabel(saleSummary.firstPeriod)}.`,
|
||||
`- Последняя дата продажи: ${deps.inventoryTraceDateLabel(saleSummary.lastPeriod)}.`,
|
||||
...deps.formatInventoryTraceRows(saleRows, 8, [itemLabel])
|
||||
]);
|
||||
}
|
||||
if (purchaseRows.length > 0) {
|
||||
(0, inventoryReplyPresentation_1.appendInventorySection)(lines, "Закупки:", [
|
||||
`- Первая дата закупки: ${deps.inventoryTraceDateLabel(purchaseSummary.firstPeriod)}.`,
|
||||
`- Последняя дата закупки: ${deps.inventoryTraceDateLabel(purchaseSummary.lastPeriod)}.`,
|
||||
...deps.formatInventoryTraceRows(purchaseRows, 8, [itemLabel])
|
||||
]);
|
||||
}
|
||||
return (0, replyContracts_1.buildFactualSummaryReply)(lines, (0, replyContracts_1.buildConfirmedBalanceSemantics)(hasSales && hasPurchases ? "strong" : hasSales || hasPurchases ? "medium" : "weak", hasSales || hasPurchases));
|
||||
}
|
||||
if (intent === "inventory_purchase_to_sale_chain") {
|
||||
const purchaseRows = rows.filter((row) => deps.isInventoryPurchaseMovement(row));
|
||||
const saleRows = rows.filter((row) => deps.isInventorySaleMovement(row));
|
||||
|
||||
@@ -299,6 +299,17 @@ exports.INVENTORY_CAPABILITY_CONTRACTS = [
|
||||
answerObjectShape: "inventory_sale_trace_bundle",
|
||||
bundleReusePolicy: "sale_trace_bundle_preferred"
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_profitability_for_item",
|
||||
intent_ids: ["inventory_profitability_for_item"],
|
||||
entry_modes: ["selected_object_drilldown", "clarification_resume"],
|
||||
transitions: ["T3", "T4", "T5", "T7"],
|
||||
requiresFocusObject: true,
|
||||
requiredAnchors: ["item"],
|
||||
resultShape: "item_revenue_purchase_cost_spread_margin_proxy",
|
||||
answerObjectShape: "inventory_profitability_bundle",
|
||||
bundleReusePolicy: "sale_trace_bundle_preferred"
|
||||
}),
|
||||
inventoryExactCapability({
|
||||
capability_id: "inventory_inventory_purchase_to_sale_chain",
|
||||
intent_ids: ["inventory_purchase_to_sale_chain"],
|
||||
|
||||
Reference in New Issue
Block a user