Files
jiu/client/lib/providers/partner_provider.dart
wangjia 3888cf2d65 feat(client): 慢请求监控(>500ms 记日志+上报);拆除列表 provider 的 _cache 失败兜底
- SlowRequestInterceptor:单次 API 调用超 500ms 即 debugPrint long-request
  日志并经 ErrorReporter 上报(error_type=slow_api,按 method+归一路径 5 分钟
  节流)。服务端 GIN 日志只见自身处理耗时,网络往返段只有客户端可观测。
- 拆除 10 个列表 provider 的 _cache 失败兜底:实测线上接口客户端视角典型
  50-120ms、最坏约 270ms(列表类全部 <500ms),失败静默端旧数据弊大于利
  (曾放大跨账号残留问题),改为明确进入错误态由用户重试。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 00:49:29 +08:00

145 lines
4.5 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/api/api_client.dart';
import '../core/auth/auth_state.dart';
import '../core/config/app_constants.dart';
import '../core/models/page_result.dart';
import '../models/finance.dart';
import '../models/partner.dart';
import '../repositories/partner_repository.dart';
import 'connectivity_provider.dart';
import 'finance_provider.dart';
final partnerRepositoryProvider = Provider<PartnerRepository>((ref) {
return PartnerRepository(ref.watch(apiClientProvider));
});
// Supplier list provider for dropdowns
final supplierListProvider =
AsyncNotifierProvider<PartnerListNotifier, PageResult<Partner>>(
() => PartnerListNotifier(type: 'supplier'),
);
// Customer list provider
final customerListProvider =
AsyncNotifierProvider<PartnerListNotifier, PageResult<Partner>>(
() => PartnerListNotifier(type: 'customer'),
);
/// 工具栏/详细搜索的往来单位下拉专用:不分 supplier/customer、一次取全部。
/// 说明:历史 partner 的 type 多为默认 'supplier'customer 常为空),
/// 按 type 过滤会漏项甚至空列表;下拉按「全部往来单位」呈现最稳。
final allPartnersProvider =
AsyncNotifierProvider<PartnerListNotifier, PageResult<Partner>>(
() => PartnerListNotifier(type: null, pageSize: 1000),
);
/// 往来单位屏专用:单表 + 类型 chips(原型无分页控件,一页取全)。
final partnersScreenProvider =
AsyncNotifierProvider<PartnerListNotifier, PageResult<Partner>>(
() => PartnerListNotifier(type: null, pageSize: 1000),
);
/// 应收/应付余额(原型 recv/pay 两列):/finance/summary 按 partner 聚合的未结清额。
/// 返回 partner_id → (recv 应收, pay 应付)。
final partnerBalancesProvider =
FutureProvider<Map<int, ({double recv, double pay})>>((ref) async {
ref.watch(authStateProvider.select((s) => s.user?.shopId));
final rows = await ref.read(financeRepositoryProvider).partnerSummary();
final map = <int, ({double recv, double pay})>{};
for (final r in rows) {
final id = r.partnerId;
if (id == null) continue;
final cur = map[id] ?? (recv: 0.0, pay: 0.0);
map[id] = r.type == 'receivable'
? (recv: cur.recv + r.totalAmount, pay: cur.pay)
: (recv: cur.recv, pay: cur.pay + r.totalAmount);
}
return map;
});
/// 详情抽屉「近期单据」:该往来单位最近的财务流水(应收/应付/收款/付款,
/// 已覆盖入库、出库与收付款的单据轨迹)。
final partnerRecentDocsProvider =
FutureProvider.family<List<FinanceRecord>, int>((ref, partnerId) async {
final page = await ref
.read(financeRepositoryProvider)
.listRecords(partnerId: partnerId, pageSize: 8);
return page.data;
});
class PartnerListNotifier extends AsyncNotifier<PageResult<Partner>> {
String? type;
int _page = 1;
int _pageSize;
String _keyword = '';
PartnerListNotifier({this.type, int pageSize = AppConstants.defaultPageSize})
: _pageSize = pageSize;
@override
Future<PageResult<Partner>> build() async {
ref.watch(authStateProvider.select((s) => s.user?.shopId));
ref.watch(networkRecoveryCountProvider);
return _fetch();
}
Future<PageResult<Partner>> _fetch() {
return ref.read(partnerRepositoryProvider).list(
type: type,
keyword: _keyword.isEmpty ? null : _keyword,
page: _page,
pageSize: _pageSize,
);
}
void setPage(int page) {
_page = page;
reload();
}
void setPageSize(int pageSize) {
_pageSize = pageSize;
_page = 1;
reload();
}
void setKeyword(String keyword) {
_keyword = keyword;
_page = 1;
reload();
}
/// 类型筛选(往来单位屏 chips):null=全部 / 'supplier' / 'customer'。
/// 后端 FIND_IN_SET 语义:选「供应商」自动含「两者」,与原型一致。
void setType(String? t) {
if (t == type) return;
type = t;
_page = 1;
reload();
}
void reload() {
state = const AsyncValue.loading();
_fetch().then((result) {
state = AsyncValue.data(result);
}, onError: (e, st) {
state = AsyncValue.error(e, st);
});
}
Future<void> createPartner(Map<String, dynamic> data) async {
await ref.read(partnerRepositoryProvider).create(data);
reload();
}
Future<void> updatePartner(int id, Map<String, dynamic> data) async {
await ref.read(partnerRepositoryProvider).update(id, data);
reload();
}
Future<void> deletePartner(int id) async {
await ref.read(partnerRepositoryProvider).delete(id);
reload();
}
}