| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- import 'dart:convert';
- import 'package:flutter/foundation.dart';
- import 'package:dio/dio.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import '../../core/network/api_client.dart';
- import '../../app.dart';
- import '../../shared/models/pagination_model.dart';
- import '../../shared/models/bill_attachment.dart';
- import 'expense_apply_model.dart';
- import 'report_model.dart';
- final expenseApplyApiProvider = Provider<ExpenseApplyApi>(
- (ref) => ExpenseApplyApi(ref.read(apiClientProvider)),
- );
- // ═══ 参考数据模型(API 返回) ═══
- class CostTypeItem {
- final String typeNo;
- final String typeName;
- final String accNo;
- final String accName;
- const CostTypeItem({required this.typeNo, required this.typeName, required this.accNo, required this.accName});
- factory CostTypeItem.fromJson(Map<String, dynamic> json) => CostTypeItem(
- typeNo: json['typeNo'] as String? ?? '',
- typeName: json['typeName'] as String? ?? '',
- accNo: json['accNo'] as String? ?? '',
- accName: json['accName'] as String? ?? '',
- );
- }
- class ProjectCodeItem {
- final String objNo;
- final String name;
- const ProjectCodeItem({required this.objNo, required this.name});
- factory ProjectCodeItem.fromJson(Map<String, dynamic> json) => ProjectCodeItem(
- objNo: json['objNo'] as String? ?? '',
- name: json['name'] as String? ?? '',
- );
- }
- class DepartmentItem {
- final String dep;
- final String name;
- const DepartmentItem({required this.dep, required this.name});
- factory DepartmentItem.fromJson(Map<String, dynamic> json) => DepartmentItem(
- dep: json['dep'] as String? ?? '',
- name: json['name'] as String? ?? '',
- );
- }
- class ExpenseApplyApi {
- final ApiClient _client;
- ExpenseApplyApi(this._client);
- /// 费用申请列表(分页)
- Future<PaginatedData<ExpenseApplyModel>> fetchList({
- String status = '',
- String keyword = '',
- String startDate = '',
- String endDate = '',
- String usr = '',
- String sortDir = 'DESC',
- int page = 1,
- int size = 20,
- }) async {
- final response = await _client.get<Map<String, dynamic>>(
- '/OA/GetExpenseApplies',
- queryParameters: {
- 'status': status,
- 'keyword': keyword,
- 'startDate': startDate,
- 'endDate': endDate,
- 'usr': usr,
- 'sortDir': sortDir,
- 'page': page,
- 'size': size,
- },
- );
- return PaginatedData.fromJson(response.data!, ExpenseApplyModel.fromJson);
- }
- /// 费用申请详情(主表+明细)
- Future<ExpenseApplyModel> fetchDetail(String billNo) async {
- final response = await _client.get<Map<String, dynamic>>(
- '/OA/GetExpenseApplyDetail',
- queryParameters: {'billNo': billNo},
- );
- return ExpenseApplyModel.fromJson(response.data!);
- }
- /// 费用类别字典
- Future<List<CostTypeItem>> getCostTypes({String keyword = '', String accNo = ''}) async {
- final response = await _client.get<Map<String, dynamic>>(
- '/OA/GetCostTypes',
- queryParameters: {'keyword': keyword, 'accNo': accNo, 'page': 1, 'size': 100},
- );
- final list = (response.data?['list'] as List<dynamic>?) ?? [];
- return list.map((e) => CostTypeItem.fromJson(e as Map<String, dynamic>)).toList();
- }
- /// 项目代号
- Future<List<ProjectCodeItem>> getProjectCodes({String keyword = '', String billDate = ''}) async {
- final response = await _client.get<Map<String, dynamic>>(
- '/OA/GetProjectCodes',
- queryParameters: {'keyword': keyword, 'billDate': billDate, 'page': 1, 'size': 100},
- );
- final list = (response.data?['list'] as List<dynamic>?) ?? [];
- return list.map((e) => ProjectCodeItem.fromJson(e as Map<String, dynamic>)).toList();
- }
- /// 部门
- Future<List<DepartmentItem>> getDepartments({String keyword = '', bool onlyActive = true}) async {
- final response = await _client.get<Map<String, dynamic>>(
- '/OA/GetDepartments',
- queryParameters: {'keyword': keyword, 'onlyActive': onlyActive, 'page': 1, 'size': 100},
- );
- final list = (response.data?['list'] as List<dynamic>?) ?? [];
- return list.map((e) => DepartmentItem.fromJson(e as Map<String, dynamic>)).toList();
- }
- /// 会计科目树(级联选择器数据源)
- Future<dynamic> getAcctSubjects() async {
- final response = await _client.get('/OA/GetAcctSubjects');
- return response.data;
- }
- /// 提交审批,返回申请单号(提取失败时返回 null,不影响主流程)
- /// BillSave 返回格式: { callok:true, resultData:{ BIL_NO:"AE20267020005", ... } }
- Future<String?> submit(Map<String, dynamic> data) async {
- final response = await _client.post<Map<String, dynamic>>('/OA/BillSave', data: {
- 'erpCategory': 'MasterService',
- 'billId': 'AE',
- 'procId': '',
- 'data': data,
- });
- final resData = response.data;
- if (resData == null) return null;
- // 从 resultData 中取(BillSave 实际返回的嵌套格式)
- final resultData = resData['resultData'];
- if (resultData is Map<String, dynamic>) {
- // BIL_NO 是 BillSave 返回的通用单号字段(不区分 AE/BX)
- final bilNo = resultData['BIL_NO'] as String?;
- if (bilNo != null && bilNo.isNotEmpty) return bilNo;
- }
- // resultData 可能是 JSON 字符串
- if (resultData is String && resultData.isNotEmpty) {
- try {
- final parsed = json.decode(resultData) as Map<String, dynamic>;
- final bilNo = parsed['BIL_NO'] as String?;
- if (bilNo != null && bilNo.isNotEmpty) return bilNo;
- } catch (_) {}
- }
- // 兜底: resData 根级 BIL_NO
- final rootBilNo = resData['BIL_NO'] as String?;
- if (rootBilNo != null && rootBilNo.isNotEmpty) return rootBilNo;
- return null;
- }
- /// 下载附件文件字节
- Future<Uint8List?> downloadAttachment(String id) async {
- return await _client.downloadFile('/OA/DownloadAttachment', queryParameters: {'id': id});
- }
- /// 检测附件服务是否可用
- Future<bool> checkAttachHealth() async {
- try {
- final response = await _client.get<Map<String, dynamic>>('/OA/CheckAttachHealth');
- debugPrint('[checkAttachHealth] response.data: ${response.data}');
- debugPrint('[checkAttachHealth] response.data type: ${response.data.runtimeType}');
- final available = response.data?['available'] as bool? ?? false;
- debugPrint('[checkAttachHealth] available: $available');
- return available;
- } catch (e) {
- debugPrint('[checkAttachHealth] error: $e');
- return false;
- }
- }
- /// 审批进度
- Future<Map<String, dynamic>> fetchApprovalTimeline(String bilId, String bilNo, {int bilItm = 0}) async {
- final response = await _client.get<Map<String, dynamic>>(
- '/OA/GetApprovalTimeline',
- queryParameters: {'bilId': bilId, 'bilNo': bilNo, 'bilItm': bilItm},
- );
- return response.data!;
- }
- /// 获取单据附件列表
- Future<List<BillAttachment>> getAttachments(String bilId, String bilNo, {int? srcItm}) async {
- final params = <String, dynamic>{
- 'bilId': bilId,
- 'bilNo': bilNo,
- };
- if (srcItm != null) params['srcItm'] = srcItm;
- final response = await _client.get<dynamic>(
- '/OA/GetAttachments',
- queryParameters: params,
- );
- final body = response.data;
- if (body is! Map) return [];
- final result = body['Result'];
- if (result is! Map) return [];
- final documents = result['documents'];
- if (documents is! List) return [];
- return documents
- .whereType<Map<String, dynamic>>()
- .map((e) => BillAttachment.fromJson(e))
- .toList();
- }
- /// 上传附件
- Future<Map<String, dynamic>> uploadAttachment(
- String filePath,
- Map<String, dynamic> metadata,
- ) async {
- final fileName = (metadata['FILENAME'] as String?) ?? filePath.split('/').last;
- final response = await _client.uploadMultipart<Map<String, dynamic>>(
- '/OA/UploadAttachment',
- files: [await MultipartFile.fromFile(filePath, filename: fileName)],
- extraFields: {'metadata': json.encode(metadata)},
- );
- return response.data ?? {};
- }
- /// 审核执行(通过/驳回/反审核)
- Future<Map<String, dynamic>> executeApproval({
- required String bilId, required String bilNo, int bilItm = 0,
- required String action, String rem = '', String effDd = '',
- String reason = '', bool isPreToStart = false, int nodeIndex = -1,
- String dataBx = '',
- }) async {
- final response = await _client.post<Map<String, dynamic>>(
- '/OA/ExecuteApproval',
- data: {
- 'bilId': bilId, 'bilNo': bilNo, 'bilItm': bilItm,
- 'action': action, 'rem': rem, 'effDd': effDd,
- 'reason': reason, 'isPreToStart': isPreToStart,
- 'nodeIndex': nodeIndex, 'dataBx': dataBx,
- },
- );
- return response.data!;
- }
- /// 费用申请报表
- Future<ReportData> getExpenseApplyReport({String? startDate, String? endDate}) async {
- final params = <String, dynamic>{};
- if (startDate != null) params['startDate'] = startDate;
- if (endDate != null) params['endDate'] = endDate;
- final response = await _client.get<Map<String, dynamic>>(
- '/OA/GetExpenseApplyReport',
- queryParameters: params,
- );
- return ReportData.fromJson(response.data!);
- }
- /// 费用申请报表明细(分页)
- Future<Map<String, dynamic>> getExpenseApplyReportDetails({
- String? startDate, String? endDate, int page = 1, int size = 20,
- }) async {
- final response = await _client.get<Map<String, dynamic>>(
- '/OA/GetExpenseApplyReportDetails',
- queryParameters: {'startDate': startDate, 'endDate': endDate, 'page': page, 'size': size},
- );
- return response.data!;
- }
- }
|