НДС в DI2: трекер авансовых УПД, доработка графика и виджета «Контроль квартала»
- Новый GET /api/di2/nds/advance-vat-tracker (nds_advance_vat_tracker): KPI «сколько можем сбавить НДС» (оценка сверху 22/122) + топ-15 поставщиков без авансовой УПД; карточка сразу после «Динамики НДС» с обязательными оговорками из спеки - Новый GET /api/di2/nds/quarter-control: redHanging / redNoAdvance («проверить зачёт») / totals / byMarker / ?all=1 — по правкам виджета из спеки - График «Динамика НДС» (месяц): два столбца «К уплате за месяц» и «Накоплено за квартал», кварталы выделены фоновыми полосами (ReferenceArea) - Виджет «Контроль квартала»: легенда с подписями маркеров, итог «N договоров, сумма авансов X», кнопка «Показать все» (полный список с маркерами), отдельный список «Оплачено, аванс не висячий», полный год в датах договоров, overflow-x для таблиц
This commit is contained in:
@@ -13,7 +13,7 @@ import { DatabaseError } from "@/components/database-error";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { CalendarDays, X, TrendingUp, TrendingDown, DollarSign, Receipt, Calculator, Settings, ChevronRight, ChevronDown, GitBranch, Plus, Trash2, ExternalLink, Link2, Search, AlertTriangle } from "lucide-react";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, Line, ComposedChart } from "recharts";
|
||||
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend, Line, ComposedChart, ReferenceArea } from "recharts";
|
||||
import { formatCurrency } from "@/lib/format";
|
||||
|
||||
interface CatNode {
|
||||
@@ -437,10 +437,17 @@ export default function Calculations() {
|
||||
refetchInterval: 5 * 60 * 1000,
|
||||
});
|
||||
|
||||
// Маркер «деньги vs отгрузка» по договорам: красные флаги квартала (главный запрос спецификации)
|
||||
const ndsMarkersQuery = useQuery<any>({
|
||||
queryKey: ["/api/di2/nds/contract-markers"],
|
||||
queryFn: () => ndsFetch("/api/di2/nds/contract-markers?marker=RED_NO_SHIPMENT&minPaid=1000000"),
|
||||
// Трекер авансовых УПД поставщиков («где сбавить НДС» — spec nds_advance_vat_tracker)
|
||||
const ndsTrackerQuery = useQuery<any>({
|
||||
queryKey: ["/api/di2/nds/advance-vat-tracker"],
|
||||
queryFn: () => ndsFetch("/api/di2/nds/advance-vat-tracker"),
|
||||
enabled: activeTab === "vat",
|
||||
});
|
||||
|
||||
// Виджет «Контроль квартала»: маркер «деньги vs отгрузка» по договорам
|
||||
const ndsQuarterControlQuery = useQuery<any>({
|
||||
queryKey: ["/api/di2/nds/quarter-control"],
|
||||
queryFn: () => ndsFetch("/api/di2/nds/quarter-control"),
|
||||
enabled: activeTab === "vat",
|
||||
});
|
||||
|
||||
@@ -560,7 +567,8 @@ export default function Calculations() {
|
||||
quarters={ndsQuartersQuery.data ?? []}
|
||||
balance={ndsBalanceQuery.data ?? []}
|
||||
sync={ndsSyncQuery.data ?? null}
|
||||
markers={ndsMarkersQuery.data ?? null}
|
||||
tracker={ndsTrackerQuery.data ?? null}
|
||||
quarterControl={ndsQuarterControlQuery.data ?? null}
|
||||
/>
|
||||
)}
|
||||
{activeTab === "profit" && profitQuery.data && !isLoading && (
|
||||
@@ -605,19 +613,41 @@ function ChartTooltip({ active, payload, label, formatValue }: any) {
|
||||
);
|
||||
}
|
||||
|
||||
// Маркеры «деньги vs отгрузка» (словарь спецификации §2.3)
|
||||
const MARKER_META: Record<string, { label: string; color: string }> = {
|
||||
RED_NO_SHIPMENT: { label: "Деньги есть — отгрузки нет", color: "#e11d48" },
|
||||
YELLOW_PAID_GT_SHIPPED: { label: "Оплачено больше отгруженного", color: "#f59e0b" },
|
||||
BLUE_SHIPPED_GT_PAID: { label: "Отгружено больше оплаченного", color: "#2563eb" },
|
||||
WHITE_ADVANCE_OPEN: { label: "Расчёты сошлись, аванс не зачтён", color: "#94a3b8" },
|
||||
GREEN_OK: { label: "Сошлось", color: "#16a34a" },
|
||||
// Маркеры «деньги vs отгрузка» (словарь спецификации §2.3 + ревью: подписи виджета).
|
||||
// В БД встречаются старые и новые имена — маппим оба.
|
||||
const MARKER_META: Record<string, { label: string; short: string; color: string }> = {
|
||||
GREEN_OK: { label: "Оплачено и отгружено сошлось", short: "Сошлось", color: "#16a34a" },
|
||||
YELLOW_PAID_GT_SHIPPED: { label: "Оплачено больше, чем отгружено (аванс на 62.02)", short: "Аванс 62.02", color: "#f59e0b" },
|
||||
YELLOW_ADVANCE: { label: "Оплачено больше, чем отгружено (аванс на 62.02)", short: "Аванс 62.02", color: "#f59e0b" },
|
||||
BLUE_SHIPPED_GT_PAID: { label: "Отгружено больше, чем оплачено (долг покупателя)", short: "Долг покупателя", color: "#2563eb" },
|
||||
BLUE_DEBT: { label: "Отгружено больше, чем оплачено (долг покупателя)", short: "Долг покупателя", color: "#2563eb" },
|
||||
RED_NO_SHIPMENT: { label: "Оплачено, отгрузок нет", short: "Нет отгрузки", color: "#e11d48" },
|
||||
WHITE_ADVANCE_OPEN: { label: "В документах не выбран договор", short: "Нет договора", color: "#94a3b8" },
|
||||
WHITE_NO_CONTRACT: { label: "В документах не выбран договор", short: "Нет договора", color: "#94a3b8" },
|
||||
};
|
||||
|
||||
function VatTab({ data, quarters, balance, sync, markers }: { data: any; quarters: any[]; balance: any[]; sync: any; markers: any }) {
|
||||
// «от 07.08.26» → «от 07.08.2026» (полный год в строке договора)
|
||||
function formatContractDate(s: string): string {
|
||||
return s.replace(/(\d{2})\.(\d{2})\.(\d{2})(?!\d)/g, "$1.$2.20$3");
|
||||
}
|
||||
|
||||
function VatTab({ data, quarters, balance, sync, tracker, quarterControl }: {
|
||||
data: any; quarters: any[]; balance: any[]; sync: any; tracker: any; quarterControl: any;
|
||||
}) {
|
||||
const [groupBy, setGroupBy] = useState<"month" | "quarter">(
|
||||
() => (localStorage.getItem("omegaprom_vat_groupby") === "quarter" ? "quarter" : "month")
|
||||
);
|
||||
const [showAllContracts, setShowAllContracts] = useState(false);
|
||||
|
||||
const allContractsQuery = useQuery<any>({
|
||||
queryKey: ["/api/di2/nds/quarter-control-all"],
|
||||
queryFn: async () => {
|
||||
const r = await fetch("/api/di2/nds/quarter-control?all=1");
|
||||
if (!r.ok) throw new Error(`${r.status}: ${await r.text() || r.statusText}`);
|
||||
return r.json();
|
||||
},
|
||||
enabled: showAllContracts,
|
||||
});
|
||||
|
||||
function changeGroupBy(v: "month" | "quarter") {
|
||||
setGroupBy(v);
|
||||
@@ -628,6 +658,17 @@ function VatTab({ data, quarters, balance, sync, markers }: { data: any; quarter
|
||||
const chartData = groupBy === "month" ? (data.monthly ?? []) : quarters;
|
||||
const periodLabel = (v: string) => (groupBy === "month" ? formatPeriodLabel(v, "month") : formatQuarterLabel(v));
|
||||
|
||||
// Фоновые полосы по календарным кварталам для помесячного графика
|
||||
const quarterBands = useMemo(() => {
|
||||
const byQ = new Map<string, string[]>();
|
||||
for (const r of data.monthly ?? []) {
|
||||
const q = `${r.period.slice(0, 4)}-Q${Math.floor((Number(r.period.slice(5, 7)) - 1) / 3) + 1}`;
|
||||
if (!byQ.has(q)) byQ.set(q, []);
|
||||
byQ.get(q)!.push(r.period);
|
||||
}
|
||||
return [...byQ.entries()].map(([key, periods]) => ({ key, x1: periods[0], x2: periods[periods.length - 1] }));
|
||||
}, [data.monthly]);
|
||||
|
||||
// Поворот (period, event, vat) в столбцы для stacked-диаграммы — чистое отображение, без расчётов
|
||||
const eventNames = useMemo(() => {
|
||||
const names = new Set<string>();
|
||||
@@ -646,8 +687,12 @@ function VatTab({ data, quarters, balance, sync, markers }: { data: any; quarter
|
||||
const salesByRate = (data.byRate ?? []).filter((r: any) => r.direction === "sales");
|
||||
const purchasesByRate = (data.byRate ?? []).filter((r: any) => r.direction === "purchases");
|
||||
const quarter = data.currentQuarter ?? null;
|
||||
const redRows: any[] = markers?.rows ?? [];
|
||||
const markerCounts: any[] = markers?.byMarker ?? [];
|
||||
const redHanging: any[] = quarterControl?.redHanging ?? [];
|
||||
const redNoAdvance: any[] = quarterControl?.redNoAdvance ?? [];
|
||||
const contractTotals = quarterControl?.totals ?? null;
|
||||
const markerCounts: any[] = quarterControl?.byMarker ?? [];
|
||||
const allContractRows: any[] = allContractsQuery.data?.rows ?? [];
|
||||
const redHangingAdvanceSum = redHanging.reduce((s: number, r: any) => s + (r.advanceBalance || 0), 0);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -733,6 +778,15 @@ function VatTab({ data, quarters, balance, sync, markers }: { data: any; quarter
|
||||
<ResponsiveContainer width="100%" height={350}>
|
||||
<ComposedChart data={chartData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
|
||||
{groupBy === "month" && quarterBands.map((b, i) => (
|
||||
<ReferenceArea
|
||||
key={b.key}
|
||||
x1={b.x1}
|
||||
x2={b.x2}
|
||||
fill="hsl(var(--muted))"
|
||||
fillOpacity={i % 2 === 0 ? 0.15 : 0.05}
|
||||
/>
|
||||
))}
|
||||
<XAxis
|
||||
dataKey="period"
|
||||
tickFormatter={periodLabel}
|
||||
@@ -743,23 +797,75 @@ function VatTab({ data, quarters, balance, sync, markers }: { data: any; quarter
|
||||
<YAxis tick={{ fontSize: 11 }} axisLine={false} tickLine={false} tickFormatter={tickFormatter} />
|
||||
<Tooltip content={(props: any) => <ChartTooltip {...props} label={periodLabel(props.label)} />} />
|
||||
<Legend wrapperStyle={{ fontSize: 11, paddingTop: 8 }} />
|
||||
<Bar dataKey="accrued" name="Начислено" fill={COLORS.income} radius={[3, 3, 0, 0]} />
|
||||
<Bar dataKey="deducted" name="Вычет" fill={COLORS.expense} radius={[3, 3, 0, 0]} />
|
||||
<Line dataKey="toPay" name="К уплате" stroke={COLORS.toPay} strokeWidth={2} dot={{ r: 3 }} />
|
||||
{groupBy === "month" && (
|
||||
<Line dataKey="vatCumulative" name="Накоп. за квартал" stroke={COLORS.cumulative} strokeWidth={2} dot={{ r: 3 }} strokeDasharray="5 5" />
|
||||
{groupBy === "month" ? (
|
||||
<>
|
||||
<Bar dataKey="toPay" name="К уплате за месяц" fill={COLORS.toPay} radius={[3, 3, 0, 0]} />
|
||||
<Bar dataKey="vatCumulative" name="Накоплено за квартал" fill={COLORS.cumulative} radius={[3, 3, 0, 0]} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Bar dataKey="accrued" name="Начислено" fill={COLORS.income} radius={[3, 3, 0, 0]} />
|
||||
<Bar dataKey="deducted" name="Вычет" fill={COLORS.expense} radius={[3, 3, 0, 0]} />
|
||||
<Line dataKey="toPay" name="К уплате" stroke={COLORS.toPay} strokeWidth={2} dot={{ r: 3 }} />
|
||||
</>
|
||||
)}
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
{groupBy === "month" && (
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
Пунктирная линия — накопительный итог внутри календарного квартала. НДС не переносится между кварталами: «к уплате» считается заново каждый квартал
|
||||
Фоновые полосы — календарные кварталы. Синий столбец — НДС к уплате за месяц, бирюзовый — накоплено с начала квартала (на границе квартала сбрасывается; НДС между кварталами не переносится)
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{tracker?.kpis && (
|
||||
<Card>
|
||||
<CardHeader className="pb-2 pt-4 px-4">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||||
Сбавить НДС: запросить авансовые УПД у поставщиков
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-3 text-xs flex-wrap">
|
||||
<span className="text-muted-foreground">Потенциал вычета: <span className="font-semibold text-foreground tabular-nums">{formatCurrency(tracker.kpis.vatToSave)}</span></span>
|
||||
<span className="text-muted-foreground">Ждут УПД: <span className="font-semibold text-foreground">{tracker.kpis.suppliersWaiting}</span></span>
|
||||
<span className="text-muted-foreground">Авансы: <span className="font-semibold text-foreground tabular-nums">{formatCurrency(tracker.kpis.advancesTotal)}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
{(tracker.rows ?? []).length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<div className="min-w-[560px] space-y-1">
|
||||
<div className="grid grid-cols-[1.2fr_1fr_auto_auto] gap-3 text-xs text-muted-foreground pb-1 border-b border-border">
|
||||
<span>Поставщик</span>
|
||||
<span>Договор/счёт</span>
|
||||
<span className="text-right">Аванс выданный</span>
|
||||
<span className="text-right pr-2">Можем сбавить</span>
|
||||
</div>
|
||||
{tracker.rows.map((r: any, i: number) => (
|
||||
<div key={i} className="grid grid-cols-[1.2fr_1fr_auto_auto] gap-3 text-sm py-1 items-baseline">
|
||||
<span className="text-foreground truncate" title={r.supplier}>{r.supplier}</span>
|
||||
<span className="text-muted-foreground truncate" title={r.contract}>{formatContractDate(r.contract || "—")}</span>
|
||||
<span className="tabular-nums text-right text-muted-foreground">{formatCurrency(r.advanceOut)}</span>
|
||||
<span className="tabular-nums text-right font-medium text-foreground pr-2">{formatCurrency(r.potentialVat)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">Все авансовые УПД получены — вычеты с выданных авансов исчерпаны</p>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground mt-3">
|
||||
Оценка сверху по 22/122: реальный вычет = НДС из УПД поставщика (ИП на ставке 5% дадут меньше, поставщики на УСН УПД не выставят).
|
||||
Госпошлина, лизинг, банки — вычета по авансу там не бывает. При поступлении товара вычет с аванса восстанавливается (п. 8 ст. 171 НК) — двойного вычета не будет, экономия кассовая.
|
||||
{tracker.kpis.snapshotDate ? ` Данные на ${tracker.kpis.snapshotDate}.` : ""}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{eventsChart.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader className="pb-2 pt-4 px-4">
|
||||
@@ -785,20 +891,20 @@ function VatTab({ data, quarters, balance, sync, markers }: { data: any; quarter
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{markers && (
|
||||
{quarterControl && (
|
||||
<Card>
|
||||
<CardHeader className="pb-2 pt-4 px-4">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
||||
Контроль квартала: деньги есть — отгрузки нет
|
||||
</CardTitle>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
{markerCounts.map((m: any) => {
|
||||
const meta = MARKER_META[m.marker] ?? { label: m.marker, color: "#94a3b8" };
|
||||
const meta = MARKER_META[m.marker] ?? { label: m.marker, short: m.marker, color: "#94a3b8" };
|
||||
return (
|
||||
<span key={m.marker} className="inline-flex items-center gap-1.5 text-xs text-muted-foreground" title={meta.label}>
|
||||
<span className="w-2 h-2 rounded-full shrink-0" style={{ backgroundColor: meta.color }} />
|
||||
{m.count}
|
||||
{meta.short} · {m.count}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
@@ -806,30 +912,100 @@ function VatTab({ data, quarters, balance, sync, markers }: { data: any; quarter
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
{redRows.length > 0 ? (
|
||||
<div className="space-y-1">
|
||||
<div className="grid grid-cols-[1fr_1fr_auto_auto] gap-3 text-xs text-muted-foreground pb-1 border-b border-border">
|
||||
<span>Контрагент</span>
|
||||
<span>Договор</span>
|
||||
<span className="text-right">Оплачено с начала года</span>
|
||||
<span className="text-right">Аванс на 62.02</span>
|
||||
</div>
|
||||
{redRows.map((r: any, i: number) => (
|
||||
<div key={i} className="grid grid-cols-[1fr_1fr_auto_auto] gap-3 text-sm py-1 items-baseline">
|
||||
<span className="text-foreground">{r.counterparty}</span>
|
||||
<span className="text-muted-foreground truncate" title={r.contract || "Договор в документах не заполнен"}>
|
||||
{r.contract || "— договор не заполнен —"}
|
||||
</span>
|
||||
<span className="tabular-nums text-right font-medium text-foreground">{formatCurrency(r.paid)}</span>
|
||||
<span className="tabular-nums text-right text-muted-foreground">{formatCurrency(r.advanceBalance)}</span>
|
||||
{showAllContracts ? (
|
||||
<div className="space-y-2">
|
||||
{allContractsQuery.isLoading ? (
|
||||
<Skeleton className="h-[200px] rounded-xl" />
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<div className="min-w-[640px] space-y-1">
|
||||
<div className="grid grid-cols-[auto_1.3fr_1fr_auto_auto] gap-3 text-xs text-muted-foreground pb-1 border-b border-border items-center">
|
||||
<span className="w-2" />
|
||||
<span>Контрагент</span>
|
||||
<span>Договор</span>
|
||||
<span className="text-right">Оплачено</span>
|
||||
<span className="text-right pr-2">Аванс 62.02</span>
|
||||
</div>
|
||||
{allContractRows.map((r: any, i: number) => {
|
||||
const meta = MARKER_META[r.marker] ?? { label: r.marker, short: r.marker, color: "#94a3b8" };
|
||||
return (
|
||||
<div key={i} className="grid grid-cols-[auto_1.3fr_1fr_auto_auto] gap-3 text-sm py-1 items-baseline">
|
||||
<span className="w-2 h-2 rounded-full shrink-0" style={{ backgroundColor: meta.color }} title={meta.label} />
|
||||
<span className="text-foreground truncate" title={r.counterparty}>{r.counterparty}</span>
|
||||
<span className="text-muted-foreground truncate" title={r.contract || "Договор в документах не заполнен"}>
|
||||
{r.contract ? formatContractDate(r.contract) : "— договор не заполнен —"}
|
||||
</span>
|
||||
<span className="tabular-nums text-right text-foreground">{formatCurrency(r.paid)}</span>
|
||||
<span className="tabular-nums text-right text-muted-foreground pr-2">{formatCurrency(r.advanceBalance)}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
<Button variant="ghost" size="sm" className="h-6 px-2 text-xs" onClick={() => setShowAllContracts(false)}>
|
||||
Свернуть
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">Крупных необеспеченных авансов (более 1 млн ₽ без отгрузки) нет</p>
|
||||
<div className="space-y-3">
|
||||
{redHanging.length > 0 ? (
|
||||
<div className="overflow-x-auto">
|
||||
<div className="min-w-[640px] space-y-1">
|
||||
<div className="grid grid-cols-[1.3fr_1fr_auto_auto] gap-3 text-xs text-muted-foreground pb-1 border-b border-border">
|
||||
<span>Контрагент</span>
|
||||
<span>Договор</span>
|
||||
<span className="text-right">Оплачено с начала года</span>
|
||||
<span className="text-right pr-2">Аванс на 62.02</span>
|
||||
</div>
|
||||
{redHanging.map((r: any, i: number) => (
|
||||
<div key={i} className="grid grid-cols-[1.3fr_1fr_auto_auto] gap-3 text-sm py-1 items-baseline">
|
||||
<span className="text-foreground truncate" title={r.counterparty}>{r.counterparty}</span>
|
||||
<span className="text-muted-foreground truncate" title={r.contract || "Договор в документах не заполнен"}>
|
||||
{r.contract ? formatContractDate(r.contract) : "— договор не заполнен —"}
|
||||
</span>
|
||||
<span className="tabular-nums text-right font-medium text-foreground">{formatCurrency(r.paid)}</span>
|
||||
<span className="tabular-nums text-right text-muted-foreground pr-2">{formatCurrency(r.advanceBalance)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">Договоров с висячим авансом и без отгрузки нет</p>
|
||||
)}
|
||||
|
||||
{redNoAdvance.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs font-medium text-muted-foreground mb-1">
|
||||
Оплачено, аванс не висячий — проверить зачёт у бухгалтерии ({redNoAdvance.length}):
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-x-4 gap-y-0.5 text-xs text-muted-foreground">
|
||||
{redNoAdvance.map((r: any, i: number) => (
|
||||
<span key={i} className="tabular-nums">{r.counterparty} — {formatCurrency(r.paid)}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between flex-wrap gap-2 pt-1">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Итого по списку: {redHanging.length} договоров, сумма авансов {formatCurrency(redHangingAdvanceSum)}
|
||||
{contractTotals ? ` · всего со сверкой ${contractTotals.totalContracts} договоров` : ""}
|
||||
</p>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs"
|
||||
onClick={() => setShowAllContracts(true)}
|
||||
disabled={allContractsQuery.isLoading && showAllContracts}
|
||||
>
|
||||
Показать все
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-xs text-muted-foreground mt-3">
|
||||
Договоры со сверкой: {markerCounts.reduce((s: number, m: any) => s + m.count, 0)}. НДС с этих авансов уже попал в «к уплате», а выручка и зачёт — в будущем.
|
||||
НДС с этих авансов уже попал в «к уплате», а выручка и зачёт — в будущем.
|
||||
Пустой договор — в платежах/реализациях договор не выбран (самостоятельная проблема учёта).
|
||||
</p>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user