Files
jiu/client/lib/providers/partner_provider.dart
T
wangjia 6238b86dcb feat(client): 登录/注册页照原型重建,ds 真相源组件族统一全部屏
- 登录/注册(login.html/register.html 1:1):两栏卡片+品牌渐变面板+主题小衣服
  pill(onSurface 变体)+记住我(记录并预填最近账号)+原型式 toast 校验;
  登录 fidelity 1.4–2.1% 三主题全绿;注册暂不入闸(少 门店编号/兑换券 字段,
  已记 CONTRACT,screens.mjs 留存根)
- ds 原子补齐:DsToast(.toast 单例)/DsCheck(.check/.agree)/DsButton lg 档/
  DsSelect 替换全部旧 DropdownButton/DsField label 在上/DsInput 后缀与密码形态
- 全屏统一:对话框按钮全 DsButton、盒式输入主题钉死(visualDensity.standard、
  h38、InputDecorationTheme 渗漏修复)、图标全 lucide、JetBrains Mono 三端同源、
  BrandMark 真相源 logo、只读模式写操作全量守卫(WriteGuard+DsToast)
- 出入库列表:版式对齐原型(卡片对齐+搜索框居中)、KPI 近30天滚动、结清后
  失效财务应收应付表;商品编辑抽屉介绍库改搜索下拉、图片双击全屏预览
- 删除旧 UI 死代码:DataTableCard/FormDialog/PageScaffold/SearchChip/
  SelectProductDialog/tabStateProvider
- golden/fidelity:stock-in/out 补注册(9%)、login 入册(8%)、goldens 全量重打;
  修复 pubspec flutter_web_plugins 非法声明(CI pub get 阻断)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
2026-07-03 09:58:14 +08:00

158 lines
4.8 KiB
Dart
Raw 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 = '';
PageResult<Partner>? _cache;
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);
try {
final result = await _fetch();
_cache = result;
return result;
} catch (_) {
if (_cache != null) return _cache!;
rethrow;
}
}
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) {
_cache = result;
state = AsyncValue.data(result);
}, onError: (e, st) {
if (_cache != null) {
state = AsyncValue.data(_cache!);
} else {
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();
}
}