feat: 更新财务系统功能和界面优化
- 优化财务仪表板数据展示 - 增强账户管理功能 - 改进预算和分类管理 - 完善报表和统计分析 - 优化交易管理界面 - 更新Workspace工作区 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,19 @@
|
||||
import * as fs from 'node:fs';
|
||||
|
||||
const INPUT_CSV = '/Users/fuwuqi/Downloads/Telegram Desktop/控天-控天_完全修正.csv';
|
||||
const OUTPUT_CSV = '/Users/fuwuqi/Downloads/Telegram Desktop/控天-控天_完全修正_带分类.csv';
|
||||
const INPUT_CSV =
|
||||
'/Users/fuwuqi/Downloads/Telegram Desktop/控天-控天_完全修正.csv';
|
||||
const OUTPUT_CSV =
|
||||
'/Users/fuwuqi/Downloads/Telegram Desktop/控天-控天_完全修正_带分类.csv';
|
||||
|
||||
// 智能分类函数
|
||||
function getCategory(project: string): string {
|
||||
const desc = project.toLowerCase();
|
||||
|
||||
// 工资
|
||||
if (desc.includes('工资') || desc.match(/amy|天天|碧桂园|皇|香缇卡|财务|客服|小哥|代理ip|sy|超鹏|小白/)) {
|
||||
if (
|
||||
desc.includes('工资') ||
|
||||
/amy|天天|碧桂园|皇|香缇卡|财务|客服|小哥|代理ip|sy|超鹏|小白/.test(desc)
|
||||
) {
|
||||
return '工资';
|
||||
}
|
||||
|
||||
@@ -23,22 +28,34 @@ function getCategory(project: string): string {
|
||||
}
|
||||
|
||||
// 服务器/技术
|
||||
if (desc.match(/服务器|技术|chatgpt|openai|ai|接口|ip|nat|宝塔|cdn|oss|google|翻译|openrouter|deepseek|claude|cursor|bolt|硅基|chatwoot/)) {
|
||||
if (
|
||||
/服务器|技术|chatgpt|openai|ai|接口|ip|nat|宝塔|cdn|oss|google|翻译|openrouter|deepseek|claude|cursor|bolt|硅基|chatwoot/.test(
|
||||
desc,
|
||||
)
|
||||
) {
|
||||
return '服务器/技术';
|
||||
}
|
||||
|
||||
// 广告推广
|
||||
if (desc.match(/广告|推广|地推|投放|打流量/)) {
|
||||
if (/广告|推广|地推|投放|打流量/.test(desc)) {
|
||||
return '广告推广';
|
||||
}
|
||||
|
||||
// 软件/工具
|
||||
if (desc.match(/会员|007|u盘|processon|飞机|虚拟卡|小红卡|信用卡|cloudflare|uizard|esim/)) {
|
||||
if (
|
||||
/会员|007|u盘|processon|飞机|虚拟卡|小红卡|信用卡|cloudflare|uizard|esim/.test(
|
||||
desc,
|
||||
)
|
||||
) {
|
||||
return '软件/工具';
|
||||
}
|
||||
|
||||
// 固定资产
|
||||
if (desc.match(/买车|电脑|笔记本|显示器|rog|硬盘|服务器.*购买|iphone|路由器|展示屏/)) {
|
||||
if (
|
||||
/买车|电脑|笔记本|显示器|rog|硬盘|服务器.*购买|iphone|路由器|展示屏/.test(
|
||||
desc,
|
||||
)
|
||||
) {
|
||||
return '固定资产';
|
||||
}
|
||||
|
||||
@@ -48,7 +65,11 @@ function getCategory(project: string): string {
|
||||
}
|
||||
|
||||
// 借款/转账
|
||||
if (desc.match(/借|转给|龙腾|投资款|换.*铢|换美金|换现金|报销|房租|生活费|办公室|出差|接待|保关|测试|开工红包/)) {
|
||||
if (
|
||||
/借|转给|龙腾|投资款|换.*铢|换美金|换现金|报销|房租|生活费|办公室|出差|接待|保关|测试|开工红包/.test(
|
||||
desc,
|
||||
)
|
||||
) {
|
||||
return '借款/转账';
|
||||
}
|
||||
|
||||
@@ -57,12 +78,12 @@ function getCategory(project: string): string {
|
||||
}
|
||||
|
||||
// 读取并处理CSV
|
||||
const content = fs.readFileSync(INPUT_CSV, 'utf-8');
|
||||
const content = fs.readFileSync(INPUT_CSV, 'utf8');
|
||||
const lines = content.split('\n');
|
||||
|
||||
// 修改表头,添加"分类"列
|
||||
const header = lines[0];
|
||||
const newHeader = header.trimEnd() + ',分类\n';
|
||||
const newHeader = `${header.trimEnd()},分类\n`;
|
||||
|
||||
// 处理每一行数据
|
||||
const newLines = [newHeader];
|
||||
@@ -84,7 +105,7 @@ for (let i = 1; i < lines.length; i++) {
|
||||
const category = getCategory(project);
|
||||
|
||||
// 添加分类列
|
||||
const newLine = line.trimEnd() + ',' + category + '\n';
|
||||
const newLine = `${line.trimEnd()},${category}\n`;
|
||||
newLines.push(newLine);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
const CSV_FILE = '/Users/fuwuqi/Downloads/Telegram Desktop/控天-控天_完全修正_带分类.csv';
|
||||
const CSV_FILE =
|
||||
'/Users/fuwuqi/Downloads/Telegram Desktop/控天-控天_完全修正_带分类.csv';
|
||||
const API_URL = 'http://localhost:3000/api/finance/transactions';
|
||||
|
||||
interface CSVRow {
|
||||
@@ -59,11 +59,7 @@ function parseDate(dateStr: string, previousDate: string = ''): string {
|
||||
const prevMonth = Number.parseInt(previousDate.split('-')[1]);
|
||||
|
||||
// 如果月份从大变小(例如12月->2月,或7月->8月),说明跨年了
|
||||
if (month < prevMonth) {
|
||||
year = prevYear + 1;
|
||||
} else {
|
||||
year = prevYear;
|
||||
}
|
||||
year = month < prevMonth ? prevYear + 1 : prevYear;
|
||||
} else if (month >= 8) {
|
||||
// 第一条记录,8-12月是2024年
|
||||
year = 2024;
|
||||
@@ -85,11 +81,7 @@ function parseDate(dateStr: string, previousDate: string = ''): string {
|
||||
const prevYear = Number.parseInt(previousDate.split('-')[0]);
|
||||
const prevMonth = Number.parseInt(previousDate.split('-')[1]);
|
||||
|
||||
if (month < prevMonth) {
|
||||
year = prevYear + 1;
|
||||
} else {
|
||||
year = prevYear;
|
||||
}
|
||||
year = month < prevMonth ? prevYear + 1 : prevYear;
|
||||
} else if (month >= 8) {
|
||||
year = 2024;
|
||||
} else {
|
||||
@@ -109,12 +101,12 @@ function parseAmount(amountStr: string): number {
|
||||
const cleaned = amountStr.trim();
|
||||
|
||||
// 如果包含乘号(*或×或x),先处理乘法
|
||||
if (cleaned.match(/[*×x]/)) {
|
||||
if (/[*×x]/.test(cleaned)) {
|
||||
// 提取乘法表达式,如 "200*3=600" 或 "200*3"
|
||||
const mulMatch = cleaned.match(/(\d+(?:\.\d+)?)\s*[*×x]\s*(\d+(?:\.\d+)?)/);
|
||||
if (mulMatch) {
|
||||
const num1 = parseFloat(mulMatch[1]);
|
||||
const num2 = parseFloat(mulMatch[2]);
|
||||
const num1 = Number.parseFloat(mulMatch[1]);
|
||||
const num2 = Number.parseFloat(mulMatch[2]);
|
||||
if (!isNaN(num1) && !isNaN(num2)) {
|
||||
return num1 * num2;
|
||||
}
|
||||
@@ -126,7 +118,7 @@ function parseAmount(amountStr: string): number {
|
||||
const parts = cleaned.split('+');
|
||||
let sum = 0;
|
||||
for (const part of parts) {
|
||||
const num = parseFloat(part.replace(/[^\d.]/g, ''));
|
||||
const num = Number.parseFloat(part.replaceAll(/[^\d.]/g, ''));
|
||||
if (!isNaN(num)) {
|
||||
sum += num;
|
||||
}
|
||||
@@ -135,22 +127,22 @@ function parseAmount(amountStr: string): number {
|
||||
}
|
||||
|
||||
// 否则直接解析
|
||||
return parseFloat(cleaned.replace(/[^\d.]/g, '')) || 0;
|
||||
return Number.parseFloat(cleaned.replaceAll(/[^\d.]/g, '')) || 0;
|
||||
}
|
||||
|
||||
// 根据分类名称获取分类ID
|
||||
function getCategoryIdByName(categoryName: string): number {
|
||||
const categoryMap: Record<string, number> = {
|
||||
'工资': 5,
|
||||
工资: 5,
|
||||
'佣金/返佣': 6,
|
||||
'分红': 7,
|
||||
分红: 7,
|
||||
'服务器/技术': 8,
|
||||
'广告推广': 9,
|
||||
广告推广: 9,
|
||||
'软件/工具': 10,
|
||||
'固定资产': 11,
|
||||
'退款': 12,
|
||||
固定资产: 11,
|
||||
退款: 12,
|
||||
'借款/转账': 13,
|
||||
'其他支出': 14,
|
||||
其他支出: 14,
|
||||
};
|
||||
|
||||
return categoryMap[categoryName] || 2; // 默认未分类支出
|
||||
@@ -158,7 +150,7 @@ function getCategoryIdByName(categoryName: string): number {
|
||||
|
||||
// 批量导入
|
||||
async function importTransactions() {
|
||||
const content = fs.readFileSync(CSV_FILE, 'utf-8');
|
||||
const content = fs.readFileSync(CSV_FILE, 'utf8');
|
||||
const rows = parseCSV(content);
|
||||
|
||||
console.log(`共解析到 ${rows.length} 条记录`);
|
||||
@@ -202,14 +194,16 @@ async function importTransactions() {
|
||||
|
||||
if (response.ok) {
|
||||
imported++;
|
||||
console.log(`✓ 导入成功 [${imported}/${rows.length}]: ${row.project} - $${amount}`);
|
||||
console.log(
|
||||
`✓ 导入成功 [${imported}/${rows.length}]: ${row.project} - $${amount}`,
|
||||
);
|
||||
} else {
|
||||
failed++;
|
||||
console.error(`✗ 导入失败: ${row.project}`, await response.text());
|
||||
}
|
||||
|
||||
// 避免请求过快
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
} catch (error) {
|
||||
failed++;
|
||||
console.error(`✗ 处理失败: ${row.project}`, error);
|
||||
|
||||
Reference in New Issue
Block a user