diff --git a/.gitignore b/.gitignore index e4c10f5..57c57b3 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ outputs/ # MiMoCode .mimocode/ +.agent/ diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..a4341f8 --- /dev/null +++ b/App.tsx @@ -0,0 +1,46 @@ +import { useMemo, useState } from 'react'; +import { Pressable, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, View } from 'react-native'; +import { StatusBar } from 'expo-status-bar'; +import { classify, commitMobileTransaction, importStatement, parseLedger, type ImportedEvent, type Rule, validateTransaction } from './src/domain'; + +const sampleLedger = `option "operating_currency" "CNY" +2026-01-01 open Assets:Alipay CNY +2026-01-01 open Assets:WeChat CNY +2026-01-01 open Expenses:Food CNY +2026-01-01 open Expenses:Uncategorized CNY +2026-01-01 open Income:Uncategorized CNY +include "mobile.bean" +`; +const sampleStatement = '交易时间,金额(元),收/支,交易对方,商品说明,交易订单号\n2026-07-01,24.50,支出,咖啡店,冰美式,ORDER-1\n2026-07-02,5000,收入,公司,工资,ORDER-2'; +const rules: Rule[] = [{ id: 'coffee', priority: 100, channel: 'Alipay', counterpartyContains: '咖啡', channelAccount: 'Assets:Alipay', categoryAccount: 'Expenses:Food', narration: '咖啡', tags: ['food'], hits: 0 }]; +type Tab = '账本' | '记账' | '导入' | '规则' | '诊断'; + +export default function App() { + const ledger = useMemo(() => parseLedger([{ path: 'main.bean', content: sampleLedger }]), []); + const [tab, setTab] = useState('账本'); const [mobileBean, setMobileBean] = useState(''); const [events, setEvents] = useState([]); + const [message, setMessage] = useState('账本已解析。手机交易只会写入 mobile.bean。'); + const [amount, setAmount] = useState(''); const [account, setAccount] = useState('Expenses:Food'); + const addManual = () => { + const draft = { date: '2026-07-10', narration: '手工记账', postings: [{ account: 'Assets:Alipay', amount: `-${amount}`, currency: 'CNY' }, { account, amount, currency: 'CNY' }] }; + const result = validateTransaction(draft, ledger); if (!result.valid) return setMessage(result.errors.join(';')); + try { const commit = commitMobileTransaction(draft, ledger, mobileBean); setMobileBean(commit.content); setAmount(''); setMessage('已追加到 mobile.bean。'); } catch (error) { setMessage(String(error)); } + }; + const loadDemo = () => { setEvents(importStatement(sampleStatement, 'alipay-csv-v1')); setMessage('已导入 2 条账单;请逐条确认。'); }; + const confirm = (event: ImportedEvent) => { + const candidate = classify(event, rules, ledger); try { const commit = commitMobileTransaction(candidate.draft, ledger, mobileBean); setMobileBean(commit.content); setEvents(current => current.filter(item => item.id !== event.id)); setMessage(candidate.ruleId ? '已按规则确认并写入 mobile.bean。' : '未匹配规则,已确认未分类分录。'); } catch (error) { setMessage(String(error)); } + }; + return + Bean Mobile{message} + {(['账本', '记账', '导入', '规则', '诊断'] as Tab[]).map(item => setTab(item)} style={[styles.tab, tab === item && styles.activeTab]}>{item})} + + {tab === '账本' && <>{ledger.accounts.size} 个已开户账户 · {ledger.transactions.length} 条桌面交易主文件只读;已配置 include "mobile.bean"。{mobileBean || '尚无手机端交易'}} + {tab === '记账' &&