Files
iistwin/server/finance/table-config.ts
Ильяс Султанов 1f5ecb6da4 fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric,
  чтобы браузер не округлял значения через input type=number
- пробелы при вставке в number-поля удаляются
- бэкенд нормализует значения number-полей в строку перед сохранением
- добавлен хелпер normalizeFieldValueForStorage

Closes: искажение расчётного счёта и других длинных числовых полей
2026-07-07 21:03:40 +03:00

115 lines
3.5 KiB
TypeScript

import { readFileSync, existsSync, writeFileSync } from "fs";
import { resolve } from "path";
export interface TableConfig {
incomeTable: string;
expenseTable: string;
incomeSumColumn: string;
expenseSumColumn: string;
columnLabels: Record<string, string>;
}
export interface IncomeCategoryFile {
id: string;
name: string;
rules: Array<{ column: string; operator: string; value: string }>;
ruleLogic?: "and" | "or";
excluded?: boolean;
autoDetected?: boolean;
treeId?: string;
children: IncomeCategoryFile[];
}
export interface AutoDetectTree {
id: string;
name: string;
columns: string[];
autoRefresh: boolean;
isPrimary: boolean;
mode: "split" | "merge";
filters: Array<{ column: string; operator: string; value: string }>;
filterLogic: "and" | "or";
}
export interface AutoDetectConfig {
trees: AutoDetectTree[];
}
const DATA_DIR = resolve(process.cwd(), "data");
function readJson<T>(filename: string, defaultValue: T): T {
const path = resolve(DATA_DIR, filename);
if (!existsSync(path)) return defaultValue;
try {
return JSON.parse(readFileSync(path, "utf-8")) as T;
} catch {
return defaultValue;
}
}
function writeJson<T>(filename: string, data: T): void {
const path = resolve(DATA_DIR, filename);
writeFileSync(path, JSON.stringify(data, null, 2), "utf-8");
}
export function getTableConfig(): TableConfig {
return readJson<TableConfig>("table-config.json", {
incomeTable: "income",
expenseTable: "expenses",
incomeSumColumn: "summa",
expenseSumColumn: "summadokumenta",
columnLabels: {
data: "Дата",
summa: "Сумма",
summadokumenta: "Сумма документа",
zakazchik: "Заказчик",
otvetstvenniipokontraktu: "Ответственный",
statya: "Статья",
dochka: "Дочка",
vnuchka: "Внучка",
kommentarii: "Комментарий",
stavkands: "Ставка НДС",
dogovorkontragenta: "Договор контрагента",
dogovorzakazchika: "Договор заказчика",
naznachenieplatezha: "Назначение платежа",
schetnaoplatu: "Счет на оплату",
statyadop: "Статья (доп.)",
nomervhodyaschegodokumenta: "Номер вх. документа",
kontragent: "Контрагент",
ssilkanomervhodyaschegodokumenta: "Номер вх. документа",
},
});
}
export function getIncomeCategories(): IncomeCategoryFile[] {
return readJson<IncomeCategoryFile[]>("income-categories.json", []);
}
export function setIncomeCategories(categories: IncomeCategoryFile[]): void {
writeJson("income-categories.json", categories);
}
export function getExpenseCategories(): IncomeCategoryFile[] {
return readJson<IncomeCategoryFile[]>("expense-categories.json", []);
}
export function setExpenseCategories(categories: IncomeCategoryFile[]): void {
writeJson("expense-categories.json", categories);
}
export function getIncomeAutoDetectConfig(): AutoDetectConfig {
return readJson<AutoDetectConfig>("income-auto-detect-config.json", { trees: [] });
}
export function setIncomeAutoDetectConfig(config: AutoDetectConfig): void {
writeJson("income-auto-detect-config.json", config);
}
export function getExpenseAutoDetectConfig(): AutoDetectConfig {
return readJson<AutoDetectConfig>("auto-detect-config.json", { trees: [] });
}
export function setExpenseAutoDetectConfig(config: AutoDetectConfig): void {
writeJson("auto-detect-config.json", config);
}