Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/contributing/ISOLATED_APP_CLOUD_QA_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

流程 skill 随项目维护在 `.agents/skills/isolated-app-cloud-qa`;Codex 与 Claude Code 项目安装见 [项目 skill](PROJECT_SKILLS_ZH.md)。不再需要 honeycomb 或全局安装。新 run 自动记录 skill 仓库 SHA、完整目录 hash 与版本,并保存私有源码副本;可用 `--skill-dir` 指定另一份源码。旧 `--skill-repo` 仅保留兼容,已存在 run 的 manifest 和数据不会迁移。

用于需要真实 iOS App UI、真实 Cloud 数据持久化和双向同步的验收。当前支持首页复制交易、父子分类改名同步、Web 交易图片和 MCP 小票附件四种场景。单元/widget 测试与 fake-provider 同步测试仍是快速回归层,实服同步另行执行。
用于需要真实 iOS App UI、真实 Cloud 数据持久化和双向同步的验收。当前支持首页复制交易、父子分类改名同步、Web 交易图片、MCP 小票附件和账户金额可信度五种场景。单元/widget 测试与 fake-provider 同步测试仍是快速回归层,实服同步另行执行。

## 环境要求

Expand Down Expand Up @@ -38,6 +38,8 @@ MCP 小票使用 `prepare --scenario mcp-receipt-attachments --cloud-ref <Cloud

分类场景的正常入口检查断言主页合成交易可见,并读取 QA 数据库核对父分类名称、子分类稳定身份与本地 `parent_id` 关系。两种场景共用隔离措施,执行结果与截图仍只进入独立报告包。

账户金额可信度使用 `prepare --scenario account-balance-trust --cloud-ref <Cloud 功能提交 SHA>`。真实 App 页面创建欠款卡与溢缴卡、消费、超额还款、再次消费、检查普通账户全部流水及导入外币 CSV;上传后等待 `QA_STAGE_READY_WEB_ACCOUNT_TRUST`。在同一 QA 网页把「QA信用卡」改名为「QA Web信用卡」,并用正数 5000 创建欠款卡「QA Web欠款卡」,再由 App 拉回,核对账户身份、初始余额、交易关系、币种与重复同步。Web 首页另核验账户全局余额与当前账本交易排行。正常入口重启核验导入交易可见及最终账户字段持久化。

Cloud 默认取 `origin/main`;需要联调独立修复分支时,通过 `--cloud-ref <分支或 SHA>` 指定已提交的版本。manifest 记录解析后的实际 SHA,Cloud 当前 checkout 不切换。

命令打印本次私有 run 路径、新模拟器 UDID 和本地 Cloud origin。将打印的实际路径赋值给 `qa_run_dir`,不要引用旧 run:
Expand Down
7 changes: 7 additions & 0 deletions docs/design/DESIGN_TOKENS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ Container(
)
```

同页的单选视图/流水筛选统一使用 `widgets/ui/ui.dart` 导出的
`CapsuleSwitcher<T>`:灰色 `surfaceCapsule` 轨道,日间默认黑底白字选中,
暗色模式使用主题色选中胶囊;选中文字由 `textOnColor` 根据背景明暗决定。
各页面直接使用组件默认配色。调用方持有选中值和业务筛选;
不另用默认 Material `ChoiceChip` 拼装 tabs。选项能完整放下时等宽,
窄屏或大字号下横向滚动;组件提供选中语义并遵循系统减少动画设置。

---

## 1. 背景色 Token (Surface)
Expand Down
492 changes: 492 additions & 0 deletions integration_test/account_balance_trust_live_test.dart

Large diffs are not rendered by default.

