expense_api.dart 20 KB

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