expense_api.dart 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  1. import 'dart:convert';
  2. import 'dart:typed_data';
  3. import 'package:dio/dio.dart';
  4. import 'package:flutter_riverpod/flutter_riverpod.dart';
  5. import '../../core/network/api_client.dart';
  6. import '../../core/network/api_response.dart';
  7. import '../../app.dart';
  8. import '../../shared/models/pagination_model.dart';
  9. import '../../shared/models/bill_attachment.dart';
  10. import '../../shared/models/bill_file_rights.dart';
  11. import '../../core/navigation/host_app_channel.dart';
  12. import '../../core/utils/date_utils.dart' as du;
  13. import '../../core/data/api_cache.dart';
  14. import 'expense_model.dart';
  15. import 'expense_list_controller.dart';
  16. import '../expense_apply/report_model.dart';
  17. final expenseApiProvider = Provider<ExpenseApi>(
  18. (ref) => ExpenseApi(ref.read(apiClientProvider)),
  19. );
  20. // ═══ 参考数据模型(API 返回) ═══
  21. class CostTypeItem {
  22. final String typeNo;
  23. final String typeName;
  24. final String accNo;
  25. final String accName;
  26. const CostTypeItem({
  27. required this.typeNo,
  28. required this.typeName,
  29. required this.accNo,
  30. required this.accName,
  31. });
  32. factory CostTypeItem.fromJson(Map<String, dynamic> json) => CostTypeItem(
  33. typeNo: json['typeNo'] as String? ?? '',
  34. typeName: json['typeName'] as String? ?? '',
  35. accNo: json['accNo'] as String? ?? '',
  36. accName: json['accName'] as String? ?? '',
  37. );
  38. }
  39. class CostProjectItem {
  40. final String idx1;
  41. final String name;
  42. final String accNo;
  43. final String accName;
  44. const CostProjectItem({
  45. required this.idx1,
  46. required this.name,
  47. required this.accNo,
  48. required this.accName,
  49. });
  50. factory CostProjectItem.fromJson(Map<String, dynamic> json) =>
  51. CostProjectItem(
  52. idx1: json['idx1'] as String? ?? '',
  53. name: json['name'] as String? ?? '',
  54. accNo: json['accNo'] as String? ?? '',
  55. accName: json['accName'] as String? ?? '',
  56. );
  57. }
  58. class ProjectCodeItem {
  59. final String objNo;
  60. final String name;
  61. const ProjectCodeItem({required this.objNo, required this.name});
  62. factory ProjectCodeItem.fromJson(Map<String, dynamic> json) =>
  63. ProjectCodeItem(
  64. objNo: json['objNo'] as String? ?? '',
  65. name: json['name'] as String? ?? '',
  66. );
  67. }
  68. class DepartmentItem {
  69. final String dep;
  70. final String name;
  71. const DepartmentItem({required this.dep, required this.name});
  72. factory DepartmentItem.fromJson(Map<String, dynamic> json) => DepartmentItem(
  73. dep: json['dep'] as String? ?? '',
  74. name: json['name'] as String? ?? '',
  75. );
  76. }
  77. class CustomerItem {
  78. final String cusNo;
  79. final String name;
  80. const CustomerItem({required this.cusNo, required this.name});
  81. factory CustomerItem.fromJson(Map<String, dynamic> json) => CustomerItem(
  82. cusNo: json['cusNo'] as String? ?? '',
  83. name: json['name'] as String? ?? '',
  84. );
  85. }
  86. class CurrencyItem {
  87. final String curId;
  88. final String name;
  89. const CurrencyItem({required this.curId, required this.name});
  90. factory CurrencyItem.fromJson(Map<String, dynamic> json) => CurrencyItem(
  91. curId: json['curId'] as String? ?? '',
  92. name: json['name'] as String? ?? '',
  93. );
  94. }
  95. class EmployeeItem {
  96. final String salNo;
  97. final String name;
  98. final String dep;
  99. final String tel;
  100. final String email;
  101. final String bnkNo;
  102. final String bnkId;
  103. final String accName;
  104. const EmployeeItem({
  105. required this.salNo,
  106. required this.name,
  107. this.dep = '',
  108. this.tel = '',
  109. this.email = '',
  110. this.bnkNo = '',
  111. this.bnkId = '',
  112. this.accName = '',
  113. });
  114. factory EmployeeItem.fromJson(Map<String, dynamic> json) => EmployeeItem(
  115. salNo: json['salNo'] as String? ?? '',
  116. name: json['name'] as String? ?? '',
  117. dep: json['dep'] as String? ?? '',
  118. tel: json['tel'] as String? ?? '',
  119. email: json['email'] as String? ?? '',
  120. bnkNo: json['bnkNo'] as String? ?? '',
  121. bnkId: json['bnkId'] as String? ?? '',
  122. accName: json['accName'] as String? ?? '',
  123. );
  124. }
  125. class ExpenseApi {
  126. final ApiClient _client;
  127. final _cache = ApiCache(keyPrefix: HostAppChannel.sn);
  128. ExpenseApi(this._client);
  129. /// 清除所有基础资料缓存(picker 下拉刷新时调用)。
  130. void clearRefCache() => _cache.clear();
  131. /// 费用报销列表(分页)
  132. Future<PaginatedData<ExpenseModel>> fetchList({
  133. String status = '',
  134. String keyword = '',
  135. String startDate = '',
  136. String endDate = '',
  137. String usr = '',
  138. String sortDir = 'DESC',
  139. int page = 1,
  140. int size = 20,
  141. }) async {
  142. final response = await _client.get<Map<String, dynamic>>(
  143. '/OA/GetExpenseList',
  144. queryParameters: {
  145. 'status': status,
  146. 'keyword': keyword,
  147. 'startDate': startDate,
  148. 'endDate': endDate,
  149. 'usr': usr,
  150. 'sortDir': sortDir,
  151. 'page': page,
  152. 'size': size,
  153. },
  154. );
  155. return PaginatedData.fromJson(response.data!, ExpenseModel.fromJson);
  156. }
  157. /// 费用报销详情(主表+明细)
  158. Future<ExpenseModel> fetchDetail(String billNo) async {
  159. final response = await _client.get<Map<String, dynamic>>(
  160. '/OA/GetExpenseDetail',
  161. queryParameters: {'billNo': billNo},
  162. );
  163. return ExpenseModel.fromJson(response.data!);
  164. }
  165. /// 提交审批,返回报销单号(提取失败时返回 null,不影响主流程)
  166. /// BillSave 返回格式: { callok:true, resultData:{ BIL_NO:"BX20267020005", ... } }
  167. Future<String?> submit(Map<String, dynamic> data) async {
  168. final response = await _client.post<Map<String, dynamic>>(
  169. '/OA/BillSave',
  170. data: {
  171. 'erpCategory': 'MasterService',
  172. 'billId': 'BX',
  173. 'procId': '',
  174. 'data': data,
  175. },
  176. );
  177. final resData = response.data;
  178. if (resData == null) return null;
  179. final resultData = resData['resultData'];
  180. if (resultData is Map<String, dynamic>) {
  181. final bilNo = resultData['BIL_NO'] as String?;
  182. if (bilNo != null && bilNo.isNotEmpty) return bilNo;
  183. }
  184. if (resultData is String && resultData.isNotEmpty) {
  185. try {
  186. final parsed = json.decode(resultData) as Map<String, dynamic>;
  187. final bilNo = parsed['BIL_NO'] as String?;
  188. if (bilNo != null && bilNo.isNotEmpty) return bilNo;
  189. } catch (_) {}
  190. }
  191. final rootBilNo = resData['BIL_NO'] as String?;
  192. if (rootBilNo != null && rootBilNo.isNotEmpty) return rootBilNo;
  193. return null;
  194. }
  195. /// 下载附件文件字节
  196. Future<Uint8List?> downloadAttachment(String id) async {
  197. return await _client.downloadFile(
  198. '/OA/DownloadAttachment',
  199. queryParameters: {'id': id},
  200. );
  201. }
  202. /// 检测附件服务是否可用
  203. Future<bool> checkAttachHealth() async {
  204. try {
  205. final response = await _client.get<Map<String, dynamic>>(
  206. '/OA/CheckAttachHealth',
  207. );
  208. return response.data?['available'] as bool? ?? false;
  209. } catch (_) {
  210. return false;
  211. }
  212. }
  213. /// 财务核销
  214. Future<void> verify(Map<String, dynamic> data) async {
  215. await _client.post('/OA/ExpenseVerify', data: data);
  216. }
  217. /// 费用类别字典
  218. Future<List<CostTypeItem>> getCostTypes({
  219. String keyword = '',
  220. String accNo = '',
  221. int page = 1,
  222. int size = 100,
  223. }) async {
  224. final cacheKey = 'getCostTypes_${keyword}_${accNo}_$page';
  225. return _cache.getOrFetch(cacheKey, () async {
  226. final response = await _client.get<Map<String, dynamic>>(
  227. '/OA/GetCostTypes',
  228. queryParameters: {
  229. 'keyword': keyword,
  230. 'accNo': accNo,
  231. 'page': page,
  232. 'size': size,
  233. },
  234. );
  235. final list = (response.data?['list'] as List<dynamic>?) ?? [];
  236. return list
  237. .map((e) => CostTypeItem.fromJson(e as Map<String, dynamic>))
  238. .toList();
  239. });
  240. }
  241. /// 费用项目字典(分页)
  242. Future<List<CostProjectItem>> getCostProjects({
  243. String keyword = '',
  244. String accNo = '',
  245. bool onlyActive = true,
  246. int page = 1,
  247. int size = 20,
  248. }) async {
  249. final cacheKey = 'getCostProjects_${keyword}_${accNo}_${onlyActive}_$page';
  250. return _cache.getOrFetch(cacheKey, () async {
  251. final response = await _client.get<Map<String, dynamic>>(
  252. '/OA/GetCostProjects',
  253. queryParameters: {
  254. 'keyword': keyword,
  255. 'accNo': accNo,
  256. 'onlyActive': onlyActive,
  257. 'page': page,
  258. 'size': size,
  259. },
  260. );
  261. final list = (response.data?['list'] as List<dynamic>?) ?? [];
  262. return list
  263. .map((e) => CostProjectItem.fromJson(e as Map<String, dynamic>))
  264. .toList();
  265. });
  266. }
  267. /// 项目代号
  268. Future<List<ProjectCodeItem>> getProjectCodes({
  269. String keyword = '',
  270. String billDate = '',
  271. int page = 1,
  272. int size = 100,
  273. }) async {
  274. final cacheKey = 'getProjectCodes_${keyword}_${billDate}_$page';
  275. return _cache.getOrFetch(cacheKey, () async {
  276. final response = await _client.get<Map<String, dynamic>>(
  277. '/OA/GetProjectCodes',
  278. queryParameters: {
  279. 'keyword': keyword,
  280. 'billDate': billDate,
  281. 'page': page,
  282. 'size': size,
  283. },
  284. );
  285. final list = (response.data?['list'] as List<dynamic>?) ?? [];
  286. return list
  287. .map((e) => ProjectCodeItem.fromJson(e as Map<String, dynamic>))
  288. .toList();
  289. });
  290. }
  291. /// 部门
  292. Future<List<DepartmentItem>> getDepartments({
  293. String keyword = '',
  294. bool onlyActive = true,
  295. int page = 1,
  296. int size = 100,
  297. }) async {
  298. final cacheKey = 'getDepartments_${keyword}_${onlyActive}_$page';
  299. return _cache.getOrFetch(cacheKey, () async {
  300. final response = await _client.get<Map<String, dynamic>>(
  301. '/OA/GetDepartments',
  302. queryParameters: {
  303. 'keyword': keyword,
  304. 'onlyActive': onlyActive,
  305. 'page': page,
  306. 'size': size,
  307. },
  308. );
  309. final list = (response.data?['list'] as List<dynamic>?) ?? [];
  310. return list
  311. .map((e) => DepartmentItem.fromJson(e as Map<String, dynamic>))
  312. .toList();
  313. });
  314. }
  315. /// 客户/厂商
  316. Future<List<CustomerItem>> getCustomers({
  317. String keyword = '',
  318. int page = 1,
  319. int size = 100,
  320. }) async {
  321. final cacheKey = 'getCustomers_${keyword}_$page';
  322. return _cache.getOrFetch(cacheKey, () async {
  323. final response = await _client.get<Map<String, dynamic>>(
  324. '/OA/GetCustomers',
  325. queryParameters: {'keyword': keyword, 'page': page, 'size': size},
  326. );
  327. final list = (response.data?['list'] as List<dynamic>?) ?? [];
  328. return list
  329. .map((e) => CustomerItem.fromJson(e as Map<String, dynamic>))
  330. .toList();
  331. });
  332. }
  333. /// 员工查询
  334. Future<List<EmployeeItem>> getEmployees({
  335. String keyword = '',
  336. String salNo = '',
  337. int page = 1,
  338. int size = 100,
  339. }) async {
  340. final cacheKey = 'getEmployees_${keyword}_${salNo}_$page';
  341. return _cache.getOrFetch(cacheKey, () async {
  342. final response = await _client.get<Map<String, dynamic>>(
  343. '/OA/GetEmployees',
  344. queryParameters: {
  345. 'keyword': keyword,
  346. 'salNo': salNo,
  347. 'page': page,
  348. 'size': size,
  349. },
  350. );
  351. final list = (response.data?['list'] as List<dynamic>?) ?? [];
  352. return list
  353. .map((e) => EmployeeItem.fromJson(e as Map<String, dynamic>))
  354. .toList();
  355. });
  356. }
  357. /// 可转入报销单的费用申请明细(keyword 模糊匹配单号/事由/部门/申请人/费用类型)
  358. Future<Map<String, dynamic>> getImportableExpenseApplyList({
  359. String keyword = '',
  360. String startDate = '',
  361. String endDate = '',
  362. int page = 1,
  363. int size = 20,
  364. String sortDir = 'DESC',
  365. }) async {
  366. final response = await _client.get<Map<String, dynamic>>(
  367. '/OA/GetImportableExpenseApplyList',
  368. queryParameters: {
  369. 'keyword': keyword,
  370. 'startDate': startDate,
  371. 'endDate': endDate,
  372. 'page': page,
  373. 'size': size,
  374. 'sortDir': sortDir,
  375. },
  376. );
  377. return response.data!;
  378. }
  379. /// 币别
  380. Future<List<CurrencyItem>> getCurrencies({
  381. String keyword = '',
  382. int page = 1,
  383. int size = 50,
  384. }) async {
  385. final cacheKey = 'getCurrencies_${keyword}_$page';
  386. return _cache.getOrFetch(cacheKey, () async {
  387. final response = await _client.get<Map<String, dynamic>>(
  388. '/OA/GetCurrencies',
  389. queryParameters: {'keyword': keyword, 'page': page, 'size': size},
  390. );
  391. final list = (response.data?['list'] as List<dynamic>?) ?? [];
  392. return list
  393. .map((e) => CurrencyItem.fromJson(e as Map<String, dynamic>))
  394. .toList();
  395. });
  396. }
  397. /// 币别查询(旧版,保留兼容)
  398. Future<ApiResponse<Map<String, dynamic>>> fetchCurrencies({
  399. String keyword = '',
  400. int page = 1,
  401. int size = 50,
  402. }) async {
  403. return await _client.get(
  404. '/OA/GetCurrencies',
  405. queryParameters: {'keyword': keyword, 'page': page, 'size': size},
  406. );
  407. }
  408. /// 审批列表
  409. Future<Map<String, dynamic>> fetchApprovalList({
  410. required String bilId,
  411. required String status,
  412. String erpCategory = 'MasterService',
  413. String keyword = '',
  414. String startDate = '',
  415. String endDate = '',
  416. int page = 1,
  417. int size = 20,
  418. }) async {
  419. final response = await _client.get<Map<String, dynamic>>(
  420. '/OA/GetApprovalList',
  421. queryParameters: {
  422. 'bilId': bilId,
  423. 'status': status,
  424. 'erpCategory': erpCategory,
  425. 'keyword': keyword,
  426. 'startDate': startDate,
  427. 'endDate': endDate,
  428. 'page': page,
  429. 'size': size,
  430. },
  431. );
  432. return response.data!;
  433. }
  434. /// 审批进度
  435. Future<Map<String, dynamic>> fetchApprovalTimeline(
  436. String bilId,
  437. String bilNo, {
  438. int bilItm = 0,
  439. }) async {
  440. final response = await _client.get<Map<String, dynamic>>(
  441. '/OA/GetApprovalTimeline',
  442. queryParameters: {'bilId': bilId, 'bilNo': bilNo, 'bilItm': bilItm},
  443. );
  444. return response.data!;
  445. }
  446. /// 获取单据附件列表
  447. Future<List<BillAttachment>> getAttachments(
  448. String bilId,
  449. String bilNo, {
  450. int? srcItm,
  451. }) async {
  452. final params = <String, dynamic>{'bilId': bilId, 'bilNo': bilNo};
  453. if (srcItm != null) params['srcItm'] = srcItm;
  454. final response = await _client.get<dynamic>(
  455. '/OA/GetAttachments',
  456. queryParameters: params,
  457. );
  458. final body = response.data;
  459. if (body is! Map) return [];
  460. final result = body['Result'];
  461. if (result is! Map) return [];
  462. final documents = result['documents'];
  463. if (documents is! List) return [];
  464. return documents
  465. .whereType<Map<String, dynamic>>()
  466. .map((e) => BillAttachment.fromJson(e))
  467. .toList();
  468. }
  469. /// 上传附件
  470. /// [metadata] Map: { BIL_ID, BIL_NO, SRCITM, ITM, TAG, EFF_DD, USR, FILENAME, EXT }
  471. Future<Map<String, dynamic>> uploadAttachment(
  472. String filePath,
  473. Map<String, dynamic> metadata,
  474. ) async {
  475. final fileName =
  476. (metadata['FILENAME'] as String?) ?? filePath.split('/').last;
  477. final response = await _client.uploadMultipart<Map<String, dynamic>>(
  478. '/OA/UploadAttachment',
  479. files: [await MultipartFile.fromFile(filePath, filename: fileName)],
  480. extraFields: {'metadata': json.encode(metadata)}, // proper JSON encoding
  481. );
  482. return response.data ?? {};
  483. }
  484. /// 审核执行(通过/驳回/反审核)
  485. Future<Map<String, dynamic>> executeApproval({
  486. required String bilId,
  487. required String bilNo,
  488. int bilItm = 0,
  489. required String action,
  490. String rem = '',
  491. String effDd = '',
  492. String reason = '',
  493. bool isPreToStart = false,
  494. int nodeIndex = -1,
  495. String dataBx = '',
  496. }) async {
  497. final response = await _client.post<Map<String, dynamic>>(
  498. '/OA/ExecuteApproval',
  499. data: {
  500. 'bilId': bilId,
  501. 'bilNo': bilNo,
  502. 'bilItm': bilItm,
  503. 'action': action,
  504. 'rem': rem,
  505. 'effDd': effDd,
  506. 'reason': reason,
  507. 'isPreToStart': isPreToStart,
  508. 'nodeIndex': nodeIndex,
  509. 'dataBx': dataBx,
  510. },
  511. );
  512. return response.data!;
  513. }
  514. /// 费用报销报表
  515. Future<ReportData> getExpenseReport({
  516. String? startDate,
  517. String? endDate,
  518. }) async {
  519. final params = <String, dynamic>{};
  520. if (startDate != null) params['startDate'] = startDate;
  521. if (endDate != null) params['endDate'] = endDate;
  522. final response = await _client.get<Map<String, dynamic>>(
  523. '/OA/GetExpenseReport',
  524. queryParameters: params,
  525. );
  526. return ReportData.fromJson(response.data!);
  527. }
  528. /// 费用报销报表明细(分页)
  529. Future<Map<String, dynamic>> getExpenseReportDetail({
  530. String? startDate,
  531. String? endDate,
  532. int page = 1,
  533. int size = 20,
  534. }) async {
  535. final response = await _client.get<Map<String, dynamic>>(
  536. '/OA/GetExpenseReportDetail',
  537. queryParameters: {
  538. 'startDate': startDate,
  539. 'endDate': endDate,
  540. 'page': page,
  541. 'size': size,
  542. },
  543. );
  544. return response.data!;
  545. }
  546. /// 获取单据状态
  547. Future<Map<String, dynamic>> getBillStatus(String billNo) async {
  548. final response = await _client.get<Map<String, dynamic>>(
  549. '/OA/GetBillStatus',
  550. queryParameters: {'billId': 'BX', 'billNo': billNo},
  551. );
  552. return response.data!;
  553. }
  554. /// 检查当前用户是否拥有指定特殊密码权限
  555. Future<bool> checkSpcRight(String ctrlID) async {
  556. final response = await _client.get<Map<String, dynamic>>(
  557. '/OA/CheckSpcRight',
  558. queryParameters: {'ctrlID': ctrlID},
  559. );
  560. return response.data?['hasRight'] == true;
  561. }
  562. /// 获取指定单据别的附件操作权限
  563. Future<BillFileRights> getBillFileRights(String billId) async {
  564. final response = await _client.get<Map<String, dynamic>>(
  565. '/OA/GetBillFileRights',
  566. queryParameters: {'billId': billId},
  567. );
  568. return BillFileRights.fromJson(response.data!);
  569. }
  570. }
  571. /// 费用报销审批列表项
  572. class ExpenseApprovalListItem {
  573. final String bilId;
  574. final String bilNo;
  575. final String bilDate;
  576. final String applicant;
  577. final String dept;
  578. final double amount;
  579. final String remark;
  580. final String status;
  581. const ExpenseApprovalListItem({
  582. required this.bilId,
  583. required this.bilNo,
  584. required this.bilDate,
  585. required this.applicant,
  586. required this.dept,
  587. required this.amount,
  588. required this.remark,
  589. required this.status,
  590. });
  591. factory ExpenseApprovalListItem.fromJson(Map<String, dynamic> json) =>
  592. ExpenseApprovalListItem(
  593. bilId: json['bilId'] as String? ?? '',
  594. bilNo: json['bilNo'] as String? ?? '',
  595. bilDate: json['bilDate'] as String? ?? '',
  596. applicant: json['applicant'] as String? ?? '',
  597. dept: json['dept'] as String? ?? '',
  598. amount: (json['amount'] as num?)?.toDouble() ?? 0,
  599. remark: json['remark'] as String? ?? '',
  600. status: json['status'] as String? ?? 'pending',
  601. );
  602. ExpenseModel toExpenseModel() => ExpenseModel(
  603. id: bilNo,
  604. expenseNo: bilNo,
  605. expenseDate: bilDate.isNotEmpty ? DateTime.tryParse(bilDate) : null,
  606. applicantId: applicant,
  607. applicantName: applicant,
  608. deptId: dept,
  609. deptName: dept,
  610. totalAmount: amount,
  611. purpose: remark,
  612. status: status == 'approved' ? 'approved' : 'pending',
  613. paymentStatus: 'unpaid',
  614. createTime: DateTime.now(),
  615. updateTime: DateTime.now(),
  616. );
  617. }
  618. final expenseApprovalListProvider = FutureProvider.autoDispose
  619. .family<List<ExpenseModel>, String>((ref, status) async {
  620. ref.watch(expenseRefreshProvider);
  621. ref.watch(expenseDateStartProvider);
  622. ref.watch(expenseDateEndProvider);
  623. ref.watch(expenseKeywordProvider);
  624. final api = ref.read(expenseApiProvider);
  625. final dateStart = ref.read(expenseDateStartProvider);
  626. final dateEnd = ref.read(expenseDateEndProvider);
  627. final keyword = ref.read(expenseKeywordProvider);
  628. final response = await api.fetchApprovalList(
  629. bilId: 'BX',
  630. keyword: keyword,
  631. status: status.isEmpty ? '' : status,
  632. startDate: dateStart != null ? du.DateUtils.formatDate(dateStart) : '',
  633. endDate: dateEnd != null ? du.DateUtils.formatDate(dateEnd) : '',
  634. );
  635. return (response['list'] as List<dynamic>?)
  636. ?.map(
  637. (e) => ExpenseApprovalListItem.fromJson(
  638. e as Map<String, dynamic>,
  639. ).toExpenseModel(),
  640. )
  641. .toList() ??
  642. [];
  643. });
  644. /// "我的报销单" 列表(制单人=当前用户,非审批流)
  645. final expenseMyListProvider = FutureProvider.autoDispose
  646. .family<List<ExpenseModel>, String>((ref, status) async {
  647. ref.watch(expenseRefreshProvider);
  648. ref.watch(expenseDateStartProvider);
  649. ref.watch(expenseDateEndProvider);
  650. ref.watch(expenseKeywordProvider);
  651. final api = ref.read(expenseApiProvider);
  652. final dateStart = ref.read(expenseDateStartProvider);
  653. final dateEnd = ref.read(expenseDateEndProvider);
  654. final keyword = ref.read(expenseKeywordProvider);
  655. final result = await api.fetchList(
  656. keyword: keyword,
  657. startDate: dateStart != null ? du.DateUtils.formatDate(dateStart) : '',
  658. endDate: dateEnd != null ? du.DateUtils.formatDate(dateEnd) : '',
  659. usr: HostAppChannel.usr,
  660. sortDir: ref.watch(expenseSortDirProvider),
  661. );
  662. return result.list;
  663. });