НДС в DI2: линии начислено/вычет на графике, таблицы с прокруткой

- Динамика НДС (месяц): поверх столбцов «к уплате за месяц»/«накоплено за квартал»
  добавлены линии «Начислено» и «Вычет»
- Трекер авансовых УПД: без лимита 15 (все ждущие), настоящая table с фиксированными
  колонками (table-fixed, truncate+title), видимы первые ~5 строк, дальше скролл
  внутри блока, шапка прилипает (sticky)
- Контроль квартала: главная таблица и «Показать все» — то же оформление table + скролл
This commit is contained in:
2026-09-29 12:02:58 +03:00
parent 6ef0621d63
commit c56ec26b49
2 changed files with 77 additions and 62 deletions

View File

@@ -801,6 +801,8 @@ function VatTab({ data, quarters, balance, sync, tracker, quarterControl }: {
<>
<Bar dataKey="toPay" name="К уплате за месяц" fill={COLORS.toPay} radius={[3, 3, 0, 0]} />
<Bar dataKey="vatCumulative" name="Накоплено за квартал" fill={COLORS.cumulative} radius={[3, 3, 0, 0]} />
<Line dataKey="accrued" name="Начислено" stroke={COLORS.income} strokeWidth={2} dot={{ r: 2 }} />
<Line dataKey="deducted" name="Вычет" stroke={COLORS.expense} strokeWidth={2} dot={{ r: 2 }} />
</>
) : (
<>
@@ -836,23 +838,27 @@ function VatTab({ data, quarters, balance, sync, tracker, quarterControl }: {
</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 className="max-h-[264px] overflow-y-auto overflow-x-auto">
<table className="w-full table-fixed text-sm">
<thead className="sticky top-0 z-10 bg-background">
<tr className="text-xs text-muted-foreground border-b border-border">
<th className="text-left font-normal pb-1 w-[30%]">Поставщик</th>
<th className="text-left font-normal pb-1 w-[30%]">Договор/счёт</th>
<th className="text-right font-normal pb-1 w-[20%]">Аванс выданный</th>
<th className="text-right font-normal pb-1 w-[20%] pr-2">Можем сбавить</th>
</tr>
</thead>
<tbody>
{tracker.rows.map((r: any, i: number) => (
<tr key={i} className="border-b border-border/50">
<td className="py-1.5 pr-3 truncate text-foreground" title={r.supplier}>{r.supplier}</td>
<td className="py-1.5 pr-3 truncate text-muted-foreground" title={r.contract || undefined}>{formatContractDate(r.contract || "—")}</td>
<td className="py-1.5 text-right tabular-nums text-muted-foreground">{formatCurrency(r.advanceOut)}</td>
<td className="py-1.5 text-right tabular-nums font-medium text-foreground pr-2">{formatCurrency(r.potentialVat)}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<p className="text-sm text-muted-foreground">Все авансовые УПД получены — вычеты с выданных авансов исчерпаны</p>
@@ -917,30 +923,36 @@ function VatTab({ data, quarters, balance, sync, tracker, quarterControl }: {
{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 className="max-h-[320px] overflow-y-auto overflow-x-auto">
<table className="w-full table-fixed text-sm">
<thead className="sticky top-0 z-10 bg-background">
<tr className="text-xs text-muted-foreground border-b border-border">
<th className="text-left font-normal pb-1 w-5" />
<th className="text-left font-normal pb-1 w-[30%]">Контрагент</th>
<th className="text-left font-normal pb-1 w-[28%]">Договор</th>
<th className="text-right font-normal pb-1 w-[20%]">Оплачено</th>
<th className="text-right font-normal pb-1 w-[19%] pr-2">Аванс 62.02</th>
</tr>
</thead>
<tbody>
{allContractRows.map((r: any, i: number) => {
const meta = MARKER_META[r.marker] ?? { label: r.marker, short: r.marker, color: "#94a3b8" };
return (
<tr key={i} className="border-b border-border/50">
<td className="py-1.5 pr-2">
<span className="block w-2 h-2 rounded-full shrink-0" style={{ backgroundColor: meta.color }} title={meta.label} />
</td>
<td className="py-1.5 pr-3 truncate text-foreground" title={r.counterparty}>{r.counterparty}</td>
<td className="py-1.5 pr-3 truncate text-muted-foreground" title={r.contract || "Договор в документах не заполнен"}>
{r.contract ? formatContractDate(r.contract) : "— договор не заполнен —"}
</td>
<td className="py-1.5 text-right tabular-nums text-foreground">{formatCurrency(r.paid)}</td>
<td className="py-1.5 text-right tabular-nums text-muted-foreground pr-2">{formatCurrency(r.advanceBalance)}</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
<Button variant="ghost" size="sm" className="h-6 px-2 text-xs" onClick={() => setShowAllContracts(false)}>
@@ -950,25 +962,29 @@ function VatTab({ data, quarters, balance, sync, tracker, quarterControl }: {
) : (
<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 className="max-h-[264px] overflow-y-auto overflow-x-auto">
<table className="w-full table-fixed text-sm">
<thead className="sticky top-0 z-10 bg-background">
<tr className="text-xs text-muted-foreground border-b border-border">
<th className="text-left font-normal pb-1 w-[30%]">Контрагент</th>
<th className="text-left font-normal pb-1 w-[30%]">Договор</th>
<th className="text-right font-normal pb-1 w-[20%]">Оплачено с начала года</th>
<th className="text-right font-normal pb-1 w-[20%] pr-2">Аванс на 62.02</th>
</tr>
</thead>
<tbody>
{redHanging.map((r: any, i: number) => (
<tr key={i} className="border-b border-border/50">
<td className="py-1.5 pr-3 truncate text-foreground" title={r.counterparty}>{r.counterparty}</td>
<td className="py-1.5 pr-3 truncate text-muted-foreground" title={r.contract || "Договор в документах не заполнен"}>
{r.contract ? formatContractDate(r.contract) : "— договор не заполнен —"}
</td>
<td className="py-1.5 text-right tabular-nums font-medium text-foreground">{formatCurrency(r.paid)}</td>
<td className="py-1.5 text-right tabular-nums text-muted-foreground pr-2">{formatCurrency(r.advanceBalance)}</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<p className="text-sm text-muted-foreground">Договоров с висячим авансом и без отгрузки нет</p>

View File

@@ -255,8 +255,7 @@ export function registerNdsRoutes(app: Express): void {
FROM nds_advance_vat_tracker
WHERE snapshot_date = (SELECT MAX(snapshot_date) FROM nds_advance_vat_tracker)
AND status = 'НЕТ АВАНСОВОЙ УПД'
ORDER BY potential_vat DESC
LIMIT 15`
ORDER BY potential_vat DESC`
)
),
]);