НДС в DI2: дефолтный период — вся история витрины, KPI «К уплате» — за квартал
- summary: без фильтров дат период = весь доступный диапазон nds_monthly (с 2025-01) - «К уплате» теперь всегда квартальный: квартал выбранного периода, если он внутри одного квартала, иначе квартал последнего месяца в данных (НДС не копится между кварталами) - карточки KPI: «Начислено»/«Вычет» за период с подписью, «К уплате» с меткой квартала
This commit is contained in:
@@ -22,37 +22,69 @@ function strParam(v: unknown): string | null {
|
||||
return typeof v === "string" && v.trim() ? v.trim() : null;
|
||||
}
|
||||
|
||||
// Границы периода: из query или текущий календарный квартал (НДС — квартальный налог)
|
||||
async function resolvePeriod(dateFrom: unknown, dateTo: unknown): Promise<{ from: string; to: string }> {
|
||||
// Границы периода: из query или вся история витрины (дефолт).
|
||||
// Отдельно возвращает границы квартала для KPI «К уплате»: квартал явного периода,
|
||||
// если он внутри одного квартала, иначе квартал последнего месяца в данных.
|
||||
async function resolvePeriod(dateFrom: unknown, dateTo: unknown): Promise<{ from: string; to: string; quarter: { from: string; to: string } }> {
|
||||
const fromParam = strParam(dateFrom);
|
||||
const toParam = strParam(dateTo);
|
||||
if (fromParam && toParam) return { from: fromParam, to: toParam };
|
||||
const bounds = await cachedQuery("nds:period-bounds", TTL_NDS, () =>
|
||||
query(`SELECT date_trunc('quarter', CURRENT_DATE)::date AS qstart,
|
||||
(date_trunc('quarter', CURRENT_DATE) + interval '3 months' - interval '1 day')::date AS qend`)
|
||||
const fmt = (d: unknown): string => (typeof d === "string" ? d : new Date(d as any).toISOString().slice(0, 10));
|
||||
const bounds = await cachedQuery("nds:data-bounds", TTL_NDS, () =>
|
||||
query(
|
||||
`SELECT MIN(month)::date AS dfrom,
|
||||
(date_trunc('quarter', MAX(month)) + interval '3 months' - interval '1 day')::date AS dto,
|
||||
date_trunc('quarter', MAX(month))::date AS qfrom,
|
||||
(date_trunc('quarter', MAX(month)) + interval '3 months' - interval '1 day')::date AS qto
|
||||
FROM nds_monthly`
|
||||
)
|
||||
);
|
||||
const row = bounds.rows[0] as any;
|
||||
const fmt = (d: any) => (typeof d === "string" ? d : new Date(d).toISOString().slice(0, 10));
|
||||
return { from: fromParam || fmt(row.qstart), to: toParam || fmt(row.qend) };
|
||||
const quarter = { from: fmt(row.qfrom), to: fmt(row.qto) };
|
||||
if (fromParam && toParam) {
|
||||
const q = await cachedQuery(stableKey("nds:explicit-quarter", { fromParam, toParam }), TTL_NDS, () =>
|
||||
query(
|
||||
`SELECT date_trunc('quarter', $1::date)::date AS qfrom,
|
||||
(date_trunc('quarter', $2::date) + interval '3 months' - interval '1 day')::date AS qto,
|
||||
date_trunc('quarter', $1::date) = date_trunc('quarter', $2::date) AS same`,
|
||||
[fromParam, toParam]
|
||||
)
|
||||
);
|
||||
const qr = q.rows[0] as any;
|
||||
if (qr.same) {
|
||||
quarter.from = fmt(qr.qfrom);
|
||||
quarter.to = fmt(qr.qto);
|
||||
}
|
||||
}
|
||||
return { from: fromParam || fmt(row.dfrom), to: toParam || fmt(row.dto), quarter };
|
||||
}
|
||||
|
||||
export function registerNdsRoutes(app: Express): void {
|
||||
// Сводка за период: KPI, помесячная динамика с накопительным итогом внутри квартала,
|
||||
// разрез по ставкам, структура начислений по типам операций, доля авансов
|
||||
// Сводка за период: помесячная динамика с накопительным итогом внутри квартала,
|
||||
// разрез по ставкам, структура начислений по типам операций, доля авансов.
|
||||
// KPI: начислено/вычет — за период; «к уплате» — за квартал (currentQuarter), НДС не копится между кварталами.
|
||||
app.get("/api/di2/nds/summary", async (req, res) => {
|
||||
try {
|
||||
const { from, to } = await resolvePeriod(req.query.dateFrom, req.query.dateTo);
|
||||
const { from, to, quarter } = await resolvePeriod(req.query.dateFrom, req.query.dateTo);
|
||||
const params = [from, to];
|
||||
const qParams = [quarter.from, quarter.to];
|
||||
|
||||
const [totals, monthly, byRate, events, advances] = await Promise.all([
|
||||
const [totals, quarterTotals, monthly, byRate, events, advances] = await Promise.all([
|
||||
cachedQuery(stableKey("nds:summary:totals", req.query), TTL_NDS, () =>
|
||||
query(
|
||||
`SELECT COALESCE(SUM(vat) FILTER (WHERE direction = 'sales'), 0)::float8 AS accrued,
|
||||
COALESCE(SUM(vat) FILTER (WHERE direction = 'purchases'), 0)::float8 AS deducted
|
||||
FROM nds_monthly WHERE month >= $1::date AND month <= $2::date`,
|
||||
params
|
||||
)
|
||||
),
|
||||
cachedQuery(`nds:summary:quarter:${quarter.from}:${quarter.to}`, TTL_NDS, () =>
|
||||
query(
|
||||
`SELECT COALESCE(SUM(vat) FILTER (WHERE direction = 'sales'), 0)::float8 AS accrued,
|
||||
COALESCE(SUM(vat) FILTER (WHERE direction = 'purchases'), 0)::float8 AS deducted,
|
||||
(COALESCE(SUM(vat) FILTER (WHERE direction = 'sales'), 0)
|
||||
- COALESCE(SUM(vat) FILTER (WHERE direction = 'purchases'), 0))::float8 AS "toPay"
|
||||
FROM nds_monthly WHERE month >= $1::date AND month <= $2::date`,
|
||||
params
|
||||
qParams
|
||||
)
|
||||
),
|
||||
cachedQuery(stableKey("nds:summary:monthly", req.query), TTL_NDS, () =>
|
||||
@@ -110,6 +142,7 @@ export function registerNdsRoutes(app: Express): void {
|
||||
dateFrom: from,
|
||||
dateTo: to,
|
||||
totals: totals.rows[0],
|
||||
currentQuarter: { dateFrom: quarter.from, dateTo: quarter.to, ...quarterTotals.rows[0] },
|
||||
monthly: monthly.rows,
|
||||
byRate: byRate.rows,
|
||||
events: events.rows,
|
||||
|
||||
Reference in New Issue
Block a user