166 lines
5.8 KiB
TypeScript
166 lines
5.8 KiB
TypeScript
// 向测试环境补充标签与自定义规则演示数据。
|
|
import axios from 'axios'
|
|
import { login } from './browser.js'
|
|
import { API_URL } from './config.js'
|
|
|
|
/** 需要补充的标签:名称与颜色 */
|
|
const TAGS: Array<{ name: string; color: string }> = [
|
|
{ name: '咖啡', color: '#8B5A2B' },
|
|
{ name: '外卖', color: '#F97316' },
|
|
]
|
|
|
|
/**
|
|
* 创建标签并返回其主键。
|
|
*
|
|
* @param token 访问令牌
|
|
* @param name 标签名称
|
|
* @param color 标签颜色
|
|
* @returns 标签主键
|
|
*/
|
|
async function createTag(token: string, name: string, color: string): Promise<number> {
|
|
const headers = { Authorization: `Bearer ${token}` }
|
|
const res = await axios.post(`${API_URL}/tag`, { name, color }, { headers })
|
|
console.log(`标签 ${name} -> ${res.data.code}`)
|
|
// 创建接口不返回主键,统一从列表中按名称查找
|
|
const list = await axios.get(`${API_URL}/tag/list`, { headers })
|
|
const found = (list.data.data as Array<{ id: number; name: string }>).find((t) => t.name === name)
|
|
if (!found) throw new Error(`未找到标签: ${name}`)
|
|
return found.id
|
|
}
|
|
|
|
/**
|
|
* 创建自定义归类规则。
|
|
*
|
|
* @param token 访问令牌
|
|
* @param payload 规则请求体
|
|
*/
|
|
async function createRule(token: string, payload: Record<string, unknown>): Promise<void> {
|
|
const res = await axios.post(`${API_URL}/rule`, payload, { headers: { Authorization: `Bearer ${token}` } })
|
|
console.log(`规则 ${payload.name} -> ${res.data.code}`)
|
|
}
|
|
|
|
/**
|
|
* 按名称查询分类主键。
|
|
*
|
|
* @param token 访问令牌
|
|
* @param name 分类名称
|
|
* @returns 分类主键
|
|
*/
|
|
async function getCategoryId(token: string, name: string): Promise<number> {
|
|
const headers = { Authorization: `Bearer ${token}` }
|
|
const res = await axios.get(`${API_URL}/category/list`, { headers })
|
|
const found = (res.data.data as Array<{ id: number; name: string }>).find((c) => c.name === name)
|
|
if (!found) throw new Error(`未找到分类: ${name}`)
|
|
return found.id
|
|
}
|
|
|
|
/**
|
|
* 按名称查询标签主键,不存在时创建。
|
|
*
|
|
* @param token 访问令牌
|
|
* @param name 标签名称
|
|
* @param color 标签颜色
|
|
* @returns 标签主键
|
|
*/
|
|
async function getTagId(token: string, name: string, color: string): Promise<number> {
|
|
const headers = { Authorization: `Bearer ${token}` }
|
|
const res = await axios.get(`${API_URL}/tag/list`, { headers })
|
|
const found = (res.data.data as Array<{ id: number; name: string }>).find((t) => t.name === name)
|
|
if (found) return found.id
|
|
return createTag(token, name, color)
|
|
}
|
|
|
|
/** 交易记录(用于给若干记录打标签,供「AI 生成规则」截图使用) */
|
|
interface TransactionRow {
|
|
id: number
|
|
categoryId: number | null
|
|
type: string
|
|
amount: number
|
|
channel: string | null
|
|
remark: string | null
|
|
transactionTime: string
|
|
includeInStats: boolean
|
|
}
|
|
|
|
/**
|
|
* 给若干记录打上标签,便于「AI 生成规则」按已打标签记录归纳。
|
|
*
|
|
* @param token 访问令牌
|
|
* @param coffeeId 咖啡标签主键
|
|
* @param takeoutId 外卖标签主键
|
|
* @param bigAmountId 大额消费标签主键
|
|
*/
|
|
async function tagSomeTransactions(
|
|
token: string,
|
|
coffeeId: number,
|
|
takeoutId: number,
|
|
bigAmountId: number,
|
|
): Promise<void> {
|
|
const headers = { Authorization: `Bearer ${token}` }
|
|
const res = await axios.get(`${API_URL}/transaction/page`, { headers, params: { page: 1, size: 50 } })
|
|
const rows = (res.data.data?.records ?? []) as TransactionRow[]
|
|
let tagged = 0
|
|
for (const row of rows) {
|
|
if (tagged >= 6) break
|
|
const remark = row.remark ?? ''
|
|
let tagIds: number[] = []
|
|
if (remark.includes('星巴克') || remark.includes('瑞幸') || remark.includes('咖啡')) {
|
|
tagIds = [coffeeId]
|
|
} else if (remark.includes('美团') || remark.includes('外卖') || remark.includes('饿了么')) {
|
|
tagIds = [takeoutId]
|
|
} else if (Number(row.amount) >= 500) {
|
|
tagIds = [bigAmountId]
|
|
}
|
|
if (tagIds.length === 0) continue
|
|
await axios.put(`${API_URL}/transaction/${row.id}`, {
|
|
categoryId: row.categoryId,
|
|
type: row.type,
|
|
amount: row.amount,
|
|
channel: row.channel,
|
|
remark: row.remark,
|
|
transactionTime: row.transactionTime,
|
|
includeInStats: row.includeInStats,
|
|
tagIds,
|
|
}, { headers })
|
|
tagged++
|
|
}
|
|
console.log(`已为 ${tagged} 条记录打标签`)
|
|
}
|
|
|
|
/** 清空现有规则,避免重复创建 */
|
|
async function clearRules(token: string): Promise<void> {
|
|
const headers = { Authorization: `Bearer ${token}` }
|
|
const res = await axios.get(`${API_URL}/rule/list`, { headers })
|
|
for (const rule of res.data.data as Array<{ id: number }>) {
|
|
await axios.delete(`${API_URL}/rule/${rule.id}`, { headers })
|
|
}
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const token = await login()
|
|
await clearRules(token)
|
|
const coffeeId = await getTagId(token, '咖啡', '#8B5A2B')
|
|
const takeoutId = await getTagId(token, '外卖', '#F97316')
|
|
const bigAmountId = await getTagId(token, '大额消费', '#F97316')
|
|
const diningId = await getCategoryId(token, '餐饮')
|
|
const transportId = await getCategoryId(token, '交通')
|
|
console.log('标签:', { 咖啡: coffeeId, 外卖: takeoutId, 大额消费: bigAmountId })
|
|
|
|
const rules: Array<Record<string, unknown>> = [
|
|
{ name: '星巴克归入餐饮', enabled: true, type: 'EXPENSE', keyword: '星巴克', categoryId: diningId, tagIds: [coffeeId] },
|
|
{ name: '滴滴出行归入交通', enabled: true, type: 'EXPENSE', keyword: '滴滴', categoryId: transportId },
|
|
{ name: '大额支出标记', enabled: true, type: 'EXPENSE', minAmount: 500, tagIds: [bigAmountId] },
|
|
]
|
|
for (const rule of rules) {
|
|
await createRule(token, rule)
|
|
}
|
|
|
|
// 给若干记录打标签,供「AI 生成规则」截图
|
|
await tagSomeTransactions(token, coffeeId, takeoutId, bigAmountId)
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('FAIL', err)
|
|
process.exit(1)
|
|
})
|