НДС в DI2: вкладка «Расчёты → НДС» переведена на декларационную витрину 1С

- Новый server/finance-di2/nds.routes.ts: GET /api/di2/nds/summary|quarters|input-balance|sync-status|contract-markers
  (только отображение nds_monthly/nds_input_balance/nds_sync_log/nds_contract_marker, без расчёта НДС)
- VatTab переписан: KPI, динамика (месяц с накопительным итогом квартала / квартал), по ставкам,
  stacked по типам операций, доля авансов, резерв вычетов (19 счёт), баннер свежести,
  таблица «деньги есть — отгрузки нет» (RED_NO_SHIPMENT > 1 млн)
- Старый GET /api/calculations/vat (вычисление 20/120 из платежей) удалён
This commit is contained in:
2026-09-29 07:49:23 +03:00
parent c0b8910094
commit de9bbfe17a
4 changed files with 499 additions and 228 deletions

View File

@@ -4465,94 +4465,6 @@ export async function registerRoutes(httpServer: Server, app: Express): Promise<
}
});
app.get("/api/calculations/vat", async (req, res) => {
try {
const { dateFrom, dateTo } = req.query;
const incParams: unknown[] = [];
const expParams: unknown[] = [];
const incConds: string[] = ["1=1"];
const expConds: string[] = ["1=1"];
if (dateFrom) {
incParams.push(dateFrom); incConds.push(`data >= $${incParams.length}`);
expParams.push(dateFrom); expConds.push(`data >= $${expParams.length}`);
}
if (dateTo) {
incParams.push(dateTo); incConds.push(`data <= $${incParams.length}`);
expParams.push(dateTo); expConds.push(`data <= $${expParams.length}`);
}
const incWhere = `WHERE ${incConds.join(" AND ")}`;
const expWhere = `WHERE ${expConds.join(" AND ")}`;
const [incByRate, expByRate, incMonthly, expMonthly] = await Promise.all([
cachedQuery(stableKey("vat-inc-rate", req.query), TTL_SHORT, () =>
query(`SELECT COALESCE(stavkands, 'Без НДС') as rate, COUNT(*) as cnt, COALESCE(SUM(summa), 0) as total FROM ${getIncomeEffectiveView()} ${incWhere} GROUP BY COALESCE(stavkands, 'Без НДС') ORDER BY total DESC`, incParams)
),
cachedQuery(stableKey("vat-exp-rate", req.query), TTL_SHORT, () =>
query(`SELECT COALESCE(stavkands, 'Без НДС') as rate, COUNT(*) as cnt, COALESCE(SUM(summadokumenta), 0) as total FROM ${getExpenseEffectiveView()} ${expWhere} GROUP BY COALESCE(stavkands, 'Без НДС') ORDER BY total DESC`, expParams)
),
cachedQuery(stableKey("vat-inc-monthly", req.query), TTL_SHORT, () =>
query(`SELECT TO_CHAR(data, 'YYYY-MM') as period, COALESCE(stavkands, 'Без НДС') as rate, COALESCE(SUM(summa), 0) as total FROM ${getIncomeEffectiveView()} ${incWhere} GROUP BY TO_CHAR(data, 'YYYY-MM'), COALESCE(stavkands, 'Без НДС') ORDER BY period`, incParams)
),
cachedQuery(stableKey("vat-exp-monthly", req.query), TTL_SHORT, () =>
query(`SELECT TO_CHAR(data, 'YYYY-MM') as period, COALESCE(stavkands, 'Без НДС') as rate, COALESCE(SUM(summadokumenta), 0) as total FROM ${getExpenseEffectiveView()} ${expWhere} GROUP BY TO_CHAR(data, 'YYYY-MM'), COALESCE(stavkands, 'Без НДС') ORDER BY period`, expParams)
),
]);
const parseRate = (r: string): number => {
const m = r.match(/(\d+)/);
return m ? Number(m[1]) / 100 : 0;
};
const incomeByRate = incByRate.rows.map((r: any) => ({
rate: r.rate,
count: Number(r.cnt),
total: Number(r.total),
vat: Number(r.total) * parseRate(r.rate) / (1 + parseRate(r.rate)),
}));
const expenseByRate = expByRate.rows.map((r: any) => ({
rate: r.rate,
count: Number(r.cnt),
total: Number(r.total),
vat: Number(r.total) * parseRate(r.rate) / (1 + parseRate(r.rate)),
}));
const totalIncomeVat = incomeByRate.reduce((s: number, r: any) => s + r.vat, 0);
const totalExpenseVat = expenseByRate.reduce((s: number, r: any) => s + r.vat, 0);
const periodsSet = new Set<string>();
[...incMonthly.rows, ...expMonthly.rows].forEach((r: any) => periodsSet.add(r.period));
const periods = [...periodsSet].sort();
const monthly = periods.map(p => {
let incVat = 0;
let expVat = 0;
incMonthly.rows.filter((r: any) => r.period === p).forEach((r: any) => {
const rate = parseRate(r.rate);
incVat += Number(r.total) * rate / (1 + rate);
});
expMonthly.rows.filter((r: any) => r.period === p).forEach((r: any) => {
const rate = parseRate(r.rate);
expVat += Number(r.total) * rate / (1 + rate);
});
return { period: p, incomeVat: Math.round(incVat * 100) / 100, expenseVat: Math.round(expVat * 100) / 100, toPay: Math.round((incVat - expVat) * 100) / 100 };
});
res.json({
incomeByRate,
expenseByRate,
totalIncomeVat: Math.round(totalIncomeVat * 100) / 100,
totalExpenseVat: Math.round(totalExpenseVat * 100) / 100,
toPay: Math.round((totalIncomeVat - totalExpenseVat) * 100) / 100,
monthly,
});
} catch (err) {
handleApiError(res, err);
}
});
app.post("/api/calculations/profit-tax", async (req, res) => {
try {
await ensureTreesReady();