22 changes: 14 additions & 8 deletions lib/data/repositories/local/local_account_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,9 @@ class LocalAccountRepository implements AccountRepository {
type: type != null ? d.Value(type) : const d.Value.absent(),
currency: currency != null ? d.Value(currency) : const d.Value.absent(),
initialBalance: initialBalance != null ? d.Value(initialBalance) : const d.Value.absent(),
creditLimit: clearCreditCardFields ? const d.Value(null) : (creditLimit != null ? d.Value(creditLimit) : const d.Value.absent()),
billingDay: clearCreditCardFields ? const d.Value(null) : (billingDay != null ? d.Value(billingDay) : const d.Value.absent()),
paymentDueDay: clearCreditCardFields ? const d.Value(null) : (paymentDueDay != null ? d.Value(paymentDueDay) : const d.Value.absent()),
creditLimit: clearCreditCardFields ? d.Value(creditLimit) : (creditLimit != null ? d.Value(creditLimit) : const d.Value.absent()),
billingDay: clearCreditCardFields ? d.Value(billingDay) : (billingDay != null ? d.Value(billingDay) : const d.Value.absent()),
paymentDueDay: clearCreditCardFields ? d.Value(paymentDueDay) : (paymentDueDay != null ? d.Value(paymentDueDay) : const d.Value.absent()),
bankName: clearMetadataFields ? const d.Value(null) : (bankName != null ? d.Value(bankName) : const d.Value.absent()),
cardLastFour: clearMetadataFields ? const d.Value(null) : (cardLastFour != null ? d.Value(cardLastFour) : const d.Value.absent()),
note: clearMetadataFields ? const d.Value(null) : (note != null ? d.Value(note) : const d.Value.absent()),
Expand Down Expand Up @@ -810,7 +810,7 @@ class LocalAccountRepository implements AccountRepository {

for (final account in accounts) {
final balance = await getAccountBalance(account.id);
if (isAssetType(account.type)) {
if (isAssetBalance(account.type, balance)) {
totalAssets += balance;
} else {
totalLiabilities += balance;
Expand All @@ -834,7 +834,7 @@ class LocalAccountRepository implements AccountRepository {
final currency = account.currency.toUpperCase();
final prev = result[currency] ?? (totalAssets: 0.0, totalLiabilities: 0.0, netWorth: 0.0);

if (isAssetType(account.type)) {
if (isAssetBalance(account.type, balance)) {
result[currency] = (
totalAssets: prev.totalAssets + balance,
totalLiabilities: prev.totalLiabilities,
Expand Down Expand Up @@ -921,7 +921,7 @@ class LocalAccountRepository implements AccountRepository {
final rate = ratesToBase[account.currency.toUpperCase()];
if (rate == null) continue;
final bal = balances[dayIndex].balance * rate;
if (isAssetType(account.type)) {
if (isAssetBalance(account.type, bal)) {
assets += bal;
} else {
liabilities += bal;
Expand All @@ -942,7 +942,10 @@ class LocalAccountRepository implements AccountRepository {

for (final account in accounts) {
final balance = await getAccountBalance(account.id);
typeBalances.update(account.type, (v) => v + balance, ifAbsent: () => balance);
final type = positiveAssetCompositionType(account.type, balance) ??
(isAssetType(account.type) && balance < 0 ? '_negative_assets_' : null);
if (type == null) continue;
typeBalances.update(type, (v) => v + balance, ifAbsent: () => balance);
}

return typeBalances.entries
Expand All @@ -959,7 +962,10 @@ class LocalAccountRepository implements AccountRepository {

for (final account in accounts) {
final balance = await getAccountBalance(account.id);
final key = (type: account.type, currency: account.currency.toUpperCase());
final type = positiveAssetCompositionType(account.type, balance) ??
(isAssetType(account.type) && balance < 0 ? '_negative_assets_' : null);
if (type == null) continue;
final key = (type: type, currency: account.currency.toUpperCase());
balances.update(key, (v) => v + balance, ifAbsent: () => balance);
}

Expand Down
20 changes: 17 additions & 3 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -3033,8 +3033,8 @@
"billingDay": "Billing Day",
"paymentDueDay": "Payment Due Day",
"creditUsed": "Used",
"creditAvailable": "Available",
"creditCardOwed": "Owed",
"creditAvailable": "Est. available",
"creditCardOwed": "Current debt",
"dayOfMonth": "{day}th of each month",
"@dayOfMonth": {
"placeholders": {
Expand Down Expand Up @@ -3877,5 +3877,19 @@
"placeholders": {
"count": { "type": "int" }
}
}
},
"creditCardOverpayment": "Overpayment",
"creditCardDebtHint": "Enter your total outstanding debt as a positive amount; enter 0 if none.",
"creditCardOverpaymentHint": "Enter the positive amount remaining on the card after overpaying.",
"creditCardAmountInvalid": "Enter a valid amount; credit-card debt or overpayment must be non-negative.",
"creditCardReminderDueRequired": "Set a payment due day before enabling reminders.",
"creditCardEstimateHint": "Estimated from recorded transactions; check your bank for the actual available amount.",
"accountAllFlows": "All transactions",
"accountOutflows": "Expenses & transfers out",
"accountInflows": "Income & transfers in",
"positiveAssetBalance": "Positive asset balances",
"negativeAssetBalanceHint": "Negative account balances are deducted from total assets.",
"importAccountCurrencyPreview": "Confirm account currencies",
"importAccountCurrencyPreviewHint": "Accounts use statement currencies. Conflicting currencies are split without changing existing accounts. Missing currencies follow an existing account, an unambiguous account currency, or the ledger currency.",
"importCurrencyInvalid": "Invalid currency; correct this row:"
}
88 changes: 86 additions & 2 deletions lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13175,13 +13175,13 @@ abstract class AppLocalizations {
/// No description provided for @creditAvailable.
///
/// In en, this message translates to:
/// **'Available'**
/// **'Est. available'**
String get creditAvailable;

/// No description provided for @creditCardOwed.
///
/// In en, this message translates to:
/// **'Owed'**
/// **'Current debt'**
String get creditCardOwed;

/// No description provided for @dayOfMonth.
Expand Down Expand Up @@ -16189,6 +16189,90 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Log entries: {count}'**
String logExportCount(int count);

/// No description provided for @creditCardOverpayment.
///
/// In en, this message translates to:
/// **'Overpayment'**
String get creditCardOverpayment;

/// No description provided for @creditCardDebtHint.
///
/// In en, this message translates to:
/// **'Enter your total outstanding debt as a positive amount; enter 0 if none.'**
String get creditCardDebtHint;

/// No description provided for @creditCardOverpaymentHint.
///
/// In en, this message translates to:
/// **'Enter the positive amount remaining on the card after overpaying.'**
String get creditCardOverpaymentHint;

/// No description provided for @creditCardAmountInvalid.
///
/// In en, this message translates to:
/// **'Enter a valid amount; credit-card debt or overpayment must be non-negative.'**
String get creditCardAmountInvalid;

/// No description provided for @creditCardReminderDueRequired.
///
/// In en, this message translates to:
/// **'Set a payment due day before enabling reminders.'**
String get creditCardReminderDueRequired;

/// No description provided for @creditCardEstimateHint.
///
/// In en, this message translates to:
/// **'Estimated from recorded transactions; check your bank for the actual available amount.'**
String get creditCardEstimateHint;

/// No description provided for @accountAllFlows.
///
/// In en, this message translates to:
/// **'All transactions'**
String get accountAllFlows;

/// No description provided for @accountOutflows.
///
/// In en, this message translates to:
/// **'Expenses & transfers out'**
String get accountOutflows;

/// No description provided for @accountInflows.
///
/// In en, this message translates to:
/// **'Income & transfers in'**
String get accountInflows;

/// No description provided for @positiveAssetBalance.
///
/// In en, this message translates to:
/// **'Positive asset balances'**
String get positiveAssetBalance;

/// No description provided for @negativeAssetBalanceHint.
///
/// In en, this message translates to:
/// **'Negative account balances are deducted from total assets.'**
String get negativeAssetBalanceHint;

/// No description provided for @importAccountCurrencyPreview.
///
/// In en, this message translates to:
/// **'Confirm account currencies'**
String get importAccountCurrencyPreview;

/// No description provided for @importAccountCurrencyPreviewHint.
///
/// In en, this message translates to:
/// **'Accounts use statement currencies. Conflicting currencies are split without changing existing accounts. Missing currencies follow an existing account, an unambiguous account currency, or the ledger currency.'**
String get importAccountCurrencyPreviewHint;

/// No description provided for @importCurrencyInvalid.
///
/// In en, this message translates to:
/// **'Invalid currency; correct this row:'**
String get importCurrencyInvalid;
}

class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
Expand Down
46 changes: 44 additions & 2 deletions lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6918,10 +6918,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get creditUsed => 'Used';

@override
String get creditAvailable => 'Available';
String get creditAvailable => 'Est. available';

@override
String get creditCardOwed => 'Owed';
String get creditCardOwed => 'Current debt';

@override
String dayOfMonth(int day) {
Expand Down Expand Up @@ -8615,4 +8615,46 @@ class AppLocalizationsEn extends AppLocalizations {
String logExportCount(int count) {
return 'Log entries: $count';
}

@override
String get creditCardOverpayment => 'Overpayment';

@override
String get creditCardDebtHint => 'Enter your total outstanding debt as a positive amount; enter 0 if none.';

@override
String get creditCardOverpaymentHint => 'Enter the positive amount remaining on the card after overpaying.';

@override
String get creditCardAmountInvalid => 'Enter a valid amount; credit-card debt or overpayment must be non-negative.';

@override
String get creditCardReminderDueRequired => 'Set a payment due day before enabling reminders.';

@override
String get creditCardEstimateHint => 'Estimated from recorded transactions; check your bank for the actual available amount.';

@override
String get accountAllFlows => 'All transactions';

@override
String get accountOutflows => 'Expenses & transfers out';

@override
String get accountInflows => 'Income & transfers in';

@override
String get positiveAssetBalance => 'Positive asset balances';

@override
String get negativeAssetBalanceHint => 'Negative account balances are deducted from total assets.';

@override
String get importAccountCurrencyPreview => 'Confirm account currencies';

@override
String get importAccountCurrencyPreviewHint => 'Accounts use statement currencies. Conflicting currencies are split without changing existing accounts. Missing currencies follow an existing account, an unambiguous account currency, or the ledger currency.';

@override
String get importCurrencyInvalid => 'Invalid currency; correct this row:';
}
42 changes: 42 additions & 0 deletions lib/l10n/app_localizations_ko.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8615,4 +8615,46 @@ class AppLocalizationsKo extends AppLocalizations {
String logExportCount(int count) {
return 'Log entries: $count';
}

@override
String get creditCardOverpayment => 'Overpayment';

@override
String get creditCardDebtHint => 'Enter your total outstanding debt as a positive amount; enter 0 if none.';

@override
String get creditCardOverpaymentHint => 'Enter the positive amount remaining on the card after overpaying.';

@override
String get creditCardAmountInvalid => 'Enter a valid amount; credit-card debt or overpayment must be non-negative.';

@override
String get creditCardReminderDueRequired => 'Set a payment due day before enabling reminders.';

@override
String get creditCardEstimateHint => 'Estimated from recorded transactions; check your bank for the actual available amount.';

@override
String get accountAllFlows => 'All transactions';

@override
String get accountOutflows => 'Expenses & transfers out';

@override
String get accountInflows => 'Income & transfers in';

@override
String get positiveAssetBalance => 'Positive asset balances';

@override
String get negativeAssetBalanceHint => 'Negative account balances are deducted from total assets.';

@override
String get importAccountCurrencyPreview => 'Confirm account currencies';

@override
String get importAccountCurrencyPreviewHint => 'Accounts use statement currencies. Conflicting currencies are split without changing existing accounts. Missing currencies follow an existing account, an unambiguous account currency, or the ledger currency.';

@override
String get importCurrencyInvalid => 'Invalid currency; correct this row:';
}
Loading
Loading