expense_apply_api.dart 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import 'dart:convert';
  2. import 'package:dio/dio.dart';
  3. import 'package:flutter_riverpod/flutter_riverpod.dart';
  4. import '../../core/network/api_client.dart';
  5. import '../../app.dart';
  6. import '../../shared/models/pagination_model.dart';
  7. import '../../shared/models/bill_attachment.dart';
  8. import 'expense_apply_model.dart';
  9. final expenseApplyApiProvider = Provider<ExpenseApplyApi>(
  10. (ref) => ExpenseApplyApi(ref.read(apiClientProvider)),
  11. );
  12. // ═══ 参考数据模型(API 返回) ═══
  13. class CostTypeItem {
  14. final String typeNo;
  15. final String typeName;
  16. final String accNo;
  17. final String accName;
  18. const CostTypeItem({required this.typeNo, required this.typeName, required this.accNo, required this.accName});
  19. factory CostTypeItem.fromJson(Map<String, dynamic> json) => CostTypeItem(
  20. typeNo: json['typeNo'] as String? ?? '',
  21. typeName: json['typeName'] as String? ?? '',
  22. accNo: json['accNo'] as String? ?? '',
  23. accName: json['accName'] as String? ?? '',
  24. );
  25. }
  26. class ProjectCodeItem {
  27. final String objNo;
  28. final String name;
  29. const ProjectCodeItem({required this.objNo, required this.name});
  30. factory ProjectCodeItem.fromJson(Map<String, dynamic> json) => ProjectCodeItem(
  31. objNo: json['objNo'] as String? ?? '',
  32. name: json['name'] as String? ?? '',
  33. );
  34. }
  35. class DepartmentItem {
  36. final String dep;
  37. final String name;
  38. const DepartmentItem({required this.dep, required this.name});
  39. factory DepartmentItem.fromJson(Map<String, dynamic> json) => DepartmentItem(
  40. dep: json['dep'] as String? ?? '',
  41. name: json['name'] as String? ?? '',
  42. );
  43. }
  44. class ExpenseApplyApi {
  45. final ApiClient _client;
  46. ExpenseApplyApi(this._client);
  47. /// 费用申请列表(分页)
  48. Future<PaginatedData<ExpenseApplyModel>> fetchList({
  49. String status = '',
  50. String keyword = '',
  51. String startDate = '',
  52. String endDate = '',
  53. String usr = '',
  54. int page = 1,
  55. int size = 20,
  56. }) async {
  57. final response = await _client.get<Map<String, dynamic>>(
  58. '/OA/GetExpenseApplies',
  59. queryParameters: {
  60. 'status': status,
  61. 'keyword': keyword,
  62. 'startDate': startDate,
  63. 'endDate': endDate,
  64. 'usr': usr,
  65. 'page': page,
  66. 'size': size,
  67. },
  68. );
  69. return PaginatedData.fromJson(response.data!, ExpenseApplyModel.fromJson);
  70. }
  71. /// 费用申请详情(主表+明细)
  72. Future<ExpenseApplyModel> fetchDetail(String billNo) async {
  73. final response = await _client.get<Map<String, dynamic>>(
  74. '/OA/GetExpenseApplyDetail',
  75. queryParameters: {'billNo': billNo},
  76. );
  77. return ExpenseApplyModel.fromJson(response.data!);
  78. }
  79. /// 费用类别字典
  80. Future<List<CostTypeItem>> getCostTypes({String keyword = '', String accNo = ''}) async {
  81. final response = await _client.get<Map<String, dynamic>>(
  82. '/OA/GetCostTypes',
  83. queryParameters: {'keyword': keyword, 'accNo': accNo, 'page': 1, 'size': 100},
  84. );
  85. final list = (response.data?['list'] as List<dynamic>?) ?? [];
  86. return list.map((e) => CostTypeItem.fromJson(e as Map<String, dynamic>)).toList();
  87. }
  88. /// 项目代号
  89. Future<List<ProjectCodeItem>> getProjectCodes({String keyword = '', String billDate = ''}) async {
  90. final response = await _client.get<Map<String, dynamic>>(
  91. '/OA/GetProjectCodes',
  92. queryParameters: {'keyword': keyword, 'billDate': billDate, 'page': 1, 'size': 100},
  93. );
  94. final list = (response.data?['list'] as List<dynamic>?) ?? [];
  95. return list.map((e) => ProjectCodeItem.fromJson(e as Map<String, dynamic>)).toList();
  96. }
  97. /// 部门
  98. Future<List<DepartmentItem>> getDepartments({String keyword = '', bool onlyActive = true}) async {
  99. final response = await _client.get<Map<String, dynamic>>(
  100. '/OA/GetDepartments',
  101. queryParameters: {'keyword': keyword, 'onlyActive': onlyActive, 'page': 1, 'size': 100},
  102. );
  103. final list = (response.data?['list'] as List<dynamic>?) ?? [];
  104. return list.map((e) => DepartmentItem.fromJson(e as Map<String, dynamic>)).toList();
  105. }
  106. /// 提交审批,返回申请单号
  107. Future<String> submit(Map<String, dynamic> data) async {
  108. final response = await _client.post<Map<String, dynamic>>('/OA/BillSave', data: {
  109. 'erpCategory': 'MasterService',
  110. 'billId': 'AE',
  111. 'procId': '',
  112. 'data': data,
  113. });
  114. final resData = response.data;
  115. // 尝试从 resultData 或 HeadData 中提取 AE_NO
  116. if (resData != null) {
  117. final resultData = resData['resultData'] as Map<String, dynamic>?;
  118. if (resultData != null) {
  119. final aeNo = resultData['AE_NO'] as String?;
  120. if (aeNo != null && aeNo.isNotEmpty) return aeNo;
  121. final headData = resultData['HeadData'] as Map<String, dynamic>?;
  122. if (headData != null) {
  123. final hdAeNo = headData['AE_NO'] as String?;
  124. if (hdAeNo != null && hdAeNo.isNotEmpty) return hdAeNo;
  125. }
  126. }
  127. }
  128. throw Exception('BillSave succeeded but could not extract bill number');
  129. }
  130. /// 审批进度
  131. Future<Map<String, dynamic>> fetchApprovalTimeline(String bilId, String bilNo, {int bilItm = 0}) async {
  132. final response = await _client.get<Map<String, dynamic>>(
  133. '/OA/GetApprovalTimeline',
  134. queryParameters: {'bilId': bilId, 'bilNo': bilNo, 'bilItm': bilItm},
  135. );
  136. return response.data!;
  137. }
  138. /// 获取单据附件列表
  139. Future<List<BillAttachment>> getAttachments(String bilId, String bilNo, {int? srcItm}) async {
  140. final params = <String, dynamic>{
  141. 'bilId': bilId,
  142. 'bilNo': bilNo,
  143. };
  144. if (srcItm != null) params['srcItm'] = srcItm;
  145. final response = await _client.get<Map<String, dynamic>>(
  146. '/OA/GetAttachments',
  147. queryParameters: params,
  148. );
  149. final data = response.data;
  150. final list = (data?['Result']?['list'] as List<dynamic>?) ??
  151. (data?['list'] as List<dynamic>?) ??
  152. [];
  153. return list.map((e) => BillAttachment.fromJson(e as Map<String, dynamic>)).toList();
  154. }
  155. /// 上传附件
  156. Future<Map<String, dynamic>> uploadAttachment(
  157. String filePath,
  158. Map<String, dynamic> metadata,
  159. ) async {
  160. final fileName = (metadata['FILENAME'] as String?) ?? filePath.split('/').last;
  161. final response = await _client.uploadMultipart<Map<String, dynamic>>(
  162. '/OA/UploadAttachment',
  163. files: [await MultipartFile.fromFile(filePath, filename: fileName)],
  164. extraFields: {'metadata': json.encode(metadata)},
  165. );
  166. return response.data ?? {};
  167. }
  168. /// 审核执行(通过/驳回/反审核)
  169. Future<Map<String, dynamic>> executeApproval({
  170. required String bilId, required String bilNo, int bilItm = 0,
  171. required String action, String rem = '', String effDd = '',
  172. String reason = '', bool isPreToStart = false, int nodeIndex = -1,
  173. String dataBx = '',
  174. }) async {
  175. final response = await _client.post<Map<String, dynamic>>(
  176. '/OA/ExecuteApproval',
  177. data: {
  178. 'bilId': bilId, 'bilNo': bilNo, 'bilItm': bilItm,
  179. 'action': action, 'rem': rem, 'effDd': effDd,
  180. 'reason': reason, 'isPreToStart': isPreToStart,
  181. 'nodeIndex': nodeIndex, 'dataBx': dataBx,
  182. },
  183. );
  184. return response.data!;
  185. }
  186. }