- Копия DI2-сервера в server/finance-di2/ с правками: schema/db-client/cache/table-config/override-tables/google-auth/audit-agent/routes - db-client — обёртка над существующим пулом server/finance/db-client (второй пул не создаётся, добавлен экспорт isConnectionError) - ai-config.ts — чистое IO конфигов из ai-agent.ts без Telegram-поллинга; роуты /api/ai/toggle и /api/ai/status удалены, без compression/startAuditScheduler/autoStartIfEnabled - Обёртка registerDi2Routes с auth-gate (authenticateToken + finance.manage), регистрация строго перед registerFinanceRoutes - Статика /di2 из dist/public-di2 перед веткой vite/static (dev и prod) - Фикс предсуществующего бага DI2: buildGlobalExclusionConditions без cats в /api/profitability
367 lines
12 KiB
TypeScript
367 lines
12 KiB
TypeScript
import { z } from "zod";
|
|
|
|
export const dbConnectionSchema = z.object({
|
|
host: z.string().min(1),
|
|
port: z.coerce.number().int().positive(),
|
|
database: z.string().min(1),
|
|
user: z.string().min(1),
|
|
password: z.string().min(1),
|
|
});
|
|
|
|
export type DbConnectionConfig = z.infer<typeof dbConnectionSchema>;
|
|
|
|
export interface TableConfig {
|
|
incomeTable: string;
|
|
expenseTable: string;
|
|
debtTable: string;
|
|
incomeSumColumn: string;
|
|
expenseSumColumn: string;
|
|
incomeColumns: string[];
|
|
expenseColumns: string[];
|
|
columnLabels: Record<string, string>;
|
|
}
|
|
|
|
export interface IncomeRow {
|
|
id: string;
|
|
data: string | null;
|
|
summa: number | null;
|
|
stavkands: string | null;
|
|
dogovorkontragenta: string | null;
|
|
zakazchik: string | null;
|
|
dogovorzakazchika: string | null;
|
|
otvetstvenniipokontraktu: string | null;
|
|
statyadop: string | null;
|
|
kommentarii: string | null;
|
|
statya: string | null;
|
|
dochka: string | null;
|
|
vnuchka: string | null;
|
|
naznachenieplatezha: string | null;
|
|
schetnaoplatu: string | null;
|
|
overrideCategoryId?: string;
|
|
overrideCategoryName?: string;
|
|
}
|
|
|
|
export interface ExpenseRow {
|
|
id: string;
|
|
data: string | null;
|
|
summadokumenta: number | null;
|
|
stavkands: string | null;
|
|
dogovorkontragenta: string | null;
|
|
zakazchik: string | null;
|
|
dogovorzakazchika: string | null;
|
|
otvetstvenniipokontraktu: string | null;
|
|
statyadop: string | null;
|
|
kommentarii: string | null;
|
|
statya: string | null;
|
|
dochka: string | null;
|
|
vnuchka: string | null;
|
|
naznachenieplatezha: string | null;
|
|
ssilkanomervhodyaschegodokumenta: string | null;
|
|
kontragent: string | null;
|
|
dataobnovleniyazapisi: string | null;
|
|
overrideCategoryId?: string;
|
|
overrideCategoryName?: string;
|
|
}
|
|
|
|
export interface DebtRow {
|
|
period: string | null;
|
|
schet: string | null;
|
|
kontragent: string | null;
|
|
dogovor: string | null;
|
|
dokumentraschetov: string | null;
|
|
organizaciya: string | null;
|
|
summadolga: number | null;
|
|
summadolgaval: number | null;
|
|
valyuta: string | null;
|
|
otsrochkapodogovoru: number | null;
|
|
kontragentinn: string | null;
|
|
dataobrazovaniyadolga: string | null;
|
|
summadokumenta: number | null;
|
|
planovayadatapogasheniya: string | null;
|
|
dneiprosrocheno: number | null;
|
|
dneidolga: number | null;
|
|
otvetstvennii: string | null;
|
|
klassprosrochki: string | null;
|
|
klassdolga: string | null;
|
|
summaprosrocheno: number | null;
|
|
summaoplacheno: number | null;
|
|
}
|
|
|
|
export interface DashboardStats {
|
|
incomeTotal: number;
|
|
incomeCount: number;
|
|
expenseTotal: number;
|
|
expenseCount: number;
|
|
debtTotal: number;
|
|
debtOverdue: number;
|
|
debtCounterparts: number;
|
|
monthlyIncome: { month: string; total: number }[];
|
|
monthlyExpense: { month: string; total: number }[];
|
|
debtByClass: { class: string; total: number }[];
|
|
billedVsPaid: { month: string; billed: number; paid: number; debt: number }[];
|
|
}
|
|
|
|
export interface FilterOptions {
|
|
statya: string[];
|
|
zakazchik: string[];
|
|
dochka: string[];
|
|
vnuchka: string[];
|
|
otvetstvennii?: string[];
|
|
organizaciya?: string[];
|
|
klassprosrochki?: string[];
|
|
}
|
|
|
|
export interface PaginatedResult<T> {
|
|
rows: T[];
|
|
total: number;
|
|
page: number;
|
|
pageSize: number;
|
|
totalPages: number;
|
|
totalSum?: number;
|
|
}
|
|
|
|
export type RuleOperator = "equals" | "not_equals" | "contains" | "greater_than" | "less_than" | "equal_number";
|
|
|
|
export interface IncomeCategoryRule {
|
|
column: string;
|
|
operator: RuleOperator;
|
|
value: string;
|
|
}
|
|
|
|
export const MAX_CATEGORY_DEPTH = 5;
|
|
|
|
export interface IncomeCategory {
|
|
id: string;
|
|
name: string;
|
|
rules: IncomeCategoryRule[];
|
|
ruleLogic?: "and" | "or";
|
|
excluded?: boolean;
|
|
children?: IncomeCategory[];
|
|
autoDetected?: boolean;
|
|
treeId?: string;
|
|
}
|
|
|
|
export const NUMERIC_OPERATORS: { value: RuleOperator; label: string }[] = [
|
|
{ value: "equal_number", label: "равно (=)" },
|
|
{ value: "greater_than", label: "больше (>)" },
|
|
{ value: "less_than", label: "меньше (<)" },
|
|
];
|
|
|
|
export const TEXT_OPERATORS: { value: RuleOperator; label: string }[] = [
|
|
{ value: "equals", label: "равно (=)" },
|
|
{ value: "not_equals", label: "не равно (≠)" },
|
|
{ value: "contains", label: "содержит (∋)" },
|
|
];
|
|
|
|
export interface ProfitabilityRow {
|
|
zakazchik: string;
|
|
totalIncome: number;
|
|
totalExpense: number;
|
|
profit: number;
|
|
margin: number;
|
|
contractCount: number;
|
|
}
|
|
|
|
export interface ContractProfitRow {
|
|
dogovorzakazchika: string;
|
|
totalIncome: number;
|
|
totalExpense: number;
|
|
profit: number;
|
|
}
|
|
|
|
export interface MonthlyProfitRow {
|
|
month: string;
|
|
income: number;
|
|
expense: number;
|
|
profit: number;
|
|
}
|
|
|
|
export interface ProfitabilitySummary {
|
|
totalIncome: number;
|
|
totalExpense: number;
|
|
totalProfit: number;
|
|
counterpartCount: number;
|
|
}
|
|
|
|
export interface ProfitabilityResponse {
|
|
rows: ProfitabilityRow[];
|
|
total: number;
|
|
page: number;
|
|
pageSize: number;
|
|
totalPages: number;
|
|
summary: ProfitabilitySummary;
|
|
}
|
|
|
|
export interface DebtByEntity {
|
|
name: string;
|
|
total: number;
|
|
cls0: number;
|
|
cls1_30: number;
|
|
cls31_60: number;
|
|
cls61_90: number;
|
|
cls91_365: number;
|
|
cls365plus: number;
|
|
}
|
|
|
|
export interface DebtOverdueClass {
|
|
cls: string;
|
|
total: number;
|
|
}
|
|
|
|
export interface DebtAnalytics {
|
|
summary: {
|
|
updatedAt: string | null;
|
|
totalDebt: number;
|
|
totalOverdue: number;
|
|
};
|
|
byKontragent: DebtByEntity[];
|
|
byManager: DebtByEntity[];
|
|
byOverdueClass: DebtOverdueClass[];
|
|
table: {
|
|
rows: DebtTableGroup[];
|
|
total: number;
|
|
page: number;
|
|
pageSize: number;
|
|
totalPages: number;
|
|
};
|
|
}
|
|
|
|
export interface DebtTableRow {
|
|
dogovor: string;
|
|
summadokumenta: number;
|
|
dokumentraschetov: string;
|
|
summaoplacheno: number;
|
|
summadolga: number;
|
|
summaprosrocheno: number;
|
|
otsrochkapodogovoru: number;
|
|
dneiprosrocheno: number;
|
|
}
|
|
|
|
export interface DebtTableGroup {
|
|
kontragent: string;
|
|
totalDebt: number;
|
|
totalOverdue: number;
|
|
rows: DebtTableRow[];
|
|
}
|
|
|
|
export interface DebtDynamicsRow {
|
|
period: string;
|
|
totalDebt: number;
|
|
totalOverdue: number;
|
|
}
|
|
|
|
export interface DebtDynamicsKontragent {
|
|
kontragent: string;
|
|
summadokumenta: number;
|
|
summaoplacheno: number;
|
|
summadolga: number;
|
|
summaprosrocheno: number;
|
|
dneiprosrocheno: number;
|
|
}
|
|
|
|
export interface DebtFiltersResponse {
|
|
periods: string[];
|
|
klassprosrochki: string[];
|
|
organizaciya: string[];
|
|
otvetstvennii: string[];
|
|
kontragenty: string[];
|
|
}
|
|
|
|
export const INCOME_FILTERABLE_COLUMNS = [
|
|
{ key: "data", label: "Дата" },
|
|
{ key: "summa", label: "Сумма" },
|
|
{ key: "statya", label: "Статья" },
|
|
{ key: "zakazchik", label: "Заказчик" },
|
|
{ key: "dochka", label: "Дочка" },
|
|
{ key: "vnuchka", label: "Внучка" },
|
|
{ key: "otvetstvenniipokontraktu", label: "Ответственный" },
|
|
{ key: "stavkands", label: "Ставка НДС" },
|
|
{ key: "kommentarii", label: "Комментарий" },
|
|
{ key: "dogovorkontragenta", label: "Договор контрагента" },
|
|
{ key: "schetnaoplatu", label: "Счет на оплату" },
|
|
{ key: "naznachenieplatezha", label: "Назначение платежа" },
|
|
{ key: "dogovorzakazchika", label: "Договор заказчика" },
|
|
{ key: "statyadop", label: "Статья (доп.)" },
|
|
{ key: "nomervhodyaschegodokumenta", label: "Номер вх. документа" },
|
|
] as const;
|
|
|
|
export const ALL_INCOME_COLUMNS: { key: string; label: string; type: "date" | "number" | "text" }[] = [
|
|
{ key: "data", label: "Дата", type: "date" },
|
|
{ key: "summa", label: "Сумма", type: "number" },
|
|
{ key: "statya", label: "Статья", type: "text" },
|
|
{ key: "zakazchik", label: "Заказчик", type: "text" },
|
|
{ key: "dochka", label: "Дочка", type: "text" },
|
|
{ key: "vnuchka", label: "Внучка", type: "text" },
|
|
{ key: "otvetstvenniipokontraktu", label: "Ответственный", type: "text" },
|
|
{ key: "stavkands", label: "Ставка НДС", type: "text" },
|
|
{ key: "kommentarii", label: "Комментарий", type: "text" },
|
|
{ key: "dogovorkontragenta", label: "Договор контрагента", type: "text" },
|
|
{ key: "schetnaoplatu", label: "Счет на оплату", type: "text" },
|
|
{ key: "naznachenieplatezha", label: "Назначение платежа", type: "text" },
|
|
{ key: "dogovorzakazchika", label: "Договор заказчика", type: "text" },
|
|
{ key: "statyadop", label: "Статья (доп.)", type: "text" },
|
|
{ key: "nomervhodyaschegodokumenta", label: "Номер вх. документа", type: "text" },
|
|
{ key: "id", label: "ID", type: "text" },
|
|
];
|
|
|
|
export const DEFAULT_VISIBLE_COLUMNS = ["data", "summa", "statya", "zakazchik", "dochka", "vnuchka", "kommentarii"];
|
|
|
|
export const ALL_EXPENSE_COLUMNS: { key: string; label: string; type: "date" | "number" | "text" }[] = [
|
|
{ key: "data", label: "Дата", type: "date" },
|
|
{ key: "summadokumenta", label: "Сумма документа", type: "number" },
|
|
{ key: "statya", label: "Статья", type: "text" },
|
|
{ key: "zakazchik", label: "Заказчик", type: "text" },
|
|
{ key: "dochka", label: "Дочка", type: "text" },
|
|
{ key: "vnuchka", label: "Внучка", type: "text" },
|
|
{ key: "kommentarii", label: "Комментарий", type: "text" },
|
|
{ key: "stavkands", label: "Ставка НДС", type: "text" },
|
|
{ key: "naznachenieplatezha", label: "Назначение платежа", type: "text" },
|
|
{ key: "otvetstvenniipokontraktu", label: "Ответственный", type: "text" },
|
|
{ key: "dogovorkontragenta", label: "Договор контрагента", type: "text" },
|
|
{ key: "dogovorzakazchika", label: "Договор заказчика", type: "text" },
|
|
{ key: "statyadop", label: "Статья (доп.)", type: "text" },
|
|
{ key: "ssilkanomervhodyaschegodokumenta", label: "Номер вх. документа", type: "text" },
|
|
{ key: "kontragent", label: "Контрагент", type: "text" },
|
|
];
|
|
|
|
export const EXPENSE_FILTERABLE_COLUMNS = [
|
|
{ key: "data", label: "Дата" },
|
|
{ key: "summadokumenta", label: "Сумма документа" },
|
|
{ key: "statya", label: "Статья" },
|
|
{ key: "zakazchik", label: "Заказчик" },
|
|
{ key: "dochka", label: "Дочка" },
|
|
{ key: "vnuchka", label: "Внучка" },
|
|
{ key: "kommentarii", label: "Комментарий" },
|
|
{ key: "stavkands", label: "Ставка НДС" },
|
|
{ key: "naznachenieplatezha", label: "Назначение платежа" },
|
|
{ key: "otvetstvenniipokontraktu", label: "Ответственный" },
|
|
{ key: "dogovorkontragenta", label: "Договор контрагента" },
|
|
{ key: "dogovorzakazchika", label: "Договор заказчика" },
|
|
{ key: "statyadop", label: "Статья (доп.)" },
|
|
{ key: "ssilkanomervhodyaschegodokumenta", label: "Номер вх. документа" },
|
|
{ key: "kontragent", label: "Контрагент" },
|
|
] as const;
|
|
|
|
export const DEFAULT_VISIBLE_EXPENSE_COLUMNS = ["data", "summadokumenta", "statya", "zakazchik", "dochka", "vnuchka", "kommentarii"];
|
|
|
|
export const CATEGORY_RULE_COLUMNS = [
|
|
...INCOME_FILTERABLE_COLUMNS.map(c => ({ ...c, type: "text" as const })),
|
|
{ key: "summa", label: "Сумма", type: "number" as const },
|
|
];
|
|
|
|
export const EXPENSE_CATEGORY_RULE_COLUMNS = [
|
|
...EXPENSE_FILTERABLE_COLUMNS.filter(c => c.key !== "summadokumenta").map(c => ({ ...c, type: "text" as const })),
|
|
{ key: "summadokumenta", label: "Сумма документа", type: "number" as const },
|
|
];
|
|
|
|
export interface ExpenseCategory {
|
|
id: string;
|
|
name: string;
|
|
rules: IncomeCategoryRule[];
|
|
ruleLogic?: "and" | "or";
|
|
excluded?: boolean;
|
|
children?: ExpenseCategory[];
|
|
autoDetected?: boolean;
|
|
treeId?: string;
|
|
}
|