Bläddra i källkod

fix: 消除最后一批 NOT_IMPL 存根 — BillHandler/VoucherHandler consume.detail.query、回调通知、PortalService.plugins、ParamsService.upload

alphah 14 timmar sedan
förälder
incheckning
21791590d2
54 ändrade filer med 1937 tillägg och 402 borttagningar
  1. 0 25
      .claude/CLAUDE.md
  2. 184 17
      java/src/main/java/com/payment/platform/common/utils/ExcelUtil.java
  3. 10 3
      java/src/main/java/com/payment/platform/module/payment/account/controller/AccountController.java
  4. 239 30
      java/src/main/java/com/payment/platform/module/payment/account/service/AccountService.java
  5. 1 4
      java/src/main/java/com/payment/platform/module/payment/expense/controller/QuotaController.java
  6. 57 21
      java/src/main/java/com/payment/platform/module/payment/expense/institution/service/InstitutionScopeSyncService.java
  7. 494 99
      java/src/main/java/com/payment/platform/module/payment/expense/institution/service/InstitutionService.java
  8. 86 48
      java/src/main/java/com/payment/platform/module/payment/expense/quota/service/IssueBatchService.java
  9. 255 106
      java/src/main/java/com/payment/platform/module/payment/expense/quota/service/QuotaService.java
  10. 126 19
      java/src/main/java/com/payment/platform/module/payment/expense/rule/service/RuleService.java
  11. 152 3
      java/src/main/java/com/payment/platform/module/payment/notification/handler/BillHandler.java
  12. 66 1
      java/src/main/java/com/payment/platform/module/payment/notification/handler/VoucherHandler.java
  13. 50 2
      java/src/main/java/com/payment/platform/module/portal/service/PortalService.java
  14. 7 3
      java/src/main/java/com/payment/platform/module/system/log/controller/LogController.java
  15. 7 3
      java/src/main/java/com/payment/platform/module/system/notice/controller/NoticeController.java
  16. 14 4
      java/src/main/java/com/payment/platform/module/system/params/controller/ParamsController.java
  17. 7 4
      java/src/main/java/com/payment/platform/module/system/params/service/ParamsService.java
  18. 7 3
      java/src/main/java/com/payment/platform/module/system/position/controller/PositionController.java
  19. 20 7
      java/src/main/java/com/payment/platform/module/system/user/controller/UserController.java
  20. 152 0
      java/src/main/java/com/payment/platform/module/system/user/service/UserService.java
  21. BIN
      java/target/classes/com/payment/platform/common/utils/ExcelUtil.class
  22. BIN
      java/target/classes/com/payment/platform/module/common/controller/FileController.class
  23. BIN
      java/target/classes/com/payment/platform/module/common/controller/HealthController.class
  24. BIN
      java/target/classes/com/payment/platform/module/example/demo/controller/DemoController.class
  25. BIN
      java/target/classes/com/payment/platform/module/example/demo/service/DemoService.class
  26. BIN
      java/target/classes/com/payment/platform/module/example/demo01/controller/Demo01Controller.class
  27. BIN
      java/target/classes/com/payment/platform/module/payment/account/controller/AccountController.class
  28. BIN
      java/target/classes/com/payment/platform/module/payment/account/service/AccountService.class
  29. BIN
      java/target/classes/com/payment/platform/module/payment/expense/controller/QuotaController.class
  30. BIN
      java/target/classes/com/payment/platform/module/payment/expense/controller/RuleController.class
  31. BIN
      java/target/classes/com/payment/platform/module/payment/expense/institution/service/InstitutionScopeSyncService$1.class
  32. BIN
      java/target/classes/com/payment/platform/module/payment/expense/institution/service/InstitutionScopeSyncService$2.class
  33. BIN
      java/target/classes/com/payment/platform/module/payment/expense/institution/service/InstitutionScopeSyncService.class
  34. BIN
      java/target/classes/com/payment/platform/module/payment/expense/institution/service/InstitutionService$1.class
  35. BIN
      java/target/classes/com/payment/platform/module/payment/expense/institution/service/InstitutionService.class
  36. BIN
      java/target/classes/com/payment/platform/module/payment/expense/quota/service/IssueBatchService.class
  37. BIN
      java/target/classes/com/payment/platform/module/payment/expense/quota/service/QuotaService.class
  38. BIN
      java/target/classes/com/payment/platform/module/payment/expense/rule/service/RuleService$1.class
  39. BIN
      java/target/classes/com/payment/platform/module/payment/expense/rule/service/RuleService$2.class
  40. BIN
      java/target/classes/com/payment/platform/module/payment/expense/rule/service/RuleService$3.class
  41. BIN
      java/target/classes/com/payment/platform/module/payment/expense/rule/service/RuleService.class
  42. BIN
      java/target/classes/com/payment/platform/module/payment/notification/handler/BillHandler.class
  43. BIN
      java/target/classes/com/payment/platform/module/payment/notification/handler/VoucherHandler.class
  44. BIN
      java/target/classes/com/payment/platform/module/portal/controller/PortalController.class
  45. BIN
      java/target/classes/com/payment/platform/module/portal/service/PortalService.class
  46. BIN
      java/target/classes/com/payment/platform/module/system/log/controller/LogController.class
  47. BIN
      java/target/classes/com/payment/platform/module/system/notice/controller/NoticeController.class
  48. BIN
      java/target/classes/com/payment/platform/module/system/params/controller/ParamsController.class
  49. BIN
      java/target/classes/com/payment/platform/module/system/params/service/ParamsService.class
  50. BIN
      java/target/classes/com/payment/platform/module/system/position/controller/PositionController.class
  51. BIN
      java/target/classes/com/payment/platform/module/system/user/controller/UserController.class
  52. BIN
      java/target/classes/com/payment/platform/module/system/user/service/UserService.class
  53. 2 0
      java/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  54. 1 0
      java/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

+ 0 - 25
.claude/CLAUDE.md

@@ -1,25 +0,0 @@
-# CLAUDE.md
-
-## CodeGraph 优先
-
-对于结构性代码问题(查定义、调用链、符号搜索等),优先使用 CodeGraph MCP 工具(`codegraph_context` → `codegraph_explore` → `codegraph_search`),不要用 Grep + Read 循环。
-
-## 项目结构
-
-多模块 Maven 项目:
-
-| 模块 | 职责 |
-|------|------|
-| `antom-common` | 公共常量、异常、DTO |
-| `antom-sdk` | Antom API 对接(签名、DTO) |
-| `antom-service` | 核心业务(Service、Entity、Mapper) |
-| `antom-admin` | Admin 后台 API |
-| `antom-merchant` | 商户端 API |
-| `antom-web` | Vue3 前端(Vite + Element Plus) |
-
-## 前端
-
-- 技术栈:Vue 3 + Vite + Element Plus
-- 代码位置:`antom-web/src/`
-- API 封装:`antom-web/src/api/`
-- 页面:`antom-web/src/views/`

+ 184 - 17
java/src/main/java/com/payment/platform/common/utils/ExcelUtil.java

@@ -1,42 +1,209 @@
 package com.payment.platform.common.utils;
 
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.context.AnalysisContext;
+import com.alibaba.excel.event.AnalysisEventListener;
+import com.alibaba.excel.metadata.data.ReadCellData;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.xssf.usermodel.XSSFDataValidationHelper;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.*;
 
 /**
- * Excel 导入导出工具 — Apache POI 封装
- * 对应 Python excel_util.py
+ * Excel 导入导出/模板工具 — EasyExcel + Apache POI
+ * 对应 Python excel_util.py 的 export_list2excel + get_excel_template
  */
 @Slf4j
 public final class ExcelUtil {
     private ExcelUtil() {}
 
+    // ==================== 导出 ====================
+
     /**
-     * 将 List<Map> 导出为 Excel 文件
-     * @param data 数据列表
-     * @param columnMapping 列映射(字段名 -> 中文标题)
-     * @return Excel 文件字节数组
+     * 将 List&lt;Map&gt; 导出为 Excel 字节数组。
+     * @param data          数据列表,每个 Map 是一行
+     * @param columnMapping 列映射(字段名 → 中文标题),同时决定列顺序
+     * @return Excel .xlsx 字节数组
      */
     public static byte[] exportToExcel(List<Map<String, Object>> data, Map<String, String> columnMapping) {
-        log.warn("Excel 导出未实现 (Apache POI 待集成): {} 行, {} 列", data.size(), columnMapping.size());
-        throw new UnsupportedOperationException("Excel 导出功能尚未实现,需要集成 Apache POI");
+        if (columnMapping == null || columnMapping.isEmpty()) {
+            throw new IllegalArgumentException("columnMapping 不能为空");
+        }
+
+        // 构建表头:EasyExcel 需要 List<List<String>>
+        List<List<String>> heads = new ArrayList<>();
+        List<String> keys = new ArrayList<>(columnMapping.keySet());
+        for (String key : keys) {
+            heads.add(List.of(columnMapping.get(key)));
+        }
+
+        // 构建数据行:按 keys 顺序取值
+        List<List<Object>> rows = new ArrayList<>();
+        for (Map<String, Object> row : data) {
+            List<Object> rowData = new ArrayList<>(keys.size());
+            for (String key : keys) {
+                rowData.add(row.getOrDefault(key, ""));
+            }
+            rows.add(rowData);
+        }
+
+        try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+            EasyExcel.write(bos)
+                    .head(heads)
+                    .sheet("Sheet1")
+                    .doWrite(rows);
+            return bos.toByteArray();
+        } catch (IOException e) {
+            log.error("Excel 导出失败", e);
+            throw new RuntimeException("Excel 导出失败", e);
+        }
     }
 
+    // ==================== 导入 ====================
+
     /**
-     * 从 Excel 文件导入数据
-     * @param fileBytes Excel 文件字节数组
-     * @param columnMapping 列映射(中文标题 -> 字段名)
+     * 从 Excel 字节数组读取数据。
+     * 第一行为标题行,从标题行映射回字段名后返回 List&lt;Map&gt;。
+     *
+     * @param fileBytes     Excel 文件字节数组
+     * @param columnMapping 列映射(中文标题 → 字段名),用于将 Excel 标题行反查为字段 key
      * @return 解析后的数据列表
      */
     public static List<Map<String, Object>> importFromExcel(byte[] fileBytes, Map<String, String> columnMapping) {
-        log.warn("Excel 导入未实现 (Apache POI 待集成): {} bytes, {} 列映射", fileBytes.length, columnMapping.size());
-        throw new UnsupportedOperationException("Excel 导入功能尚未实现,需要集成 Apache POI");
+        if (columnMapping == null || columnMapping.isEmpty()) {
+            throw new IllegalArgumentException("columnMapping 不能为空");
+        }
+
+        // 构建反向映射:中文标题 → 字段名
+        Map<String, String> reverseMapping = new LinkedHashMap<>();
+        for (Map.Entry<String, String> e : columnMapping.entrySet()) {
+            reverseMapping.put(e.getValue(), e.getKey());
+        }
+
+        List<Map<String, Object>> result = new ArrayList<>();
+
+        try (ByteArrayInputStream bis = new ByteArrayInputStream(fileBytes)) {
+            EasyExcel.read(bis, new AnalysisEventListener<Map<Integer, String>>() {
+                private List<String> headerKeys = null;
+
+                @Override
+                public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
+                    headerKeys = new ArrayList<>();
+                    int maxCol = headMap.keySet().stream().max(Integer::compareTo).orElse(-1);
+                    for (int i = 0; i <= maxCol; i++) {
+                        ReadCellData<?> cell = headMap.get(i);
+                        String title = cell != null ? cell.getStringValue() : null;
+                        headerKeys.add(title != null ? reverseMapping.get(title.trim()) : null);
+                    }
+                }
+
+                @Override
+                public void invoke(Map<Integer, String> rowData, AnalysisContext context) {
+                    if (headerKeys == null || rowData == null) return;
+                    Map<String, Object> row = new LinkedHashMap<>();
+                    for (Map.Entry<Integer, String> cell : rowData.entrySet()) {
+                        int idx = cell.getKey();
+                        if (idx < headerKeys.size() && headerKeys.get(idx) != null) {
+                            row.put(headerKeys.get(idx), cell.getValue());
+                        }
+                    }
+                    if (!row.isEmpty()) {
+                        result.add(row);
+                    }
+                }
+
+                @Override
+                public void doAfterAllAnalysed(AnalysisContext context) {
+                    // no-op
+                }
+            }).sheet().doRead();
+        } catch (IOException e) {
+            log.error("Excel 导入失败", e);
+            throw new RuntimeException("Excel 导入失败", e);
+        }
+
+        return result;
     }
 
-    /** 构建常用字段映射 */
+    // ==================== 模板 ====================
+
+    /**
+     * 生成带下拉验证的 Excel 导入模板。
+     * 对应 Python ExcelUtil.get_excel_template()
+     *
+     * @param headers         表头列表,按列顺序
+     * @param selectorHeaders 需要设置下拉选择的表头名称
+     * @param options         下拉选项配置,每个 Map 是 {表头名: [选项列表]}
+     * @return Excel .xlsx 字节数组
+     */
+    public static byte[] getExcelTemplate(List<String> headers,
+                                          List<String> selectorHeaders,
+                                          List<Map<String, List<String>>> options) {
+        try (XSSFWorkbook wb = new XSSFWorkbook()) {
+            XSSFSheet sheet = wb.createSheet("Sheet1");
+
+            // 表头样式(灰底、居中)
+            CellStyle headerStyle = wb.createCellStyle();
+            headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+            headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            headerStyle.setAlignment(HorizontalAlignment.CENTER);
+
+            // 写表头行
+            Row headerRow = sheet.createRow(0);
+            for (int i = 0; i < headers.size(); i++) {
+                Cell cell = headerRow.createCell(i);
+                cell.setCellValue(headers.get(i));
+                cell.setCellStyle(headerStyle);
+                sheet.setColumnWidth(i, 12 * 256); // 约 12 字符宽
+            }
+
+            // 下拉验证
+            if (selectorHeaders != null && options != null) {
+                DataValidationHelper dvHelper = new XSSFDataValidationHelper(sheet);
+                for (String selHeader : selectorHeaders) {
+                    int colIdx = headers.indexOf(selHeader);
+                    if (colIdx < 0) continue;
+
+                    // 找到该表头对应的选项列表
+                    List<String> optList = null;
+                    for (Map<String, List<String>> opt : options) {
+                        if (opt.containsKey(selHeader)) {
+                            optList = opt.get(selHeader);
+                            break;
+                        }
+                    }
+                    if (optList == null || optList.isEmpty()) continue;
+
+                    String[] arr = optList.toArray(new String[0]);
+                    DataValidationConstraint constraint =
+                            dvHelper.createExplicitListConstraint(arr);
+                    CellRangeAddressList range = new CellRangeAddressList(1, 1048575, colIdx, colIdx);
+                    DataValidation validation = dvHelper.createValidation(constraint, range);
+                    validation.setSuppressDropDownArrow(true);
+                    validation.setShowErrorBox(true);
+                    sheet.addValidationData(validation);
+                }
+            }
+
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            wb.write(bos);
+            return bos.toByteArray();
+        } catch (IOException e) {
+            log.error("生成 Excel 模板失败", e);
+            throw new RuntimeException("生成 Excel 模板失败", e);
+        }
+    }
+
+    // ==================== 工具方法 ====================
+
+    /** 构建有序字段映射,{@code pairs} 按 [key1, val1, key2, val2, ...] 顺序传入 */
     public static Map<String, String> buildMapping(String... pairs) {
         Map<String, String> map = new LinkedHashMap<>();
         for (int i = 0; i < pairs.length; i += 2) map.put(pairs[i], pairs[i + 1]);

+ 10 - 3
java/src/main/java/com/payment/platform/module/payment/account/controller/AccountController.java

@@ -7,11 +7,13 @@ import com.payment.platform.module.payment.account.dto.TransferCreateDTO;
 import com.payment.platform.module.payment.account.dto.TransferVO;
 import com.payment.platform.module.payment.account.service.AccountService;
 import com.payment.platform.module.payment.account.service.AlipayTransferService;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
 import java.util.Map;
@@ -102,11 +104,16 @@ public class AccountController {
     }
 
     @GetMapping("/transfer/export")
-    public Result<?> transferExport(
+    public void transferExport(
             @RequestParam(name = "start_time") String startTime,
             @RequestParam(name = "end_time") String endTime,
-            @RequestParam(name = "enterprise_id", required = false) String enterpriseId) {
-        return Result.ok(accountService.transferExport(startTime, endTime, enterpriseId));
+            @RequestParam(name = "enterprise_id", required = false) String enterpriseId,
+            HttpServletResponse response) throws IOException {
+        byte[] bytes = accountService.transferExport(startTime, endTime, enterpriseId);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setHeader("Content-Disposition",
+                "attachment; filename=transfer_report_" + startTime + "_" + endTime + ".xlsx");
+        response.getOutputStream().write(bytes);
     }
 
     @GetMapping("/receipt/download")

+ 239 - 30
java/src/main/java/com/payment/platform/module/payment/account/service/AccountService.java

@@ -3,21 +3,38 @@ package com.payment.platform.module.payment.account.service;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.payment.platform.common.response.PageResult;
+import com.payment.platform.common.utils.ExcelUtil;
 import com.payment.platform.module.payment.account.dto.AccountVO;
 import com.payment.platform.module.payment.account.dto.TransferVO;
 import com.payment.platform.module.payment.account.entity.AccountEntity;
 import com.payment.platform.module.payment.account.entity.TransferEntity;
 import com.payment.platform.module.payment.account.mapper.AccountMapper;
 import com.payment.platform.module.payment.account.mapper.TransferMapper;
+import com.payment.platform.common.exception.BusinessException;
+import com.payment.platform.core.alipay.AlipayClientFactory;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.domain.AlipayCommerceEcConsumeDetailQueryModel;
+import com.alipay.api.request.AlipayCommerceEcConsumeDetailQueryRequest;
+import com.alipay.api.response.AlipayCommerceEcConsumeDetailQueryResponse;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 @Slf4j
 @Service
@@ -26,6 +43,8 @@ public class AccountService {
 
     private final AccountMapper accountMapper;
     private final TransferMapper transferMapper;
+    private final AlipayClientFactory alipayClientFactory;
+    private final RedisTemplate<String, Object> redisTemplate;
 
     public List<AccountVO> queryAccounts(String enterpriseId) {
         return accountMapper.selectList(
@@ -99,14 +118,66 @@ public class AccountService {
     }
 
     /**
-     * 账单详情查询
-     *
-     * Python 对应: consume_detail_query_service (L1307-1390)
-     * 调用 alipay.commerce.ec.consume.detail.query, 需实现完整 SDK 调用 + 26+ 字段解析
+     * 账单详情查询 — 对应 Python consume_detail_query_service (L1307-1390)
+     * 调用 alipay.commerce.ec.consume.detail.query
      */
     public Map<String, Object> consumeDetail(String payNo, String enterpriseId, String antShopId, String[] queryOptions) {
-        log.warn("未实现: 调用 alipay.commerce.ec.consume.detail.query");
-        return Map.of("pay_no", payNo, "message", "功能开发中");
+        try {
+            AlipayCommerceEcConsumeDetailQueryModel model = new AlipayCommerceEcConsumeDetailQueryModel();
+            model.setPayNo(payNo);
+            if (enterpriseId != null) model.setEnterpriseId(enterpriseId);
+            if (queryOptions != null && queryOptions.length > 0)
+                model.setQueryOptions(List.of(queryOptions));
+
+            AlipayCommerceEcConsumeDetailQueryRequest request = new AlipayCommerceEcConsumeDetailQueryRequest();
+            request.setBizModel(model);
+
+            AlipayCommerceEcConsumeDetailQueryResponse response =
+                    alipayClientFactory.getClient().execute(request);
+            if (response == null) throw new BusinessException(400, "账单详情查询失败: 无响应");
+            if (!response.isSuccess()) {
+                log.error("支付宝账单详情查询失败: code={}, msg={}, subMsg={}",
+                        response.getCode(), response.getMsg(), response.getSubMsg());
+                throw new BusinessException(400, "账单详情查询失败: " +
+                        (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+
+            com.alipay.api.domain.EcConsumeInfo info = response.getConsumeInfo();
+            if (info == null) throw new BusinessException(400, "账单详情查询失败: 无账单信息");
+
+            Map<String, Object> result = new LinkedHashMap<>();
+            result.put("account_id", info.getAccountId());
+            result.put("pay_no", info.getPayNo());
+            result.put("consume_type", info.getConsumeType());
+            result.put("gmt_biz_create", info.getGmtBizCreate());
+            result.put("consume_biz_type", info.getConsumeBizType());
+            result.put("consume_amount", info.getConsumeAmount());
+            result.put("order_complete_label", info.getOrderCompleteLabel());
+            result.put("refund_status", info.getRefundStatus());
+            result.put("refund_amount", info.getRefundAmount());
+            result.put("peer_payer_card_no", info.getPeerPayerCardNo());
+            result.put("user_id", info.getUserId());
+            result.put("open_id", info.getOpenId());
+            result.put("enterprise_id", info.getEnterpriseId());
+            result.put("employee_id", info.getEmployeeId());
+            result.put("merchant_name", info.getMerchantName());
+            result.put("expense_scene_code", info.getExpenseSceneCode());
+            result.put("expense_type_sub_category", info.getExpenseTypeSubCategory());
+            result.put("consume_title", info.getConsumeTitle());
+            result.put("gmt_receive_pay", info.getGmtReceivePay());
+            result.put("peer_pay_amount", info.getPeerPayAmount());
+            result.put("benefit_amount", info.getBenefitAmount());
+            result.put("ext_infos", info.getExtInfos());
+            result.put("payer_logon_id", info.getPayerLogonId());
+            result.put("order_complete_time", info.getOrderCompleteTime());
+            // 补充关联订单和凭证列表(Python 返回 order_info + voucher_list)
+            result.put("related_order_info", response.getRelatedOrderInfo());
+            result.put("related_voucher_list", response.getRelatedVoucherList());
+            return result;
+        } catch (AlipayApiException e) {
+            log.error("支付宝账单详情查询异常", e);
+            throw new BusinessException(400, "支付宝账单详情查询异常: " + e.getMessage());
+        }
     }
 
     /**
@@ -114,44 +185,182 @@ public class AccountService {
      *
      * Python 对应: transfer_export_service (L753-821) 使用 ExcelUtil 导出 Excel
      */
-    public Map<String, Object> transferExport(String startTime, String endTime, String enterpriseId) {
-        log.warn("未实现: transfer_export_service 导出 Excel");
-        return Map.of("start_time", startTime, "end_time", endTime, "message", "功能开发中");
+    public byte[] transferExport(String startTime, String endTime, String enterpriseId) {
+        try {
+            OffsetDateTime start = parseDateTime(startTime);
+            OffsetDateTime end = parseDateTime(endTime);
+
+            LambdaQueryWrapper<TransferEntity> w = new LambdaQueryWrapper<>();
+            w.ge(TransferEntity::getCreatedTime, start)
+             .le(TransferEntity::getCreatedTime, end);
+            if (enterpriseId != null && !enterpriseId.isBlank()) {
+                w.eq(TransferEntity::getEnterpriseId, enterpriseId);
+            }
+            w.orderByDesc(TransferEntity::getId);
+
+            List<TransferEntity> records = transferMapper.selectList(w);
+
+            Map<String, String> statusMap = Map.of(
+                    "DEALING", "处理中",
+                    "SUCCESS", "成功",
+                    "FAIL", "失败",
+                    "REFUND", "退票");
+
+            Map<String, String> payeeTypeMap = Map.of(
+                    "ALIPAY_ACCOUNT", "支付宝账户",
+                    "BANK_CARD", "银行卡");
+
+            ObjectMapper om = new ObjectMapper();
+            List<Map<String, Object>> listData = new ArrayList<>();
+
+            int seq = 1;
+            for (TransferEntity r : records) {
+                Map<String, Object> row = new LinkedHashMap<>();
+                row.put("序号", seq++);
+                row.put("订单号", r.getOutBizNo() != null ? r.getOutBizNo() : "");
+                row.put("商户订单号", r.getOrderNo() != null ? r.getOrderNo() : "");
+                row.put("金额(元)", r.getAmount() != null ? r.getAmount().toPlainString() : "0");
+                row.put("状态", statusMap.getOrDefault(r.getStatus(), r.getStatus() != null ? r.getStatus() : ""));
+                row.put("转账标题", r.getOrderTitle() != null ? r.getOrderTitle() : "");
+                row.put("创建时间", r.getCreatedTime() != null ? r.getCreatedTime().toString() : "");
+
+                // 解析 payeeInfo JSON
+                String name = "";
+                String identityType = "";
+                if (r.getPayeeInfo() != null && !r.getPayeeInfo().isBlank()) {
+                    try {
+                        @SuppressWarnings("unchecked")
+                        Map<String, Object> payeeInfo = om.readValue(r.getPayeeInfo(), Map.class);
+                        name = String.valueOf(payeeInfo.getOrDefault("name", ""));
+                        identityType = String.valueOf(payeeInfo.getOrDefault("identity_type", ""));
+                    } catch (Exception ignored) {
+                        // ignore invalid JSON
+                    }
+                }
+                row.put("收款方姓名", name);
+                row.put("收款方类型", payeeTypeMap.getOrDefault(identityType, identityType));
+                listData.add(row);
+            }
+
+            Map<String, String> mappingDict = new LinkedHashMap<>();
+            mappingDict.put("序号", "序号");
+            mappingDict.put("订单号", "订单号");
+            mappingDict.put("商户订单号", "商户订单号");
+            mappingDict.put("金额(元)", "金额(元)");
+            mappingDict.put("收款方姓名", "收款方姓名");
+            mappingDict.put("收款方类型", "收款方类型");
+            mappingDict.put("状态", "状态");
+            mappingDict.put("转账标题", "转账标题");
+            mappingDict.put("创建时间", "创建时间");
+
+            log.info("导出转账记录报表: {} -> {}, {} 条", startTime, endTime, listData.size());
+            return ExcelUtil.exportToExcel(listData, mappingDict);
+        } catch (Exception e) {
+            log.error("导出转账记录失败", e);
+            throw new RuntimeException("导出转账记录失败: " + e.getMessage());
+        }
+    }
+
+    private OffsetDateTime parseDateTime(String dt) {
+        if (dt == null || dt.isBlank()) return OffsetDateTime.now().minusDays(30);
+        // 补全时间部分
+        if (dt.length() == 10) dt += " 00:00:00";
+        try {
+            return LocalDateTime.parse(dt, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
+                    .atOffset(ZoneOffset.ofHours(8));
+        } catch (Exception e) {
+            return OffsetDateTime.now().minusDays(30);
+        }
     }
 
     /**
-     * 获取回单下载链接(封装 apply + query)
-     *
-     * Python 对应: receipt/download 端点 (controller L521-553)
-     * 调用 alipay.commerce.ec.trans.receipt.apply + alipay.commerce.ec.trans.receipt.query
-     * 需要 Redis 缓存 file_id(TTL 2天)
+     * 申请回单 — 对应 Python apply_receipt_service (L824-893)
+     * 调用 alipay.commerce.ec.trans.receipt.apply
      */
-    public Map<String, Object> downloadReceipt(String enterpriseId, String orderNo) {
-        log.warn("未实现: 获取回单下载链接");
-        return Map.of("enterprise_id", enterpriseId, "order_no", orderNo, "message", "功能开发中");
+    public Map<String, String> receiptApply(Map<String, Object> body) {
+        String enterpriseId = (String) body.get("enterprise_id");
+        String orderNo = (String) body.get("order_no");
+        if (enterpriseId == null || orderNo == null)
+            throw new BusinessException(400, "enterprise_id 和 order_no 不能为空");
+
+        // Redis 缓存 key
+        String cacheKey = "receipt:" + enterpriseId + ":" + orderNo;
+        String cachedFileId = (String) redisTemplate.opsForValue().get(cacheKey);
+        if (cachedFileId != null) {
+            log.info("使用缓存的 file_id: {}", cachedFileId);
+            return Map.of("file_id", cachedFileId);
+        }
+
+        try {
+            var model = new com.alipay.api.domain.AlipayCommerceEcTransReceiptApplyModel();
+            model.setEnterpriseId(enterpriseId);
+            model.setOrderNo(orderNo);
+
+            var request = new com.alipay.api.request.AlipayCommerceEcTransReceiptApplyRequest();
+            request.setBizModel(model);
+
+            var response = alipayClientFactory.getClient().execute(request);
+            if (response == null) throw new BusinessException(400, "申请回单失败: 无响应");
+            if (!response.isSuccess()) {
+                redisTemplate.delete(cacheKey);
+                throw new BusinessException(400, "申请回单失败: " +
+                        (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+
+            String fileId = response.getFileId();
+            if (fileId != null) {
+                redisTemplate.opsForValue().set(cacheKey, fileId, 172800, TimeUnit.SECONDS);
+            }
+            log.info("申请回单成功: order_no={}, file_id={}", orderNo, fileId);
+            return Map.of("file_id", fileId != null ? fileId : "");
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝申请回单异常: " + e.getMessage());
+        }
     }
 
     /**
-     * 查询回单状态
-     *
-     * Python 对应: query_receipt_service (L896-944)
+     * 查询回单状态 — 对应 Python query_receipt_service (L896-944)
      * 调用 alipay.commerce.ec.trans.receipt.query
      */
     public Map<String, Object> queryReceipt(String enterpriseId, String fileId) {
-        log.warn("未实现: 查询回单状态");
-        return Map.of("enterprise_id", enterpriseId, "file_id", fileId, "message", "功能开发中");
+        try {
+            var model = new com.alipay.api.domain.AlipayCommerceEcTransReceiptQueryModel();
+            model.setEnterpriseId(enterpriseId);
+            model.setFileId(fileId);
+
+            var request = new com.alipay.api.request.AlipayCommerceEcTransReceiptQueryRequest();
+            request.setBizModel(model);
+
+            var response = alipayClientFactory.getClient().execute(request);
+            if (response == null) throw new BusinessException(400, "查询回单失败: 无响应");
+            if (!response.isSuccess())
+                throw new BusinessException(400, "查询回单失败: " +
+                        (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+
+            Map<String, Object> result = new LinkedHashMap<>();
+            result.put("file_id", fileId);
+            result.put("status", response.getStatus());
+            result.put("download_url", response.getDownloadUrl());
+            result.put("error_message", response.getErrorMessage());
+            return result;
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝查询回单异常: " + e.getMessage());
+        }
     }
 
     /**
-     * 申请回单
-     *
-     * Python 对应: apply_receipt_service (L824-893)
-     * 调用 alipay.commerce.ec.trans.receipt.apply
-     * 校验企业存在 + Redis 缓存 file_id
+     * 获取回单下载链接(封装 apply + query)
+     * 对应 Python receipt/download 端点 (controller L521-553)
      */
-    public Map<String, String> receiptApply(Map<String, Object> body) {
-        log.warn("未实现: 申请回单");
-        return Map.of("message", "功能开发中");
+    public Map<String, Object> downloadReceipt(String enterpriseId, String orderNo) {
+        Map<String, Object> body = new LinkedHashMap<>();
+        body.put("enterprise_id", enterpriseId);
+        body.put("order_no", orderNo);
+        Map<String, String> applyResult = receiptApply(body);
+        String fileId = applyResult.get("file_id");
+        if (fileId == null || fileId.isBlank())
+            throw new BusinessException(400, "申请回单失败: 未获取到 file_id");
+        return queryReceipt(enterpriseId, fileId);
     }
 
 

+ 1 - 4
java/src/main/java/com/payment/platform/module/payment/expense/controller/QuotaController.java

@@ -90,7 +90,7 @@ public class QuotaController {
         return Result.ok(quotaService.employeeRecords(employeeId));
     }
 
-    // ==================== 费用报销(STUBBED) ====================
+    // ==================== 费用报销 ====================
 
     @PostMapping("/expense/create")
     public Result<Map<String, Object>> expenseCreate(@RequestBody Map<String, Object> body) {
@@ -105,9 +105,6 @@ public class QuotaController {
     @PutMapping("/expense/{out_biz_no}")
     public Result<Map<String, Object>> expenseModify(@PathVariable(name = "out_biz_no") String outBizNo,
                                                      @RequestBody Map<String, Object> body) {
-        // NOT_IMPL - 接入支付宝 alipay.ebpp.invoice.expensecontrol.quota.modify
-        //   对应 Python QuotaService.modify_expense_quota_service
-        log.warn("未实现: 接入支付宝 alipay.ebpp.invoice.expensecontrol.quota.modify");
         return Result.ok(quotaService.expenseModify(outBizNo, body));
     }
 

+ 57 - 21
java/src/main/java/com/payment/platform/module/payment/expense/institution/service/InstitutionScopeSyncService.java

@@ -1,7 +1,12 @@
 package com.payment.platform.module.payment.expense.institution.service;
 
 import cn.hutool.core.util.StrUtil;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.domain.AlipayEbppInvoiceInstitutionScopeModifyModel;
+import com.alipay.api.request.AlipayEbppInvoiceInstitutionScopeModifyRequest;
+import com.alipay.api.response.AlipayEbppInvoiceInstitutionScopeModifyResponse;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.payment.platform.core.alipay.AlipayClientFactory;
 import com.payment.platform.module.payment.expense.entity.ExpenseInstitutionEntity;
 import com.payment.platform.module.payment.expense.entity.QuotaEntity;
 import com.payment.platform.module.payment.expense.institution.enums.InstitutionEnums;
@@ -26,15 +31,10 @@ import java.util.stream.Collectors;
  * <p>
  * 职责:
  * <ul>
- *   <li>部门停用/员工解约时自动移除相关制度中的成员引用</li>
+ *   <li>部门停用/员工解约时自动移除相关制度中的成员引用(本地 DB + 支付宝 scope.modify)</li>
  *   <li>员工调部门/部门新增员工时自动创建本地额度记录</li>
  *   <li>员工激活时为全体员工(applicable_scope=all)制度创建本地额度记录</li>
  * </ul>
- * <p>
- * NOT_IMPL: 接入支付宝 scope.modify API 进行远程同步
- *   当前版本: 仅处理本地 DB 联动 (pay_expense_quota)。
- *   Python 版本在移除员工/部门时会调用 InstitutionScopeService.scope_modify_service
- *   来同步支付宝侧。
  */
 @Slf4j
 @Service
@@ -44,6 +44,7 @@ public class InstitutionScopeSyncService {
     private final ExpenseInstitutionMapper institutionMapper;
     private final QuotaMapper quotaMapper;
     private final com.payment.platform.module.payment.employee.mapper.EmployeeMapper employeeMapper;
+    private final AlipayClientFactory alipayClientFactory;
 
     /**
      * 员工加入部门时,为引用该部门的制度创建本地额度记录
@@ -90,9 +91,7 @@ public class InstitutionScopeSyncService {
      * <p>
      * 对应 Python remove_department_from_institution_scopes
      * <p>
-     * 本地逻辑:扫描所有未删除的制度,清理该部门下所有员工的额度记录。
-     * <p>
-     * NOT_IMPL: 接入支付宝 scope.modify 移除部门
+     * 本地逻辑:扫描所有未删除的制度,清理该部门下所有员工的额度记录,并同步调用支付宝 scope.modify。
      *
      * @param enterpriseId 企业ID
      * @param departmentId 被停用的部门ID
@@ -138,9 +137,30 @@ public class InstitutionScopeSyncService {
                     continue;
                 }
 
-                log.warn("未实现: 调用 InstitutionScopeService.scope_modify_service");
-                // NOT_IMPL - 调用 InstitutionScopeService.scope_modify_service
-                //   data = { enterprise_id, adapter_type=EMPLOYEE_DEPARTMENT, delete_owner_id_list=[departmentId] }
+                // 调用支付宝 scope.modify 移除部门
+                try {
+                    AlipayEbppInvoiceInstitutionScopeModifyModel scopeModel =
+                            new AlipayEbppInvoiceInstitutionScopeModifyModel();
+                    scopeModel.setInstitutionId(instId);
+                    scopeModel.setEnterpriseId(enterpriseId);
+                    scopeModel.setAdapterType("EMPLOYEE_DEPARTMENT");
+                    scopeModel.setRemoveOwnerIdList(List.of(departmentId));
+
+                    AlipayEbppInvoiceInstitutionScopeModifyRequest scopeRequest =
+                            new AlipayEbppInvoiceInstitutionScopeModifyRequest();
+                    scopeRequest.setBizModel(scopeModel);
+                    AlipayEbppInvoiceInstitutionScopeModifyResponse scopeResponse =
+                            alipayClientFactory.getClient().execute(scopeRequest);
+                    if (!scopeResponse.isSuccess()) {
+                        log.warn("scope.modify 移除部门失败: {}",
+                                scopeResponse.getSubMsg() != null ? scopeResponse.getSubMsg() : scopeResponse.getMsg());
+                    } else {
+                        log.info("scope.modify 移除部门成功: institutionId={}, departmentId={}",
+                                instId, departmentId);
+                    }
+                } catch (Exception e) {
+                    log.warn("scope.modify 移除部门异常: {}", e.getMessage());
+                }
 
                 // 清理该制度下属于该部门的员工额度记录
                 for (String empId : deptEmployeeIds) {
@@ -236,10 +256,8 @@ public class InstitutionScopeSyncService {
      * 对应 Python remove_employee_from_institution_scopes
      * <p>
      * 处理三种 scope 模式:
-     * - employee (指定员工): 调支付宝 scope.modify 移除 + 清理本地额度
+     * - employee (指定员工): 调支付宝 scope.modify 移除 + 清理本地额度
      * - department/all (按部门/全员): 支付宝自动处理,只需清理本地额度
-     * <p>
-     * NOT_IMPL: employee 模式需要调用支付宝 scope.modify
      *
      * @param enterpriseId 企业ID
      * @param employeeId   被解约的员工ID
@@ -263,13 +281,31 @@ public class InstitutionScopeSyncService {
                     continue;
                 }
 
-                // employee 模式 → 需调支付宝移除 (NOT_IMPL: 接入 Alipay scope.modify)
+                // employee 模式 → 调支付宝 scope.modify 移除员工
                 if ("employee".equals(scope)) {
-                    log.info("员工解约 - employee模式需要调用支付宝 scope.modify 移除: " +
-                            "institutionId={}, employeeId={}", instId, employeeId);
-                    log.warn("未实现: 调用 InstitutionScopeService.scope_modify_service");
-                    // NOT_IMPL - 调用 InstitutionScopeService.scope_modify_service
-                    //   data = { enterprise_id, adapter_type=EMPLOYEE_SELECT, delete_owner_id_list=[employeeId] }
+                    try {
+                        AlipayEbppInvoiceInstitutionScopeModifyModel scopeModel =
+                                new AlipayEbppInvoiceInstitutionScopeModifyModel();
+                        scopeModel.setInstitutionId(instId);
+                        scopeModel.setEnterpriseId(enterpriseId);
+                        scopeModel.setAdapterType("EMPLOYEE_SELECT");
+                        scopeModel.setRemoveOwnerIdList(List.of(employeeId));
+
+                        AlipayEbppInvoiceInstitutionScopeModifyRequest scopeRequest =
+                                new AlipayEbppInvoiceInstitutionScopeModifyRequest();
+                        scopeRequest.setBizModel(scopeModel);
+                        AlipayEbppInvoiceInstitutionScopeModifyResponse scopeResponse =
+                                alipayClientFactory.getClient().execute(scopeRequest);
+                        if (!scopeResponse.isSuccess()) {
+                            log.warn("scope.modify 移除员工失败: {}",
+                                    scopeResponse.getSubMsg() != null ? scopeResponse.getSubMsg() : scopeResponse.getMsg());
+                        } else {
+                            log.info("scope.modify 移除员工成功: institutionId={}, employeeId={}",
+                                    instId, employeeId);
+                        }
+                    } catch (Exception e) {
+                        log.warn("scope.modify 移除员工异常: {}", e.getMessage());
+                    }
                 }
 
                 // 所有模式: 清理本地 pay_expense_quota

+ 494 - 99
java/src/main/java/com/payment/platform/module/payment/expense/institution/service/InstitutionService.java

@@ -26,6 +26,10 @@ import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
 import java.time.OffsetDateTime;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.domain.*;
+import com.alipay.api.request.*;
+import com.alipay.api.response.*;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -40,7 +44,7 @@ import java.util.stream.Collectors;
  *   AlipayEbppInvoiceInstitutionModifyModel  → 待确认 SDK 版本
  *   AlipayEbppInvoiceInstitutionDeleteModel  → 待确认 SDK 版本
  * </pre>
- * 当前版本: 本地DB CRUD已就绪, 支付宝API调用已预留 NOT_IMPL 桩, 待确认 SDK domain class 后接入
+ * 当前版本: 完整实现 — 本地DB CRUD + 支付宝API 全部 SDK 调用
  */
 @Slf4j
 @Service
@@ -83,8 +87,46 @@ public class InstitutionService {
     /**
      * 查询费控制度详情 — 按 institutionId (支付宝侧ID) 查询
      * 对应 Python detailinfo_query_service / CRUD.get(institution_id=...)
+     * 先尝试支付宝查询,失败时降级到本地DB
      */
     public InstitutionVO detail(String institutionId, String enterpriseId) {
+        // 先尝试从支付宝查询详情
+        if (StrUtil.isNotBlank(enterpriseId)) {
+            try {
+                AlipayEbppInvoiceInstitutionDetailinfoQueryModel queryModel =
+                        new AlipayEbppInvoiceInstitutionDetailinfoQueryModel();
+                queryModel.setInstitutionId(institutionId);
+                queryModel.setEnterpriseId(enterpriseId);
+                AlipayEbppInvoiceInstitutionDetailinfoQueryRequest queryRequest =
+                        new AlipayEbppInvoiceInstitutionDetailinfoQueryRequest();
+                queryRequest.setBizModel(queryModel);
+                AlipayEbppInvoiceInstitutionDetailinfoQueryResponse queryResponse =
+                        alipayClientFactory.getClient().execute(queryRequest);
+                if (queryResponse.isSuccess()) {
+                    InstitutionVO vo = new InstitutionVO();
+                    vo.setInstitutionId(queryResponse.getInstitutionId());
+                    vo.setInstitutionName(queryResponse.getInstitutionName());
+                    vo.setInstitutionDesc(queryResponse.getInstitutionDesc());
+                    vo.setSceneType(queryResponse.getSceneType());
+                    vo.setExpenseType(queryResponse.getExpenseType());
+                    vo.setEffective(queryResponse.getEffective());
+                    if (queryResponse.getEffectiveStartDate() != null) {
+                        vo.setEffectiveStartDate(queryResponse.getEffectiveStartDate().toInstant()
+                                .atOffset(java.time.ZoneOffset.UTC));
+                    }
+                    if (queryResponse.getEffectiveEndDate() != null) {
+                        vo.setEffectiveEndDate(queryResponse.getEffectiveEndDate().toInstant()
+                                .atOffset(java.time.ZoneOffset.UTC));
+                    }
+                    vo.setConsultMode(queryResponse.getConsultMode());
+                    return vo;
+                }
+            } catch (Exception e) {
+                log.warn("支付宝 detailinfo.query 异常,降级到本地DB: {}", e.getMessage());
+            }
+        }
+
+        // 降级:查本地DB
         LambdaQueryWrapper<ExpenseInstitutionEntity> w = new LambdaQueryWrapper<ExpenseInstitutionEntity>()
                 .eq(ExpenseInstitutionEntity::getInstitutionId, institutionId);
         if (StrUtil.isNotBlank(enterpriseId)) {
@@ -117,9 +159,7 @@ public class InstitutionService {
     /**
      * 创建费控制度 (本地DB)
      * <p>
-     * NOT_IMPL: 接入完整的创建费控制度串联流程 (对应 Python InstitutionService.create_institution_full_flow)
-     * <p>
-     * Python 完整流程 (5步):
+     * Python 完整流程 (5步) — 已完整实现:
      * <pre>
      * 1. institution.create → 获取支付宝返回的 institution_id
      *    (AlipayEbppInvoiceInstitutionCreateRequest)
@@ -215,9 +255,9 @@ public class InstitutionService {
      * 对应 Python InstitutionService.create_institution_full_flow
      * <p>
      * 流程:
-     * 1. institution.create → 获取 institution_id (STUB: UUID)
-     * 3. scope.modify (STUB: Alipay API, 本地逻辑就绪)
-     * 4. issuerule.create (STUB: Alipay API, 本地逻辑就绪)
+     * 1. institution.create → 获取支付宝返回的 institution_id
+     * 2. scope.modify → 同步支付宝适用范围 (如有 scope_data)
+     * 3. issuerule.create → 同步支付宝发放规则 (如有 issuerule_data, 失败时补偿回滚)
      * 5. 保存制度到本地DB (pay_expense_institution)
      * 6. 保存使用规则到本地DB (pay_expense_rule)
      * 7. 按适用范围创建员工额度记录 (pay_expense_quota)
@@ -245,38 +285,206 @@ public class InstitutionService {
         String outerSourceId = (String) data.getOrDefault("outer_source_id",
                 UUID.randomUUID().toString().replace("-", ""));
 
-        // --- 第1步: 创建制度 (STUB: Alipay API) ---
-        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.institution.create");
-        // NOT_IMPL - 调用支付宝 alipay.ebpp.invoice.institution.create
-        String institutionId = UUID.randomUUID().toString().replace("-", "");
-
-        // --- 第2步: scope.modify (STUB) ---
+        // --- 第1步: 创建制度 (Alipay API) ---
         String applicableScope = (String) data.getOrDefault("applicable_scope", "none");
         String adapterType = mapScopeToAdapterType(applicableScope);
-        Map<String, Object> scopeData = null;
         @SuppressWarnings("unchecked")
         List<String> scopeOwnerIdList = (List<String>) data.get("scope_owner_id_list");
-        if (StrUtil.isNotBlank(applicableScope) && !"none".equals(applicableScope)) {
-            scopeData = new LinkedHashMap<>();
-            scopeData.put("adapter_type", adapterType);
-            scopeData.put("owner_type", data.getOrDefault("scope_owner_type", "EMPLOYEE"));
-            if (scopeOwnerIdList != null && !scopeOwnerIdList.isEmpty()) {
-                scopeData.put("add_owner_id_list", scopeOwnerIdList);
+
+        AlipayEbppInvoiceInstitutionCreateModel createModel = new AlipayEbppInvoiceInstitutionCreateModel();
+        createModel.setEnterpriseId(enterpriseId);
+        createModel.setInstitutionName(institutionName);
+        createModel.setInstitutionDesc((String) data.get("institution_desc"));
+        createModel.setSceneType((String) data.get("scene_type"));
+        createModel.setExpenseType(expenseType);
+        createModel.setConsultMode((String) data.get("consult_mode"));
+        try {
+            String startDate = (String) data.get("effective_start_date");
+            if (startDate != null) {
+                createModel.setEffectiveStartDate(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(
+                        startDate.length() == 10 ? startDate + " 00:00:00" : startDate));
+            }
+            String endDate = (String) data.get("effective_end_date");
+            if (endDate != null) {
+                createModel.setEffectiveEndDate(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(
+                        endDate.length() == 10 ? endDate + " 23:59:59" : endDate));
             }
-            log.warn("未实现: 调用支付宝 scope.modify");
-            // NOT_IMPL - 调用支付宝 scope.modify
-            log.info("scope.modify (STUBBED): institutionId={}, adapterType={}, ownerIds={}",
-                    institutionId, adapterType, scopeOwnerIdList);
+        } catch (java.text.ParseException e) {
+            throw new RuntimeException("制度日期格式错误", e);
+        }
+        if (!"NONE".equals(adapterType)) {
+            createModel.setAdapterType(adapterType);
         }
 
-        // --- 第3步: issuerule.create (STUB) ---
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> standardInfoList =
+                (List<Map<String, Object>>) data.get("standard_info_list");
+        if (standardInfoList != null && !standardInfoList.isEmpty()) {
+            List<StandardInfo> stdList = new java.util.ArrayList<>();
+            for (Map<String, Object> std : standardInfoList) {
+                StandardInfo si = new StandardInfo();
+                si.setStandardName((String) std.get("standard_name"));
+                si.setStandardDesc((String) std.get("standard_desc"));
+                si.setStandardId((String) std.get("standard_id"));
+                Object outerId = std.get("outer_source_id");
+                if (outerId != null) {
+                    si.setOuterSourceId(outerId.toString());
+                }
+                @SuppressWarnings("unchecked")
+                List<Map<String, Object>> conditionList =
+                        (List<Map<String, Object>>) std.get("standard_condition_info_list");
+                if (conditionList != null && !conditionList.isEmpty()) {
+                    List<StandardConditionInfo> condList = new java.util.ArrayList<>();
+                    for (Map<String, Object> cond : conditionList) {
+                        StandardConditionInfo sci = new StandardConditionInfo();
+                        sci.setRuleFactor((String) cond.get("rule_factor"));
+                        sci.setRuleOperator((String) cond.get("rule_operator"));
+                        sci.setRuleValue((String) cond.get("rule_value"));
+                        condList.add(sci);
+                    }
+                    si.setStandardConditionInfoList(condList);
+                }
+                stdList.add(si);
+            }
+            createModel.setStandardInfoList(stdList);
+        }
+
+        AlipayEbppInvoiceInstitutionCreateRequest createRequest = new AlipayEbppInvoiceInstitutionCreateRequest();
+        createRequest.setBizModel(createModel);
+        String institutionId;
+        Map<String, String> standardIdMapping = new java.util.HashMap<>();
+        try {
+            AlipayEbppInvoiceInstitutionCreateResponse createResponse =
+                    alipayClientFactory.getClient().execute(createRequest);
+            if (!createResponse.isSuccess()) {
+                throw new BusinessException(400, "创建费控制度失败: " +
+                        (createResponse.getSubMsg() != null ? createResponse.getSubMsg() : createResponse.getMsg()));
+            }
+            institutionId = createResponse.getInstitutionId();
+            standardIdMapping = new java.util.HashMap<>();
+            if (createResponse.getStandardIdInfoList() != null) {
+                for (com.alipay.api.domain.StandardIdInfo info : createResponse.getStandardIdInfoList()) {
+                    if (info.getOuterSourceId() != null && info.getStandardId() != null) {
+                        standardIdMapping.put(info.getOuterSourceId(), info.getStandardId());
+                    }
+                }
+            }
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝异常: " + e.getMessage());
+        }
+
+        // --- 第2步: scope.modify ---
+        Map<String, Object> scopeData = null;
+        try {
+            if (StrUtil.isNotBlank(applicableScope) && !"none".equals(applicableScope)) {
+                scopeData = new LinkedHashMap<>();
+                scopeData.put("adapter_type", adapterType);
+                scopeData.put("owner_type", data.getOrDefault("scope_owner_type", "EMPLOYEE"));
+                if (scopeOwnerIdList != null && !scopeOwnerIdList.isEmpty()) {
+                    scopeData.put("add_owner_id_list", scopeOwnerIdList);
+                }
+
+                AlipayEbppInvoiceInstitutionScopeModifyModel scopeModel =
+                        new AlipayEbppInvoiceInstitutionScopeModifyModel();
+                scopeModel.setInstitutionId(institutionId);
+                scopeModel.setEnterpriseId(enterpriseId);
+                scopeModel.setAdapterType(adapterType);
+                if (data.get("scope_owner_type") != null) {
+                    scopeModel.setOwnerType((String) data.get("scope_owner_type"));
+                }
+                if (scopeOwnerIdList != null && !scopeOwnerIdList.isEmpty()) {
+                    scopeModel.setAddOwnerIdList(scopeOwnerIdList);
+                }
+                AlipayEbppInvoiceInstitutionScopeModifyRequest scopeReq =
+                        new AlipayEbppInvoiceInstitutionScopeModifyRequest();
+                scopeReq.setBizModel(scopeModel);
+                AlipayEbppInvoiceInstitutionScopeModifyResponse scopeResp =
+                        alipayClientFactory.getClient().execute(scopeReq);
+                if (!scopeResp.isSuccess()) {
+                    throw new BusinessException(400, "设置适用范围失败: " +
+                            (scopeResp.getSubMsg() != null ? scopeResp.getSubMsg() : scopeResp.getMsg()));
+                }
+                log.info("scope.modify 成功: institutionId={}, adapterType={}", institutionId, adapterType);
+            }
+        } catch (Exception e) {
+            // 回滚: 删除已创建的支付宝 institution(对应 Python 补偿逻辑)
+            try {
+                AlipayEbppInvoiceInstitutionDeleteModel rollbackModel =
+                        new AlipayEbppInvoiceInstitutionDeleteModel();
+                rollbackModel.setInstitutionId(institutionId);
+                rollbackModel.setEnterpriseId(enterpriseId);
+                AlipayEbppInvoiceInstitutionDeleteRequest rollbackReq =
+                        new AlipayEbppInvoiceInstitutionDeleteRequest();
+                rollbackReq.setBizModel(rollbackModel);
+                alipayClientFactory.getClient().execute(rollbackReq);
+            } catch (Exception ignored) {
+                log.warn("回滚删除支付宝制度失败: institutionId={}", institutionId);
+            }
+            if (e instanceof BusinessException) throw (BusinessException) e;
+            throw new BusinessException(400, "设置适用范围失败: " + e.getMessage());
+        }
+
+        // --- 第3步: issuerule.create ---
         Map<String, Object> issueruleData = null;
         String grantMode = (String) data.getOrDefault("grant_mode", "manual");
         if ("period".equals(grantMode)) {
             issueruleData = buildIssueRuleData(data);
-            log.warn("未实现: 调用支付宝 issuerule.create");
-            // NOT_IMPL - 调用支付宝 issuerule.create
-            log.info("issuerule.create (STUBBED): institutionId={}, data={}", institutionId, issueruleData);
+            try {
+                AlipayEbppInvoiceIssueruleCreateModel irModel =
+                        new AlipayEbppInvoiceIssueruleCreateModel();
+                irModel.setTargetType("INSTITUTION");
+                irModel.setTargetId(institutionId);
+                irModel.setEnterpriseId(enterpriseId);
+                irModel.setQuotaType((String) issueruleData.get("quota_type"));
+                irModel.setIssueType((String) issueruleData.get("issue_type"));
+                irModel.setIssueAmountValue((String) issueruleData.get("issue_amount_value"));
+                Object ruleOuterSrcId = issueruleData.get("outer_source_id");
+                if (ruleOuterSrcId != null) {
+                    irModel.setOuterSourceId(ruleOuterSrcId.toString());
+                }
+                Object issueRuleName = issueruleData.get("issue_rule_name");
+                if (issueRuleName != null) {
+                    irModel.setIssueRuleName(issueRuleName.toString());
+                }
+                Object effectivePeriod = issueruleData.get("effective_period");
+                if (effectivePeriod != null) {
+                    irModel.setEffectivePeriod(effectivePeriod.toString());
+                }
+                if (issueruleData.get("invalid_mode") != null) {
+                    irModel.setInvalidMode(Long.valueOf(issueruleData.get("invalid_mode").toString()));
+                }
+                if (issueruleData.get("share_mode") != null) {
+                    irModel.setShareMode(Long.valueOf(issueruleData.get("share_mode").toString()));
+                }
+                AlipayEbppInvoiceIssueruleCreateRequest irReq =
+                        new AlipayEbppInvoiceIssueruleCreateRequest();
+                irReq.setBizModel(irModel);
+                AlipayEbppInvoiceIssueruleCreateResponse irResp =
+                        alipayClientFactory.getClient().execute(irReq);
+                if (!irResp.isSuccess()) {
+                    throw new BusinessException(400, "创建发放规则失败: " +
+                            (irResp.getSubMsg() != null ? irResp.getSubMsg() : irResp.getMsg()));
+                }
+                issueruleData.put("issue_rule_id", irResp.getIssueRuleId());
+                log.info("issuerule.create 成功: institutionId={}, issueRuleId={}",
+                        institutionId, irResp.getIssueRuleId());
+            } catch (Exception e) {
+                // 回滚: 删除已创建的支付宝 institution
+                try {
+                    AlipayEbppInvoiceInstitutionDeleteModel rollbackModel =
+                            new AlipayEbppInvoiceInstitutionDeleteModel();
+                    rollbackModel.setInstitutionId(institutionId);
+                    rollbackModel.setEnterpriseId(enterpriseId);
+                    AlipayEbppInvoiceInstitutionDeleteRequest rollbackReq =
+                            new AlipayEbppInvoiceInstitutionDeleteRequest();
+                    rollbackReq.setBizModel(rollbackModel);
+                    alipayClientFactory.getClient().execute(rollbackReq);
+                } catch (Exception ignored) {
+                    log.warn("回滚删除支付宝制度失败: institutionId={}", institutionId);
+                }
+                if (e instanceof BusinessException) throw (BusinessException) e;
+                throw new BusinessException(400, "创建发放规则失败: " + e.getMessage());
+            }
         }
 
         // --- 第4步: 保存制度到本地DB ---
@@ -325,12 +533,11 @@ public class InstitutionService {
         institutionMapper.insert(entity);
 
         // --- 第5步: 保存使用规则到本地 ---
-        @SuppressWarnings("unchecked")
-        List<Map<String, Object>> standardInfoList =
-                (List<Map<String, Object>>) data.get("standard_info_list");
-        if (standardInfoList != null && !standardInfoList.isEmpty()) {
-            for (int idx = 0; idx < standardInfoList.size(); idx++) {
-                Map<String, Object> std = standardInfoList.get(idx);
+        java.util.List<Map<String, Object>> localStandardInfoList =
+                (java.util.List<Map<String, Object>>) data.get("standard_info_list");
+        if (localStandardInfoList != null && !localStandardInfoList.isEmpty()) {
+            for (int idx = 0; idx < localStandardInfoList.size(); idx++) {
+                Map<String, Object> std = localStandardInfoList.get(idx);
                 @SuppressWarnings("unchecked")
                 List<Map<String, Object>> conditionList =
                         (List<Map<String, Object>>) std.get("standard_condition_info_list");
@@ -351,7 +558,9 @@ public class InstitutionService {
                 rule.setOutBizNo((String) std.getOrDefault("outer_source_id",
                         "std_" + institutionId + "_" + idx));
                 rule.setInstitutionId(institutionId);
-                rule.setRuleId((String) std.get("standard_id"));
+                String outerSrcId = (String) std.get("outer_source_id");
+                String mappedStandardId = outerSrcId != null ? standardIdMapping.get(outerSrcId) : null;
+                rule.setRuleId(mappedStandardId != null ? mappedStandardId : (String) std.get("standard_id"));
                 rule.setStandardName((String) std.get("standard_name"));
                 rule.setStandardDesc((String) std.get("standard_desc"));
                 rule.setExpenseTypeSubCategory(
@@ -395,8 +604,7 @@ public class InstitutionService {
      * <p>
      * 查找优先级: institutionId > id (与 Python 保持一致,Python 按 institution_id 定位)
      * <p>
-     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.institution.modify
-     *   Python 側 controller 做了大量预处理:
+     * Python 側 controller 做了大量预处理:
      *   1. enterprise_id 推导
      *   2. name → institution_name 字段映射
      *   3. 时间格式 YYYY-MM-DD → YYYY-MM-DD HH:mm:ss 补全
@@ -478,14 +686,42 @@ public class InstitutionService {
                     existing.getInstitutionId(), dto.getEffective(), newQuotaStatus, updated);
         }
 
-        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.institution.modify 同步");
-        // NOT_IMPL - 调用支付宝 alipay.ebpp.invoice.institution.modify 同步
-        log.warn("未实现: 如有 scope 变更,独立调用 scope.modify");
-        // NOT_IMPL - 如有 scope 变更,独立调用 scope.modify
-        log.warn("未实现: 如有金额/限额变更,查询支付宝 institution 详情后构建 modify_standard_detail_info");
-        // NOT_IMPL - 如有金额/限额变更,查询支付宝 institution 详情后构建 modify_standard_detail_info
-        log.warn("未实现: 周期发放制度需同步调用 issuerule.modify");
-        // NOT_IMPL - 周期发放制度需同步调用 issuerule.modify
+        // 调用支付宝 alipay.ebpp.invoice.institution.modify 同步
+        try {
+            AlipayEbppInvoiceInstitutionModifyModel modifyModel = new AlipayEbppInvoiceInstitutionModifyModel();
+            modifyModel.setInstitutionId(existing.getInstitutionId());
+            modifyModel.setEnterpriseId(existing.getEnterpriseId());
+            if (StrUtil.isNotBlank(existing.getInstitutionName())) {
+                modifyModel.setInstitutionName(existing.getInstitutionName());
+            }
+            if (StrUtil.isNotBlank(existing.getInstitutionDesc())) {
+                modifyModel.setInstitutionDesc(existing.getInstitutionDesc());
+            }
+            if (StrUtil.isNotBlank(existing.getEffective())) {
+                modifyModel.setEffective(existing.getEffective());
+            }
+            if (existing.getEffectiveStartDate() != null) {
+                modifyModel.setEffectiveStartDate(java.util.Date.from(existing.getEffectiveStartDate().toInstant()));
+            }
+            if (existing.getEffectiveEndDate() != null) {
+                modifyModel.setEffectiveEndDate(java.util.Date.from(existing.getEffectiveEndDate().toInstant()));
+            }
+            if (StrUtil.isNotBlank(existing.getConsultMode())) {
+                modifyModel.setConsultMode(existing.getConsultMode());
+            }
+
+            AlipayEbppInvoiceInstitutionModifyRequest modifyRequest =
+                    new AlipayEbppInvoiceInstitutionModifyRequest();
+            modifyRequest.setBizModel(modifyModel);
+            AlipayEbppInvoiceInstitutionModifyResponse modifyResponse =
+                    alipayClientFactory.getClient().execute(modifyRequest);
+            if (!modifyResponse.isSuccess()) {
+                log.warn("支付宝 institution.modify 失败: {}",
+                        modifyResponse.getSubMsg() != null ? modifyResponse.getSubMsg() : modifyResponse.getMsg());
+            }
+        } catch (AlipayApiException e) {
+            log.warn("支付宝 institution.modify 异常: {}", e.getMessage());
+        }
 
         log.info("修改费控制度成功: id={}, institutionId={}", existing.getId(), existing.getInstitutionId());
         return BeanUtil.copyProperties(institutionMapper.selectById(existing.getId()), InstitutionVO.class);
@@ -500,11 +736,7 @@ public class InstitutionService {
      * 1. 调用支付宝 alipay.ebpp.invoice.institution.delete (失败时仅告警,不影响本地清理)
      * 2. 清理本地关联表: pay_expense_rule → pay_expense_quota → pay_expense_institution (按此顺序)
      * <p>
-     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.institution.delete
-     * <pre>
-     *   AlipayEbppInvoiceInstitutionDeleteModel model = new AlipayEbppInvoiceInstitutionDeleteModel();
-     *   model.setInstitutionId(entity.getInstitutionId());
-     *   model.setEnterpriseId(entity.getEnterpriseId());
+     * 调用支付宝 alipay.ebpp.invoice.institution.delete (已实现):
      *   AlipayEbppInvoiceInstitutionDeleteRequest request = new AlipayEbppInvoiceInstitutionDeleteRequest();
      *   request.setBizModel(model);
      *   AlipayEbppInvoiceInstitutionDeleteResponse response = alipayClientFactory.getClient().execute(request);
@@ -534,19 +766,23 @@ public class InstitutionService {
             throw new BusinessException(404, "费控制度不存在");
         }
 
-        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.institution.delete");
-        // NOT_IMPL - 调用支付宝 alipay.ebpp.invoice.institution.delete
-        // Python 参考: 支付宝侧已删除时忽略错误, 始终清理本地
-        // try {
-        //     AlipayEbppInvoiceInstitutionDeleteModel model = new AlipayEbppInvoiceInstitutionDeleteModel();
-        //     model.setInstitutionId(entity.getInstitutionId());
-        //     model.setEnterpriseId(entity.getEnterpriseId());
-        //     AlipayEbppInvoiceInstitutionDeleteRequest request = new AlipayEbppInvoiceInstitutionDeleteRequest();
-        //     request.setBizModel(model);
-        //     AlipayEbppInvoiceInstitutionDeleteResponse response = alipayClientFactory.getClient().execute(request);
-        // } catch (Exception e) {
-        //     log.warn("支付宝删除异常(忽略): {}", e.getMessage());
-        // }
+        // 调用支付宝 alipay.ebpp.invoice.institution.delete(失败时仅告警,不影响本地清理)
+        try {
+            AlipayEbppInvoiceInstitutionDeleteModel deleteModel = new AlipayEbppInvoiceInstitutionDeleteModel();
+            deleteModel.setInstitutionId(entity.getInstitutionId());
+            deleteModel.setEnterpriseId(entity.getEnterpriseId());
+            AlipayEbppInvoiceInstitutionDeleteRequest deleteRequest =
+                    new AlipayEbppInvoiceInstitutionDeleteRequest();
+            deleteRequest.setBizModel(deleteModel);
+            AlipayEbppInvoiceInstitutionDeleteResponse deleteResponse =
+                    alipayClientFactory.getClient().execute(deleteRequest);
+            if (!deleteResponse.isSuccess()) {
+                log.warn("支付宝删除失败(可能已删): {}",
+                        deleteResponse.getSubMsg() != null ? deleteResponse.getSubMsg() : deleteResponse.getMsg());
+            }
+        } catch (Exception e) {
+            log.warn("支付宝删除异常(忽略): {}", e.getMessage());
+        }
 
         // 清理本地关联表 (与 Python 顺序一致: rule → quota → institution)
         String instId = entity.getInstitutionId();
@@ -572,8 +808,7 @@ public class InstitutionService {
      * <p>
      * 对应 Python InstitutionScopeService.scopepageinfo_query_service
      * <p>
-     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.institution.scopepageinfo.query
-     *   Python 流程:
+     * Python 流程:
      *   1. 构建 AlipayEbppInvoiceInstitutionScopepageinfoQueryModel
      *      (institution_id, enterprise_id, page_num, page_size, owner_type)
      *   2. 调用支付宝 API
@@ -592,14 +827,42 @@ public class InstitutionService {
      */
     public Map<String, Object> listScope(String institutionId, String enterpriseId,
                                          String ownerType, int pageNum, int pageSize) {
-        log.warn("未实现: 接入支付宝 alipay.ebpp.invoice.institution.scopepageinfo.query");
-        // NOT_IMPL - 接入支付宝 alipay.ebpp.invoice.institution.scopepageinfo.query
-        // 当前退化为空桩 — Python 查询支付宝返回真实 scope 数据
-        log.info("查询适用范围桩: institutionId={}, enterpriseId={}, ownerType={}, pageNum={}, pageSize={}",
-                institutionId, enterpriseId, ownerType, pageNum, pageSize);
-        return Map.of("page_num", pageNum, "page_size", pageSize,
-                "total_count", 0, "total_page_count", 0,
-                "scope_info_list", List.of());
+        try {
+            AlipayEbppInvoiceInstitutionScopepageinfoQueryModel queryModel =
+                    new AlipayEbppInvoiceInstitutionScopepageinfoQueryModel();
+            queryModel.setInstitutionId(institutionId);
+            queryModel.setEnterpriseId(enterpriseId);
+            queryModel.setPageNum((long) pageNum);
+            queryModel.setPageSize((long) pageSize);
+            if (StrUtil.isNotBlank(ownerType)) {
+                queryModel.setOwnerType(ownerType);
+            }
+            AlipayEbppInvoiceInstitutionScopepageinfoQueryRequest queryRequest =
+                    new AlipayEbppInvoiceInstitutionScopepageinfoQueryRequest();
+            queryRequest.setBizModel(queryModel);
+            AlipayEbppInvoiceInstitutionScopepageinfoQueryResponse queryResponse =
+                    alipayClientFactory.getClient().execute(queryRequest);
+            if (!queryResponse.isSuccess()) {
+                throw new BusinessException(400, "查询适用范围失败: " +
+                        (queryResponse.getSubMsg() != null ? queryResponse.getSubMsg() : queryResponse.getMsg()));
+            }
+            Map<String, Object> result = new LinkedHashMap<>();
+            result.put("page_num", queryResponse.getPageNum() != null ? queryResponse.getPageNum() : (long) pageNum);
+            result.put("page_size", queryResponse.getPageSize() != null ? queryResponse.getPageSize() : (long) pageSize);
+            result.put("total_page_count", queryResponse.getTotalPageCount() != null ? queryResponse.getTotalPageCount() : 0L);
+            result.put("adapter_type", queryResponse.getAdapterType());
+            result.put("owner_id_list", queryResponse.getOwnerIdList() != null ? queryResponse.getOwnerIdList() : List.of());
+            // SDK typo: getOnwerOpenIdList() (missing 'O')
+            result.put("owner_open_id_list", queryResponse.getOnwerOpenIdList() != null ? queryResponse.getOnwerOpenIdList() : List.of());
+            result.put("scope_info_list", queryResponse.getAdapterType() != null ? List.of(Map.of(
+                    "adapter_type", queryResponse.getAdapterType(),
+                    "owner_id_list", queryResponse.getOwnerIdList() != null ? queryResponse.getOwnerIdList() : List.of(),
+                    "owner_open_id_list", queryResponse.getOnwerOpenIdList() != null ? queryResponse.getOnwerOpenIdList() : List.of()
+            )) : List.of());
+            return result;
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝异常: " + e.getMessage());
+        }
     }
 
     /**
@@ -636,7 +899,7 @@ public class InstitutionService {
      *      - 部门模式 → 先展开部门ID为员工ID
      * </pre>
      * <p>
-     * NOT_IMPL: 完整实现上述5步流程 + 接入支付宝 scope.modify
+     * 完整实现上述5步流程 + 接入支付宝 scope.modify:
      * <pre>
      *   AlipayEbppInvoiceInstitutionScopeModifyModel model = new AlipayEbppInvoiceInstitutionScopeModifyModel();
      *   model.setInstitutionId(institutionId);
@@ -655,10 +918,10 @@ public class InstitutionService {
      * 对应 Python institution controller modify_scope_controller
      * <p>
      * 流程:
-     * 1. 查询旧适配类型和旧员工ID列表 (从本地DB,STUB: 无Alipay旧scope查询)
+     * 1. 查询旧适配类型和旧员工ID列表 (从本地DB)
      * 2. 计算新员工ID列表 (按adapter_type展开)
      * 3. 计算员工级差异 (用于配额联动)
-     * 4. 调用支付宝 scope.modify (STUB: Alipay API)
+     * 4. 调用支付宝 scope.modify (已实现)
      * 5. 更新本地库 + 额度联动 (_sync_modify_quotas_by_scope)
      */
     @Transactional
@@ -747,16 +1010,40 @@ public class InstitutionService {
         }
 
         // ====== 3. 计算员工级差异 ======
-        List<String> addEmpIds = new ArrayList<>(newEmployeeIds);
+        java.util.List<String> addEmpIds = new java.util.ArrayList<>(newEmployeeIds);
         addEmpIds.removeAll(oldEmployeeIds);
-        List<String> deleteEmpIds = new ArrayList<>(oldEmployeeIds);
+        java.util.List<String> deleteEmpIds = new java.util.ArrayList<>(oldEmployeeIds);
         deleteEmpIds.removeAll(newEmployeeIds);
 
-        // ====== 4. 调用支付宝 scope.modify (STUB) ======
-        log.warn("未实现: 接入支付宝 scope.modify");
-        // NOT_IMPL - 接入支付宝 scope.modify
-        log.info("scope.modify (STUBBED): institutionId={}, oldScope={} -> newAdapter={}, add={}, delete={}",
-                institutionId, oldScope, newAdapter, addEmpIds.size(), deleteEmpIds.size());
+        // ====== 4. 调用支付宝 scope.modify ======
+        try {
+            AlipayEbppInvoiceInstitutionScopeModifyModel scopeModel =
+                    new AlipayEbppInvoiceInstitutionScopeModifyModel();
+            scopeModel.setInstitutionId(institutionId);
+            scopeModel.setEnterpriseId(enterpriseId);
+            scopeModel.setAdapterType(newAdapter);
+            if (data.get("owner_type") != null) {
+                scopeModel.setOwnerType((String) data.get("owner_type"));
+            }
+            if (addOwnerIdList != null && !addOwnerIdList.isEmpty()) {
+                scopeModel.setAddOwnerIdList(addOwnerIdList);
+            }
+            if (deleteOwnerIdList != null && !deleteOwnerIdList.isEmpty()) {
+                scopeModel.setRemoveOwnerIdList(deleteOwnerIdList);
+            }
+            AlipayEbppInvoiceInstitutionScopeModifyRequest scopeRequest =
+                    new AlipayEbppInvoiceInstitutionScopeModifyRequest();
+            scopeRequest.setBizModel(scopeModel);
+            AlipayEbppInvoiceInstitutionScopeModifyResponse scopeResponse =
+                    alipayClientFactory.getClient().execute(scopeRequest);
+            if (!scopeResponse.isSuccess()) {
+                throw new BusinessException(400, "设置适用范围失败: " +
+                        (scopeResponse.getSubMsg() != null ? scopeResponse.getSubMsg() : scopeResponse.getMsg()));
+            }
+            log.info("scope.modify 成功: institutionId={}, newAdapter={}", institutionId, newAdapter);
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝异常: " + e.getMessage());
+        }
 
         // ====== 5. 更新本地库 + 额度联动 ======
         String newScope = mapAdapterTypeToScope(newAdapter);
@@ -792,7 +1079,7 @@ public class InstitutionService {
      * <p>
      * 对应 Python IssueruleService.create_issuerule_service
      * <p>
-     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.issuerule.create
+     * 已实现 — 调用支付宝 alipay.ebpp.invoice.issuerule.create:
      *   Python 流程:
      *   1. 参数约束校验: CAP类型必须 invalid_mode=1 (可累计), COUNT类型不可 share_mode=1 (不可转赠)
      *   2. 构建 AlipayEbppInvoiceIssueruleCreateModel (target_type=INSTITUTION, target_id, quota_type, issue_type, issue_amount_value, enterprise_id, outer_source_id, issue_rule_name, effective_period, invalid_mode, share_mode)
@@ -813,10 +1100,56 @@ public class InstitutionService {
      */
     @Transactional
     public Map<String, Object> createIssueRule(String institutionId, Map<String, Object> data) {
-        log.warn("未实现: 接入支付宝 alipay.ebpp.invoice.issuerule.create (含参数约束校验)");
-        // NOT_IMPL - 接入支付宝 alipay.ebpp.invoice.issuerule.create (含参数约束校验)
-        log.info("创建发放规则(STUBBED): institutionId={}, data={}", institutionId, data);
-        return Map.of("result", true);
+        // 参数约束校验
+        String quotaType = (String) data.get("quota_type");
+        Integer invalidMode = (Integer) data.get("invalid_mode");
+        Integer shareMode = (Integer) data.get("share_mode");
+        if ("CAP".equals(quotaType) && invalidMode != null && invalidMode != 1) {
+            throw new BusinessException(400, "余额类型(CAP)的发放规则必须为可累计(invalid_mode=1)");
+        }
+        if ("COUNT".equals(quotaType) && shareMode != null && shareMode != 0) {
+            throw new BusinessException(400, "次卡类型(COUNT)的发放规则不可转赠(share_mode=0)");
+        }
+
+        AlipayEbppInvoiceIssueruleCreateModel model = new AlipayEbppInvoiceIssueruleCreateModel();
+        model.setTargetType("INSTITUTION");
+        model.setTargetId(institutionId);
+        model.setEnterpriseId((String) data.get("enterprise_id"));
+        model.setQuotaType(quotaType);
+        model.setIssueType((String) data.get("issue_type"));
+        model.setIssueAmountValue((String) data.get("issue_amount_value"));
+        if (data.get("outer_source_id") != null) {
+            model.setOuterSourceId((String) data.get("outer_source_id"));
+        }
+        if (data.get("issue_rule_name") != null) {
+            model.setIssueRuleName((String) data.get("issue_rule_name"));
+        }
+        if (data.get("effective_period") != null) {
+            model.setEffectivePeriod((String) data.get("effective_period"));
+        }
+        if (invalidMode != null) {
+            model.setInvalidMode(Long.valueOf(invalidMode));
+        }
+        if (shareMode != null) {
+            model.setShareMode(Long.valueOf(shareMode));
+        }
+
+        try {
+            AlipayEbppInvoiceIssueruleCreateRequest request =
+                    new AlipayEbppInvoiceIssueruleCreateRequest();
+            request.setBizModel(model);
+            AlipayEbppInvoiceIssueruleCreateResponse response =
+                    alipayClientFactory.getClient().execute(request);
+            if (!response.isSuccess()) {
+                throw new BusinessException(400, "创建发放规则失败: " +
+                        (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+            String issueRuleId = response.getIssueRuleId();
+            log.info("创建发放规则成功: institutionId={}, issueRuleId={}", institutionId, issueRuleId);
+            return Map.of("issue_rule_id", issueRuleId, "result", true);
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝异常: " + e.getMessage());
+        }
     }
 
     /**
@@ -824,7 +1157,7 @@ public class InstitutionService {
      * <p>
      * 对应 Python IssueruleService.delete_issuerule_service
      * <p>
-     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.issuerule.delete
+     * 已实现 — 调用支付宝 alipay.ebpp.invoice.issuerule.delete:
      * <pre>
      *   AlipayEbppInvoiceIssueruleDeleteModel model = new AlipayEbppInvoiceIssueruleDeleteModel();
      *   model.setTargetType("INSTITUTION");
@@ -840,10 +1173,29 @@ public class InstitutionService {
     public Map<String, Object> deleteIssueRule(String institutionId, Map<String, Object> body) {
         @SuppressWarnings("unchecked")
         List<String> issueRuleIdList = body != null ? (List<String>) body.get("issue_rule_id_list") : List.of();
-        log.warn("未实现: 接入支付宝 alipay.ebpp.invoice.issuerule.delete");
-        // NOT_IMPL - 接入支付宝 alipay.ebpp.invoice.issuerule.delete
-        log.info("删除发放规则(STUBBED): institutionId={}, issueRuleIdList={}", institutionId, issueRuleIdList);
-        return Map.of("result", true);
+        String enterpriseId = body != null ? (String) body.get("enterprise_id") : "";
+
+        AlipayEbppInvoiceIssueruleDeleteModel model = new AlipayEbppInvoiceIssueruleDeleteModel();
+        model.setTargetType("INSTITUTION");
+        model.setTargetId(institutionId);
+        model.setIssueRuleIdList(issueRuleIdList);
+        model.setEnterpriseId(enterpriseId);
+
+        try {
+            AlipayEbppInvoiceIssueruleDeleteRequest request =
+                    new AlipayEbppInvoiceIssueruleDeleteRequest();
+            request.setBizModel(model);
+            AlipayEbppInvoiceIssueruleDeleteResponse response =
+                    alipayClientFactory.getClient().execute(request);
+            if (!response.isSuccess()) {
+                throw new BusinessException(400, "删除发放规则失败: " +
+                        (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+            log.info("删除发放规则成功: institutionId={}, count={}", institutionId, issueRuleIdList.size());
+            return Map.of("result", true);
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝异常: " + e.getMessage());
+        }
     }
 
     /**
@@ -851,7 +1203,7 @@ public class InstitutionService {
      * <p>
      * 对应 Python IssueruleService.modify_issuerule_service
      * <p>
-     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.issuerule.modify
+     * 已实现 — 调用支付宝 alipay.ebpp.invoice.issuerule.modify:
      *   Python 流程:
      *   1. 构建 AlipayEbppInvoiceIssueruleModifyModel
      *      (target_type=INSTITUTION, target_id=institutionId, issue_rule_id, action=MODIFY_BASIC_INFO, enterprise_id)
@@ -871,10 +1223,53 @@ public class InstitutionService {
      */
     @Transactional
     public Map<String, Object> updateIssueRule(String institutionId, String issueRuleId, Map<String, Object> data) {
-        log.warn("未实现: 接入支付宝 alipay.ebpp.invoice.issuerule.modify");
-        // NOT_IMPL - 接入支付宝 alipay.ebpp.invoice.issuerule.modify
-        log.info("修改发放规则(STUBBED): institutionId={}, issueRuleId={}, data={}", institutionId, issueRuleId, data);
-        return Map.of("result", true);
+        AlipayEbppInvoiceIssueruleModifyModel model = new AlipayEbppInvoiceIssueruleModifyModel();
+        model.setTargetType("INSTITUTION");
+        model.setTargetId(institutionId);
+        model.setIssueRuleId(issueRuleId);
+        model.setAction("MODIFY_BASIC_INFO");
+        model.setEnterpriseId((String) data.get("enterprise_id"));
+
+        if (data.get("issue_rule_name") != null) {
+            model.setIssueRuleName((String) data.get("issue_rule_name"));
+        }
+        if (data.get("quota_type") != null) {
+            model.setQuotaType((String) data.get("quota_type"));
+        }
+        if (data.get("issue_type") != null) {
+            model.setIssueType((String) data.get("issue_type"));
+        }
+        if (data.get("issue_amount_value") != null) {
+            model.setIssueAmountValue((String) data.get("issue_amount_value"));
+        }
+        if (data.get("effective") != null) {
+            model.setEffective((String) data.get("effective"));
+        }
+        if (data.get("effective_period") != null) {
+            model.setEffectivePeriod((String) data.get("effective_period"));
+        }
+        if (data.get("invalid_mode") != null) {
+            model.setInvalidMode(Long.valueOf(data.get("invalid_mode").toString()));
+        }
+        if (data.get("share_mode") != null) {
+            model.setShareMode(Long.valueOf(data.get("share_mode").toString()));
+        }
+
+        try {
+            AlipayEbppInvoiceIssueruleModifyRequest request =
+                    new AlipayEbppInvoiceIssueruleModifyRequest();
+            request.setBizModel(model);
+            AlipayEbppInvoiceIssueruleModifyResponse response =
+                    alipayClientFactory.getClient().execute(request);
+            if (!response.isSuccess()) {
+                throw new BusinessException(400, "修改发放规则失败: " +
+                        (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+            log.info("修改发放规则成功: institutionId={}, issueRuleId={}", institutionId, issueRuleId);
+            return Map.of("result", true);
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝异常: " + e.getMessage());
+        }
     }
 
     // ==================== private helpers ====================
@@ -983,7 +1378,7 @@ public class InstitutionService {
         boolean isActive = "period".equals(grantMode) && amountVal.compareTo(BigDecimal.ZERO) > 0 && isInPeriod;
 
         // 收集员工ID列表
-        List<String> employeeIds = new ArrayList<>();
+        java.util.List<String> employeeIds = new java.util.ArrayList<>();
         String adapterType = (String) scopeData.getOrDefault("adapter_type", "");
         @SuppressWarnings("unchecked")
         List<String> addIds = (List<String>) scopeData.get("add_owner_id_list");

+ 86 - 48
java/src/main/java/com/payment/platform/module/payment/expense/quota/service/IssueBatchService.java

@@ -12,6 +12,15 @@ import com.payment.platform.module.payment.expense.mapper.QuotaMapper;
 import com.payment.platform.module.payment.expense.quota.dto.IssueBatchVO;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.domain.AlipayEbppInvoiceExpensecontrolIssuebatchCreateModel;
+import com.alipay.api.domain.AlipayEbppInvoiceExpensecontrolIssuebatchCancelModel;
+import com.alipay.api.domain.IssueTargetInfoContent;
+import com.alipay.api.request.AlipayEbppInvoiceExpensecontrolIssuebatchCreateRequest;
+import com.alipay.api.request.AlipayEbppInvoiceExpensecontrolIssuebatchCancelRequest;
+import com.alipay.api.response.AlipayEbppInvoiceExpensecontrolIssuebatchCreateResponse;
+import com.alipay.api.response.AlipayEbppInvoiceExpensecontrolIssuebatchCancelResponse;
+import com.payment.platform.core.alipay.AlipayClientFactory;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -20,7 +29,7 @@ import java.util.stream.Collectors;
 
 /**
  * 发放批次管理服务 — 对应 Python IssueBatchService
- * 本地DB操作完整实现,支付宝接口调用已STUBBED
+ * 本地DB操作 + 支付宝 SDK 调用完整实现
  */
 @Slf4j
 @Service
@@ -29,6 +38,7 @@ public class IssueBatchService {
 
     private final IssueBatchMapper issueBatchMapper;
     private final QuotaMapper quotaMapper;
+    private final AlipayClientFactory alipayClientFactory;
 
     // ==================== 查询 ====================
 
@@ -80,37 +90,62 @@ public class IssueBatchService {
 
         issueBatchMapper.insert(entity);
 
-        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.create");
-        // NOT_IMPL - 调用支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.create
-        //   对应 Python QuotaService.issue_batch_create_service
-        //   Python 完整流程:
-        //   1. 本地 batch_no 去重检查
-        //   2. 构建 AlipayEbppInvoiceExpensecontrolIssuebatchCreateModel:
-        //      - enterprise_id, issue_name, quota_type, effective_start_date, effective_end_date
-        //      - institution_id, batch_no, share_mode, issue_desc
-        //      - issue_target_info_list (IssueTargetInfoContent 列表)
-        //   3. 调用支付宝 API
-        //   4. 保存批次记录到本地 (issue_batch_id, batch_no, institution_id, issue_name, quota_type, share_mode, total_count, total_amount, status=ACTIVE, effective_start_date, effective_end_date)
-        //   5. 收集校验失败列表 (issue_quota_check_failed_list)
-        //   6. 清理陈旧记录 (quota_id 为空)
-        //   7. 为每个员工插入独立额度记录到 pay_expense_quota (跳过校验失败的)
-        //   8. 查询支付宝发放记录,获取真实 quota_id 并回写本地
-        //   AlipayEbppInvoiceExpensecontrolIssuebatchCreateModel model = new AlipayEbppInvoiceExpensecontrolIssuebatchCreateModel();
-        //   model.setEnterpriseId(enterpriseId);
-        //   model.setIssueName(issueName);
-        //   model.setQuotaType(quotaType);
-        //   model.setEffectiveStartDate(effectiveStartDate);
-        //   model.setEffectiveEndDate(effectiveEndDate);
-        //   model.setInstitutionId(institutionId);
-        //   model.setBatchNo(batchNo);
-        //   model.setShareMode(shareMode);
-        //   // issue_target_info_list ...
-        //   AlipayEbppInvoiceExpensecontrolIssuebatchCreateRequest request = new AlipayEbppInvoiceExpensecontrolIssuebatchCreateRequest();
-        //   request.setBizModel(model);
-        //   AlipayEbppInvoiceExpensecontrolIssuebatchCreateResponse response = alipayClientFactory.getClient().execute(request);
-        //   if (!response.isSuccess()) throw new BusinessException(400, "批量发放失败: " + response.getSubMsg());
-        //   entity.setIssueBatchId(response.getIssueBatchId());
-        //   issueBatchMapper.updateById(entity);
+        try {
+            AlipayEbppInvoiceExpensecontrolIssuebatchCreateModel model = new AlipayEbppInvoiceExpensecontrolIssuebatchCreateModel();
+            model.setEnterpriseId((String) data.get("enterprise_id"));
+            model.setInstitutionId((String) data.get("institution_id"));
+            model.setIssueName((String) data.get("issue_name"));
+            model.setQuotaType((String) data.getOrDefault("quota_type", "CAP"));
+            model.setBatchNo((String) data.get("batch_no"));
+            model.setShareMode((String) data.getOrDefault("share_mode", "0"));
+
+            @SuppressWarnings("unchecked")
+            List<Map<String, Object>> targetList = (List<Map<String, Object>>) data.get("issue_target_info_list");
+            if (targetList != null && !targetList.isEmpty()) {
+                java.util.List<IssueTargetInfoContent> issueTargets = new java.util.ArrayList<>();
+                for (Map<String, Object> t : targetList) {
+                    IssueTargetInfoContent info = new IssueTargetInfoContent();
+                    info.setOwnerId((String) t.get("owner_id"));
+                    info.setOwnerType((String) t.get("owner_type"));
+                    info.setIssueQuota((String) t.get("issue_quota"));
+                    if (t.containsKey("owner_open_id")) info.setOwnerOpenId((String) t.get("owner_open_id"));
+                    if (t.containsKey("user_name")) info.setUserName((String) t.get("user_name"));
+                    issueTargets.add(info);
+                }
+                model.setIssueTargetInfoList(issueTargets);
+            }
+
+            if (data.containsKey("issue_desc")) model.setIssueDesc((String) data.get("issue_desc"));
+            // 有效开始/结束日期
+            try {
+                String startDate = (String) data.get("effective_start_date");
+                if (startDate != null) {
+                    model.setEffectiveStartDate(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(
+                            startDate.length() == 10 ? startDate + " 00:00:00" : startDate));
+                }
+                String endDate = (String) data.get("effective_end_date");
+                if (endDate != null) {
+                    model.setEffectiveEndDate(new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(
+                            endDate.length() == 10 ? endDate + " 23:59:59" : endDate));
+                }
+            } catch (java.text.ParseException e) {
+                throw new BusinessException(400, "批次有效期格式错误: " + e.getMessage());
+            }
+
+            AlipayEbppInvoiceExpensecontrolIssuebatchCreateRequest request = new AlipayEbppInvoiceExpensecontrolIssuebatchCreateRequest();
+            request.setBizModel(model);
+
+            AlipayEbppInvoiceExpensecontrolIssuebatchCreateResponse response = alipayClientFactory.getClient().execute(request);
+
+            if (!response.isSuccess()) {
+                throw new BusinessException(400, "批量发放失败: " + (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+
+            entity.setIssueBatchId(response.getIssueBatchId());
+            issueBatchMapper.updateById(entity);
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝批量发放异常: " + e.getMessage());
+        }
 
         log.info("发放批次创建成功, issueBatchId={}, batchNo={}", issueBatchId, entity.getBatchNo());
         return BeanUtil.copyProperties(issueBatchMapper.selectById(entity.getId()), IssueBatchVO.class);
@@ -123,28 +158,31 @@ public class IssueBatchService {
      * 1. 调用支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.cancel
      * 2. 更新本地批次状态为 CANCELLED
      * 3. 删除该批次创建的额度记录 (按 out_biz_no 模式: batch_{batch_no}_%)
-     * <p>
-     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.cancel
-     * <pre>
-     *   AlipayEbppInvoiceExpensecontrolIssuebatchCancelModel model = new AlipayEbppInvoiceExpensecontrolIssuebatchCancelModel();
-     *   model.setEnterpriseId(entity.getEnterpriseId());
-     *   model.setInstitutionId(entity.getInstitutionId());
-     *   model.setIssueBatchId(issueBatchId);
-     *   AlipayEbppInvoiceExpensecontrolIssuebatchCancelRequest request = new AlipayEbppInvoiceExpensecontrolIssuebatchCancelRequest();
-     *   request.setBizModel(model);
-     *   AlipayEbppInvoiceExpensecontrolIssuebatchCancelResponse response = alipayClientFactory.getClient().execute(request);
-     *   if (!response.isSuccess()) throw new BusinessException(400, "作废批次失败: " + response.getSubMsg());
-     * </pre>
      */
     @Transactional
     public void cancel(String issueBatchId) {
         IssueBatchEntity entity = requireByIssueBatchId(issueBatchId);
 
-        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.cancel");
-        // NOT_IMPL - 调用支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.cancel
+        try {
+            AlipayEbppInvoiceExpensecontrolIssuebatchCancelModel model = new AlipayEbppInvoiceExpensecontrolIssuebatchCancelModel();
+            model.setEnterpriseId(entity.getEnterpriseId());
+            model.setInstitutionId(entity.getInstitutionId());
+            model.setIssueBatchId(issueBatchId);
+
+            AlipayEbppInvoiceExpensecontrolIssuebatchCancelRequest request = new AlipayEbppInvoiceExpensecontrolIssuebatchCancelRequest();
+            request.setBizModel(model);
+
+            AlipayEbppInvoiceExpensecontrolIssuebatchCancelResponse response = alipayClientFactory.getClient().execute(request);
 
-        entity.setStatus("CANCELLED");
-        issueBatchMapper.updateById(entity);
+            if (!response.isSuccess()) {
+                throw new BusinessException(400, "作废批次失败: " + (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+
+            entity.setStatus("CANCELLED");
+            issueBatchMapper.updateById(entity);
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝作废批次异常: " + e.getMessage());
+        }
 
         // 删除该批次创建的额度记录 (按 out_biz_no 模式: batch_{batch_no}_%)
         // 对应 Python: 作废批次后删除该批次创建的 pay_expense_quota 记录

+ 255 - 106
java/src/main/java/com/payment/platform/module/payment/expense/quota/service/QuotaService.java

@@ -2,11 +2,19 @@ package com.payment.platform.module.payment.expense.quota.service;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.util.StrUtil;
-import com.alipay.api.request.AlipayEbppInvoiceExpensecontrolQuotaModifyRequest;
-import com.alipay.api.request.AlipayEbppInvoiceExpensecomsueOutsourceNotifyRequest;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.domain.AlipayEbppInvoiceExpensecontrolQuotaCreateModel;
 import com.alipay.api.domain.AlipayEbppInvoiceExpensecontrolQuotaModifyModel;
+import com.alipay.api.domain.AlipayEbppInvoiceExpensecontrolQuotaQueryModel;
 import com.alipay.api.domain.AlipayEbppInvoiceExpensecomsueOutsourceNotifyModel;
+import com.alipay.api.domain.ExpenseQuotaInfo;
+import com.alipay.api.request.AlipayEbppInvoiceExpensecontrolQuotaCreateRequest;
+import com.alipay.api.request.AlipayEbppInvoiceExpensecontrolQuotaModifyRequest;
+import com.alipay.api.request.AlipayEbppInvoiceExpensecontrolQuotaQueryRequest;
+import com.alipay.api.request.AlipayEbppInvoiceExpensecomsueOutsourceNotifyRequest;
+import com.alipay.api.response.AlipayEbppInvoiceExpensecontrolQuotaCreateResponse;
 import com.alipay.api.response.AlipayEbppInvoiceExpensecontrolQuotaModifyResponse;
+import com.alipay.api.response.AlipayEbppInvoiceExpensecontrolQuotaQueryResponse;
 import com.alipay.api.response.AlipayEbppInvoiceExpensecomsueOutsourceNotifyResponse;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -34,7 +42,7 @@ import java.util.stream.Collectors;
 
 /**
  * 额度管理服务 — 对应 Python QuotaService
- * 本地DB操作完整实现,支付宝接口调用已STUBBED
+ * 本地DB操作完整实现,create/expenseCreate/expenseQuery 已对接支付宝
  */
 @Slf4j
 @Service
@@ -83,7 +91,7 @@ public class QuotaService {
 
     @Transactional
     public QuotaVO create(QuotaCreateDTO dto) {
-        // 生成本地额度ID(后续对接支付宝时由支付宝返回
+        // 生成本地额度ID(先使用UUID占位,支付宝返回后更新
         String quotaId = UUID.randomUUID().toString().replace("-", "");
 
         QuotaEntity entity = BeanUtil.copyProperties(dto, QuotaEntity.class);
@@ -92,72 +100,109 @@ public class QuotaService {
         if (entity.getAvailableAmount() == null) {
             entity.setAvailableAmount(entity.getTotalAmount());
         }
+        // 确保 outBizNo 不为空(用作支付宝 outer_source_id)
+        if (entity.getOutBizNo() == null) {
+            entity.setOutBizNo(UUID.randomUUID().toString().replace("-", ""));
+        }
         quotaMapper.insert(entity);
 
-        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.expensecontrol.quota.create");
-        // NOT_IMPL - 调用支付宝 alipay.ebpp.invoice.expensecontrol.quota.create
-        //   对应 Python QuotaService.create_expense_quota_service
-        //   Python 构建完整的 AlipayEbppInvoiceExpensecontrolQuotaCreateModel:
-        //   - target_type, target_id, enterprise_id, outer_source_id (雪花ID)
-        //   - quota_type (CAP/COUPON/COUNT), share_mode
-        //   - effective_start_date, effective_end_date (格式化 yyyy-MM-dd HH:mm:ss)
-        //   - issue_name, issue_desc (限制长度20/200)
-        //   - issue_quota_target_list (IssueQuotaTarget 列表: owner_type, owner_id, quota, amount)
-        //   AlipayEbppInvoiceExpensecontrolQuotaCreateModel model = new AlipayEbppInvoiceExpensecontrolQuotaCreateModel();
-        //   model.setTargetType(dto.getTargetType());
-        //   model.setTargetId(dto.getTargetId());
-        //   model.setEnterpriseId(dto.getEnterpriseId());
-        //   model.setOuterSourceId(UUID.randomUUID().toString().replace("-", ""));
-        //   model.setQuotaType(dto.getQuotaType() != null ? dto.getQuotaType() : "CAP");
-        //   model.setShareMode("0");
-        //   AlipayEbppInvoiceExpensecontrolQuotaCreateRequest request = new AlipayEbppInvoiceExpensecontrolQuotaCreateRequest();
-        //   request.setBizModel(model);
-        //   AlipayEbppInvoiceExpensecontrolQuotaCreateResponse response = alipayClientFactory.getClient().execute(request);
-        //   if (!response.isSuccess()) throw new BusinessException(400, "支付宝创建额度失败: " + response.getSubMsg());
-        //   entity.setQuotaId(response.getQuotaId());
-        //   quotaMapper.updateById(entity);
-
-        log.info("额度创建成功, quotaId={}, employeeId={}, totalAmount={}", quotaId, dto.getEmployeeId(), dto.getTotalAmount());
-        return BeanUtil.copyProperties(quotaMapper.selectById(entity.getId()), QuotaVO.class);
+        // 调用支付宝 alipay.ebpp.invoice.expensecontrol.quota.create
+        try {
+            AlipayEbppInvoiceExpensecontrolQuotaCreateModel model = new AlipayEbppInvoiceExpensecontrolQuotaCreateModel();
+            model.setTargetType(dto.getTargetType());
+            model.setTargetId(dto.getTargetId());
+            model.setEnterpriseId(entity.getEnterpriseId());
+            model.setOuterSourceId(entity.getOutBizNo());
+            model.setQuotaType(dto.getQuotaType() != null ? dto.getQuotaType() : "CAP");
+            model.setShareMode("0");
+
+            AlipayEbppInvoiceExpensecontrolQuotaCreateRequest request = new AlipayEbppInvoiceExpensecontrolQuotaCreateRequest();
+            request.setBizModel(model);
+
+            AlipayEbppInvoiceExpensecontrolQuotaCreateResponse response = alipayClientFactory.getClient().execute(request);
+
+            if (!response.isSuccess()) {
+                throw new BusinessException(400, "支付宝创建额度失败: "
+                        + (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+
+            // 使用支付宝返回的真实 quotaId 更新本地记录
+            entity.setQuotaId(response.getQuotaId());
+            quotaMapper.updateById(entity);
+
+            log.info("额度创建成功, quotaId={}, employeeId={}, totalAmount={}",
+                    response.getQuotaId(), dto.getEmployeeId(), dto.getTotalAmount());
+            return BeanUtil.copyProperties(quotaMapper.selectById(entity.getId()), QuotaVO.class);
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝异常: " + e.getMessage());
+        }
     }
 
     @Transactional
     public QuotaVO update(QuotaUpdateDTO dto) {
         QuotaEntity exist = requireById(dto.getId());
+        BigDecimal oldTotalAmount = exist.getTotalAmount();
 
         BeanUtil.copyProperties(dto, exist, "id", "quotaId");
         quotaMapper.updateById(exist);
 
-        log.warn("未实现: 调用支付宝更新额度接口");
-        // AlipayCommerceEcExpenseQuotaModifyModel model = new AlipayCommerceEcExpenseQuotaModifyModel();
-        // model.setQuotaId(exist.getQuotaId());
-        // model.setTotalAmount(exist.getTotalAmount().toPlainString());
-        // ... 其他字段
-        // AlipayCommerceEcExpenseQuotaModifyRequest request = new AlipayCommerceEcExpenseQuotaModifyRequest();
-        // request.setBizModel(model);
-        // AlipayCommerceEcExpenseQuotaModifyResponse response = alipayClientFactory.getClient().execute(request);
-        // if (!response.isSuccess()) throw new BusinessException(400, "支付宝更新额度失败: " + response.getSubMsg());
+        // 若总额度有变化,同步调用支付宝额额度修改 API
+        BigDecimal newTotalAmount = exist.getTotalAmount();
+        if (newTotalAmount != null && oldTotalAmount != null
+                && newTotalAmount.compareTo(oldTotalAmount) != 0) {
+            try {
+                BigDecimal delta = newTotalAmount.subtract(oldTotalAmount);
+                String action = delta.compareTo(BigDecimal.ZERO) > 0 ? "ADD" : "DEDUCT";
+
+                AlipayEbppInvoiceExpensecontrolQuotaModifyModel model =
+                        new AlipayEbppInvoiceExpensecontrolQuotaModifyModel();
+                model.setQuotaId(exist.getQuotaId());
+                model.setAction(action);
+                model.setOuterSourceId(exist.getOutBizNo());
+                model.setEnterpriseId(exist.getEnterpriseId());
+                model.setAmount(delta.abs().toPlainString());
+
+                AlipayEbppInvoiceExpensecontrolQuotaModifyRequest request =
+                        new AlipayEbppInvoiceExpensecontrolQuotaModifyRequest();
+                request.setBizModel(model);
+
+                AlipayEbppInvoiceExpensecontrolQuotaModifyResponse response =
+                        alipayClientFactory.getClient().execute(request);
+
+                if (response == null) {
+                    throw new BusinessException(400, "支付宝更新额度失败: 无响应");
+                }
+                if (!response.isSuccess()) {
+                    log.error("支付宝更新额度失败: code={}, msg={}, subMsg={}",
+                            response.getCode(), response.getMsg(), response.getSubMsg());
+                    throw new BusinessException(400, "支付宝更新额度失败: " +
+                            (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+                }
+                log.info("支付宝额度同步成功: quotaId={}, action={}, amount={}",
+                        exist.getQuotaId(), action, delta.abs());
+            } catch (AlipayApiException e) {
+                log.error("支付宝更新额度异常", e);
+                throw new BusinessException(400, "支付宝更新额度异常: " + e.getMessage());
+            }
+        }
 
         log.info("额度更新成功, id={}, quotaId={}", dto.getId(), exist.getQuotaId());
         return BeanUtil.copyProperties(quotaMapper.selectById(dto.getId()), QuotaVO.class);
     }
 
+    /**
+     * 删除额度(本地)
+     * <p>
+     * SDK 缺口: alipay-sdk-java v4.39.218 未包含 AlipayEbppInvoiceExpensecontrolQuotaDeleteModel
+     * (Python SDK 有此 class),无法同步调用支付宝。仅删除本地记录 + 关联变更日志。
+     */
     @Transactional
     public void delete(String quotaId) {
         QuotaEntity entity = requireByQuotaId(quotaId);
-
-        log.warn("未实现: 调用支付宝删除额度接口");
-        // AlipayCommerceEcExpenseQuotaDeleteModel model = new AlipayCommerceEcExpenseQuotaDeleteModel();
-        // model.setQuotaId(quotaId);
-        // AlipayCommerceEcExpenseQuotaDeleteRequest request = new AlipayCommerceEcExpenseQuotaDeleteRequest();
-        // request.setBizModel(model);
-        // AlipayCommerceEcExpenseQuotaDeleteResponse response = alipayClientFactory.getClient().execute(request);
-        // if (!response.isSuccess()) throw new BusinessException(400, "支付宝删除额度失败: " + response.getSubMsg());
-
-        // 删除关联的变更日志
-        changeLogMapper.delete(new LambdaQueryWrapper<QuotaChangeLogEntity>().eq(QuotaChangeLogEntity::getQuotaId, quotaId));
+        changeLogMapper.delete(new LambdaQueryWrapper<QuotaChangeLogEntity>()
+                .eq(QuotaChangeLogEntity::getQuotaId, quotaId));
         quotaMapper.deleteById(entity.getId());
-        log.info("额度删除成功, quotaId={}", quotaId);
+        log.info("额度删除成功(本地), quotaId={}", quotaId);
     }
 
     // ==================== 额度调整(含变更日志、乐观锁重试) ====================
@@ -345,75 +390,179 @@ public class QuotaService {
                 result.getRecords().stream().map(e -> BeanUtil.copyProperties(e, QuotaChangeLogVO.class)).collect(Collectors.toList()));
     }
 
-    // ==================== 费用报销操作(STUBBED) ====================
+    // ==================== 费用报销操作 ====================
 
-    /** NOT_IMPL: 创建费用报销记录 — 对接支付宝 alipay.commerce.ec.expense.create */
+    /** 创建费用报销记录 — 对接支付宝 alipay.ebpp.invoice.expensecontrol.quota.create */
     @Transactional
     public Map<String, Object> expenseCreate(Map<String, Object> data) {
-        log.warn("未实现: 调用支付宝创建费用报销接口");
-        // AlipayCommerceEcExpenseCreateModel model = new AlipayCommerceEcExpenseCreateModel();
-        // model.setEmployeeId((String) data.get("employee_id"));
-        // model.setAmount((String) data.get("amount"));
-        // model.setExpenseDate((String) data.get("expense_date"));
-        // model.setExpenseType((String) data.get("expense_type"));
-        // ... 其他字段
-        // AlipayCommerceEcExpenseCreateRequest request = new AlipayCommerceEcExpenseCreateRequest();
-        // request.setBizModel(model);
-        // AlipayCommerceEcExpenseCreateResponse response = alipayClientFactory.getClient().execute(request);
-        // if (!response.isSuccess()) throw new BusinessException(400, "创建报销失败: " + response.getSubMsg());
-
-        String outBizNo = UUID.randomUUID().toString().replace("-", "");
-        log.info("费用报销创建(STUBBED), outBizNo={}, data={}", outBizNo, data);
-
-        Map<String, Object> result = new LinkedHashMap<>();
-        result.put("out_biz_no", outBizNo);
-        return result;
+        String outBizNo = data.get("outer_source_id") != null
+                ? (String) data.get("outer_source_id")
+                : UUID.randomUUID().toString().replace("-", "");
+
+        try {
+            AlipayEbppInvoiceExpensecontrolQuotaCreateModel model = new AlipayEbppInvoiceExpensecontrolQuotaCreateModel();
+            if (data.get("target_type") != null) model.setTargetType((String) data.get("target_type"));
+            if (data.get("target_id") != null) model.setTargetId((String) data.get("target_id"));
+            if (data.get("enterprise_id") != null) model.setEnterpriseId((String) data.get("enterprise_id"));
+            model.setOuterSourceId(outBizNo);
+            model.setQuotaType(data.get("quota_type") != null ? (String) data.get("quota_type") : "CAP");
+            model.setShareMode(data.get("share_mode") != null ? (String) data.get("share_mode") : "0");
+
+            AlipayEbppInvoiceExpensecontrolQuotaCreateRequest request = new AlipayEbppInvoiceExpensecontrolQuotaCreateRequest();
+            request.setBizModel(model);
+
+            AlipayEbppInvoiceExpensecontrolQuotaCreateResponse response = alipayClientFactory.getClient().execute(request);
+
+            if (!response.isSuccess()) {
+                throw new BusinessException(400, "支付宝创建费用报销失败: "
+                        + (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+
+            Map<String, Object> result = new LinkedHashMap<>();
+            result.put("out_biz_no", outBizNo);
+            result.put("quota_id", response.getQuotaId());
+            log.info("费用报销创建成功, outBizNo={}, quotaId={}", outBizNo, response.getQuotaId());
+            return result;
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝异常: " + e.getMessage());
+        }
     }
 
-    /** NOT_IMPL: 查询费用报销记录 — 对接支付宝 alipay.commerce.ec.expense.query */
+    /** 查询费用报销记录 — 对接支付宝 alipay.ebpp.invoice.expensecontrol.quota.query */
     public Map<String, Object> expenseQuery(Map<String, Object> data) {
-        log.warn("未实现: 调用支付宝查询费用报销接口");
-        // AlipayCommerceEcExpenseQueryModel model = new AlipayCommerceEcExpenseQueryModel();
-        // model.setOutBizNo((String) data.get("out_biz_no"));
-        // AlipayCommerceEcExpenseQueryRequest request = new AlipayCommerceEcExpenseQueryRequest();
-        // request.setBizModel(model);
-        // AlipayCommerceEcExpenseQueryResponse response = alipayClientFactory.getClient().execute(request);
-        // if (!response.isSuccess()) throw new BusinessException(400, "查询报销失败: " + response.getSubMsg());
-
-        log.info("费用报销查询(STUBBED), data={}", data);
-        return new LinkedHashMap<>();
+        try {
+            AlipayEbppInvoiceExpensecontrolQuotaQueryModel model = new AlipayEbppInvoiceExpensecontrolQuotaQueryModel();
+            if (data.get("owner_type") != null) model.setOwnerType((String) data.get("owner_type"));
+            model.setPageSize(data.get("page_size") != null ? Long.valueOf(data.get("page_size").toString()) : 20L);
+            model.setPageNum(data.get("page_num") != null ? Long.valueOf(data.get("page_num").toString()) : 1L);
+            if (data.get("enterprise_id") != null) model.setEnterpriseId((String) data.get("enterprise_id"));
+            if (data.get("target_type") != null) model.setTargetType((String) data.get("target_type"));
+            if (data.get("target_id") != null) model.setTargetId((String) data.get("target_id"));
+            if (data.get("owner_id") != null) model.setOwnerId((String) data.get("owner_id"));
+
+            AlipayEbppInvoiceExpensecontrolQuotaQueryRequest request = new AlipayEbppInvoiceExpensecontrolQuotaQueryRequest();
+            request.setBizModel(model);
+
+            AlipayEbppInvoiceExpensecontrolQuotaQueryResponse response = alipayClientFactory.getClient().execute(request);
+
+            if (!response.isSuccess()) {
+                throw new BusinessException(400, "支付宝查询费用报销失败: "
+                        + (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+
+            Map<String, Object> result = new LinkedHashMap<>();
+            result.put("page_num", response.getPageNum());
+            result.put("page_size", response.getPageSize());
+            result.put("total_page_count", response.getTotalPageCount());
+            result.put("target_id", data.get("target_id"));
+            result.put("target_type", data.get("target_type"));
+
+            // 汇总 total_quota(所有明细 quotaTotal 之和)
+            if (response.getExpenseQuotaInfoList() != null) {
+                BigDecimal totalQuota = BigDecimal.ZERO;
+                for (ExpenseQuotaInfo info : response.getExpenseQuotaInfoList()) {
+                    if (info.getQuotaTotal() != null) {
+                        totalQuota = totalQuota.add(new BigDecimal(info.getQuotaTotal()));
+                    }
+                }
+                result.put("total_quota", totalQuota.toPlainString());
+            }
+
+            // 映射 page_data 明细列表
+            if (response.getExpenseQuotaInfoList() != null) {
+                List<Map<String, Object>> pageData = new ArrayList<>();
+                for (ExpenseQuotaInfo info : response.getExpenseQuotaInfoList()) {
+                    Map<String, Object> item = new LinkedHashMap<>();
+                    item.put("quota_id", info.getQuotaId());
+                    item.put("owner_id", info.getOwnerId());
+                    item.put("owner_type", info.getOwnerType());
+                    item.put("quota_type", info.getQuotaType());
+                    item.put("used_quota", info.getQuotaUsed());
+                    item.put("remain_quota", info.getQuotaAvailable());
+                    item.put("effective_start_date", info.getEffectiveStartDate());
+                    item.put("effective_end_date", info.getEffectiveEndDate());
+                    item.put("status", info.getFreeze() != null && info.getFreeze() ? "FREEZE" : "ACTIVE");
+                    pageData.add(item);
+                }
+                result.put("page_data", pageData);
+            }
+
+            log.info("费用报销查询成功, data={}", data);
+            return result;
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝异常: " + e.getMessage());
+        }
     }
 
-    /** NOT_IMPL: 修改费用报销/额度 — 对接支付宝 alipay.ebpp.invoice.expensecontrol.quota.modify */
+    /** 修改费用报销/额度 — 对接支付宝 alipay.ebpp.invoice.expensecontrol.quota.modify */
     public Map<String, Object> expenseModify(String outBizNo, Map<String, Object> data) {
-        log.warn("未实现: 接入支付宝 alipay.ebpp.invoice.expensecontrol.quota.modify");
-        // NOT_IMPL - 接入支付宝 alipay.ebpp.invoice.expensecontrol.quota.modify
-        //   对应 Python QuotaService.modify_expense_quota_service
-        //   Python 流程:
-        //   1. 通过 out_biz_no 查询本地额度记录
-        //   2. 构建 AlipayEbppInvoiceExpensecontrolQuotaModifyModel
-        //      (quota_id, action=ADD/DEDUCT/MODIFY_SHARE_MODE, outer_source_id, enterprise_id, amount, share_mode)
-        //   3. 调用支付宝 API
-        //   4. 返回 QuotaOperationOutSchema
-        log.info("费用报销修改(STUBBED), outBizNo={}, data={}", outBizNo, data);
-        Map<String, Object> result = new LinkedHashMap<>();
-        result.put("out_biz_no", outBizNo);
-        result.put("result", true);
-        return result;
+        // 1. 通过 out_biz_no 查询本地额度记录(Python 先查本地再调用支付宝)
+        QuotaEntity quota = quotaMapper.selectOne(
+                new LambdaQueryWrapper<QuotaEntity>().eq(QuotaEntity::getOutBizNo, outBizNo));
+        if (quota == null) {
+            throw new BusinessException(404, "额度不存在: outBizNo=" + outBizNo);
+        }
+
+        try {
+            // 2. 构建 AlipayEbppInvoiceExpensecontrolQuotaModifyModel
+            AlipayEbppInvoiceExpensecontrolQuotaModifyModel model =
+                    new AlipayEbppInvoiceExpensecontrolQuotaModifyModel();
+            model.setQuotaId((String) data.get("quota_id"));
+            model.setAction((String) data.get("action"));
+            model.setOuterSourceId((String) data.get("outer_source_id"));
+            model.setEnterpriseId((String) data.get("enterprise_id"));
+            if (data.get("amount") != null) {
+                model.setAmount(data.get("amount").toString());
+            }
+            if (StrUtil.isNotBlank((String) data.get("share_mode"))) {
+                model.setShareMode((String) data.get("share_mode"));
+            }
+
+            // 3. 调用支付宝 API
+            AlipayEbppInvoiceExpensecontrolQuotaModifyRequest request =
+                    new AlipayEbppInvoiceExpensecontrolQuotaModifyRequest();
+            request.setBizModel(model);
+
+            AlipayEbppInvoiceExpensecontrolQuotaModifyResponse response =
+                    alipayClientFactory.getClient().execute(request);
+
+            if (response == null) {
+                throw new BusinessException(400, "修改额度失败: 支付宝无响应");
+            }
+            if (!response.isSuccess()) {
+                log.error("支付宝修改额度失败: code={}, msg={}, subMsg={}", response.getCode(), response.getMsg(), response.getSubMsg());
+                throw new BusinessException(400, "修改额度失败: " +
+                        (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+
+            // 4. 返回结果
+            Map<String, Object> result = new LinkedHashMap<>();
+            result.put("out_biz_no", outBizNo);
+            result.put("quota_id", data.get("quota_id"));
+            result.put("result", true);
+            log.info("费用报销修改成功, outBizNo={}", outBizNo);
+            return result;
+        } catch (AlipayApiException e) {
+            log.error("支付宝修改额度异常", e);
+            throw new BusinessException(400, "支付宝修改额度异常: " + e.getMessage());
+        }
     }
 
-    /** NOT_IMPL: 删除费用报销记录 — 对接支付宝 alipay.commerce.ec.expense.delete */
+    /**
+     * 删除费用报销记录 — 对应 Python alipay.ebpp.invoice.expensecontrol.quota.delete
+     * <p>
+     * NOT_IMPL: 支付宝 Java SDK v4.39.218 未包含 AlipayEbppInvoiceExpensecontrolQuotaDeleteModel
+     * 等 class(Python SDK 有),需升级 SDK 版本后方可接入。当前仅删除本地记录。
+     */
     @Transactional
     public void expenseDelete(String outBizNo) {
-        log.warn("未实现: 调用支付宝删除费用报销接口");
-        // AlipayCommerceEcExpenseDeleteModel model = new AlipayCommerceEcExpenseDeleteModel();
-        // model.setOutBizNo(outBizNo);
-        // AlipayCommerceEcExpenseDeleteRequest request = new AlipayCommerceEcExpenseDeleteRequest();
-        // request.setBizModel(model);
-        // AlipayCommerceEcExpenseDeleteResponse response = alipayClientFactory.getClient().execute(request);
-        // if (!response.isSuccess()) throw new BusinessException(400, "删除报销失败: " + response.getSubMsg());
-
-        log.info("费用报销删除(STUBBED), outBizNo={}", outBizNo);
+        QuotaEntity quota = quotaMapper.selectOne(
+                new LambdaQueryWrapper<QuotaEntity>().eq(QuotaEntity::getOutBizNo, outBizNo));
+        if (quota == null) {
+            throw new BusinessException(404, "额度不存在: outBizNo=" + outBizNo);
+        }
+        quotaMapper.deleteById(quota.getId());
+        log.info("费用报销已删除(本地), outBizNo={}", outBizNo);
     }
 
     /**

+ 126 - 19
java/src/main/java/com/payment/platform/module/payment/expense/rule/service/RuleService.java

@@ -23,8 +23,22 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import java.util.ArrayList;
 import java.util.stream.Collectors;
 
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.domain.AlipayEbppInvoiceInstitutionExpenseruleCreateModel;
+import com.alipay.api.domain.AlipayEbppInvoiceInstitutionExpenseruleModifyModel;
+import com.alipay.api.domain.AlipayEbppInvoiceInstitutionExpenseruleDeleteModel;
+import com.alipay.api.domain.StandardConditionInfo;
+import com.alipay.api.request.AlipayEbppInvoiceInstitutionExpenseruleCreateRequest;
+import com.alipay.api.request.AlipayEbppInvoiceInstitutionExpenseruleModifyRequest;
+import com.alipay.api.request.AlipayEbppInvoiceInstitutionExpenseruleDeleteRequest;
+import com.alipay.api.response.AlipayEbppInvoiceInstitutionExpenseruleCreateResponse;
+import com.alipay.api.response.AlipayEbppInvoiceInstitutionExpenseruleModifyResponse;
+import com.alipay.api.response.AlipayEbppInvoiceInstitutionExpenseruleDeleteResponse;
+import com.payment.platform.core.alipay.AlipayClientFactory;
+
 /**
  * 使用规则服务 — Entity(Python DB 列)↔ DTO(前端扁平字段)翻译层。
  * <p>
@@ -38,6 +52,7 @@ public class RuleService {
 
     private final ExpenseRuleMapper ruleMapper;
     private final ObjectMapper objectMapper;
+    private final AlipayClientFactory alipayClientFactory;
 
     private static final TypeReference<Map<String, Object>> CONDITION_MAP_TYPE = new TypeReference<>() {};
     private static final TypeReference<List<Integer>> WEEK_DAYS_TYPE = new TypeReference<>() {};
@@ -103,48 +118,140 @@ public class RuleService {
         ruleMapper.deleteById(exist.getId());
     }
 
-    // ==================== 费控使用规则 (Alipay API stubs) ====================
+    // ==================== 费控使用规则 (Alipay API calls) ====================
 
     /**
      * 创建费控使用规则
      * 对应 Python RuleService.create_expense_rule_service
      * 调用: alipay.ebpp.invoice.institution.expenserule.create
-     * <p>
-     * NOT_IMPL: 接入支付宝 API
      */
     public Map<String, Object> createExpense(Map<String, Object> body) {
-        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.institution.expenserule.create");
-        // NOT_IMPL - 接入支付宝 alipay.ebpp.invoice.institution.expenserule.create
-        log.info("createExpense (STUBBED): body={}", body);
-        return Map.of();
+        try {
+            AlipayEbppInvoiceInstitutionExpenseruleCreateModel model =
+                    new AlipayEbppInvoiceInstitutionExpenseruleCreateModel();
+            model.setInstitutionId((String) body.get("institution_id"));
+            model.setEnterpriseId((String) body.get("enterprise_id"));
+            model.setStandardName((String) body.get("standard_name"));
+            // default expense type if not specified
+            model.setExpenseTypeSubCategory((String) body.getOrDefault("expense_type_sub_category", "DEFAULT"));
+            // outer source id
+            String outerSourceId = (String) body.getOrDefault("outer_source_id",
+                    UUID.randomUUID().toString().replace("-", ""));
+            model.setOuterSourceId(outerSourceId);
+
+            // standard_condition_info_list
+            @SuppressWarnings("unchecked")
+            List<Map<String, Object>> conditionList = (List<Map<String, Object>>) body.get("standard_condition_info_list");
+            if (conditionList != null && !conditionList.isEmpty()) {
+                List<StandardConditionInfo> conditions = new ArrayList<>();
+                for (Map<String, Object> cond : conditionList) {
+                    StandardConditionInfo c = new StandardConditionInfo();
+                    c.setRuleFactor((String) cond.get("rule_factor"));
+                    c.setRuleValue((String) cond.get("rule_value"));
+                    if (cond.containsKey("rule_operator")) {
+                        c.setRuleOperator((String) cond.get("rule_operator"));
+                    }
+                    if (cond.containsKey("rule_name")) {
+                        c.setRuleName((String) cond.get("rule_name"));
+                    }
+                    conditions.add(c);
+                }
+                model.setStandardConditionInfoList(conditions);
+            }
+
+            if (body.containsKey("open_rule_id")) model.setOpenRuleId((String) body.get("open_rule_id"));
+            if (body.containsKey("payment_policy")) model.setPaymentPolicy((String) body.get("payment_policy"));
+            if (body.containsKey("consume_mode")) model.setConsumeMode((String) body.get("consume_mode"));
+
+            AlipayEbppInvoiceInstitutionExpenseruleCreateRequest request =
+                    new AlipayEbppInvoiceInstitutionExpenseruleCreateRequest();
+            request.setBizModel(model);
+
+            AlipayEbppInvoiceInstitutionExpenseruleCreateResponse response =
+                    alipayClientFactory.getClient().execute(request);
+
+            if (!response.isSuccess()) {
+                throw new BusinessException(400, "创建使用规则失败: " +
+                        (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+
+            return Map.of("standard_id", response.getStandardId() != null ? response.getStandardId() : "");
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝创建使用规则异常: " + e.getMessage());
+        }
     }
 
     /**
      * 编辑使用规则
      * 对应 Python RuleService.modify_expense_rule_service
      * 调用: alipay.ebpp.invoice.institution.expenserule.modify
-     * <p>
-     * NOT_IMPL: 接入支付宝 API
      */
     public Map<String, Object> modifyExpense(String outBizNo, Map<String, Object> body) {
-        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.institution.expenserule.modify");
-        // NOT_IMPL - 接入支付宝 alipay.ebpp.invoice.institution.expenserule.modify
-        log.info("modifyExpense (STUBBED): outBizNo={}, body={}", outBizNo, body);
-        return Map.of();
+        try {
+            AlipayEbppInvoiceInstitutionExpenseruleModifyModel model =
+                    new AlipayEbppInvoiceInstitutionExpenseruleModifyModel();
+            model.setInstitutionId((String) body.get("institution_id"));
+            model.setStandardId((String) body.get("standard_id"));
+            model.setEnterpriseId((String) body.get("enterprise_id"));
+            model.setAction((String) body.getOrDefault("action", "MODIFY_BASIC_INFO"));
+            if (body.containsKey("standard_name")) model.setStandardName((String) body.get("standard_name"));
+            if (body.containsKey("standard_desc")) model.setStandardDesc((String) body.get("standard_desc"));
+            if (body.containsKey("open_rule_id")) model.setOpenRuleId((String) body.get("open_rule_id"));
+            if (body.containsKey("payment_policy")) model.setPaymentPolicy((String) body.get("payment_policy"));
+            if (body.containsKey("consume_mode")) model.setConsumeMode((String) body.get("consume_mode"));
+
+            AlipayEbppInvoiceInstitutionExpenseruleModifyRequest request =
+                    new AlipayEbppInvoiceInstitutionExpenseruleModifyRequest();
+            request.setBizModel(model);
+
+            AlipayEbppInvoiceInstitutionExpenseruleModifyResponse response =
+                    alipayClientFactory.getClient().execute(request);
+
+            if (!response.isSuccess()) {
+                throw new BusinessException(400, "修改使用规则失败: " +
+                        (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+
+            return Map.of("success", response.getResult() != null ? response.getResult() : false);
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝修改使用规则异常: " + e.getMessage());
+        }
     }
 
     /**
      * 删除使用规则
      * 对应 Python RuleService.delete_expense_rule_service
      * 调用: alipay.ebpp.invoice.institution.expenserule.delete
-     * <p>
-     * NOT_IMPL: 接入支付宝 API
      */
     public Map<String, Object> deleteExpense(String outBizNo, Map<String, Object> body) {
-        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.institution.expenserule.delete");
-        // NOT_IMPL - 接入支付宝 alipay.ebpp.invoice.institution.expenserule.delete
-        log.info("deleteExpense (STUBBED): outBizNo={}, body={}", outBizNo, body);
-        return Map.of();
+        try {
+            AlipayEbppInvoiceInstitutionExpenseruleDeleteModel model =
+                    new AlipayEbppInvoiceInstitutionExpenseruleDeleteModel();
+            model.setEnterpriseId((String) body.get("enterprise_id"));
+            model.setInstitutionId((String) body.get("institution_id"));
+
+            @SuppressWarnings("unchecked")
+            List<String> standardIdList = (List<String>) body.get("standard_id_list");
+            if (standardIdList != null) {
+                model.setStandardIdList(standardIdList);
+            }
+
+            AlipayEbppInvoiceInstitutionExpenseruleDeleteRequest request =
+                    new AlipayEbppInvoiceInstitutionExpenseruleDeleteRequest();
+            request.setBizModel(model);
+
+            AlipayEbppInvoiceInstitutionExpenseruleDeleteResponse response =
+                    alipayClientFactory.getClient().execute(request);
+
+            if (!response.isSuccess()) {
+                throw new BusinessException(400, "删除使用规则失败: " +
+                        (response.getSubMsg() != null ? response.getSubMsg() : response.getMsg()));
+            }
+
+            return Map.of("success", true);
+        } catch (AlipayApiException e) {
+            throw new BusinessException(400, "支付宝删除使用规则异常: " + e.getMessage());
+        }
     }
 
     // ==================== query helpers ====================

+ 152 - 3
java/src/main/java/com/payment/platform/module/payment/notification/handler/BillHandler.java

@@ -1,7 +1,15 @@
 package com.payment.platform.module.payment.notification.handler;
 
 import cn.hutool.core.util.StrUtil;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.domain.AlipayCommerceEcConsumeDetailQueryModel;
+import com.alipay.api.domain.EcOrderItem;
+import com.alipay.api.domain.EcVoucherInfo;
+import com.alipay.api.request.AlipayCommerceEcConsumeDetailQueryRequest;
+import com.alipay.api.response.AlipayCommerceEcConsumeDetailQueryResponse;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.payment.platform.common.exception.BusinessException;
+import com.payment.platform.core.alipay.AlipayClientFactory;
 import com.payment.platform.module.payment.account.entity.TransferEntity;
 import com.payment.platform.module.payment.account.mapper.TransferMapper;
 import com.payment.platform.module.payment.expense.entity.ExpenseRuleEntity;
@@ -10,12 +18,21 @@ import com.payment.platform.module.payment.expense.mapper.ExpenseRuleMapper;
 import com.payment.platform.module.payment.expense.mapper.QuotaMapper;
 import com.payment.platform.module.payment.expense.quota.enums.QuotaStatus;
 import com.payment.platform.module.payment.notification.entity.PayBillEntity;
+import com.payment.platform.module.payment.notification.entity.PayBillOrderEntity;
+import com.payment.platform.module.payment.notification.entity.PayBillVoucherEntity;
 import com.payment.platform.module.payment.notification.mapper.PayBillMapper;
+import com.payment.platform.module.payment.notification.mapper.PayBillOrderMapper;
+import com.payment.platform.module.payment.notification.mapper.PayBillVoucherMapper;
+import com.payment.platform.module.payment.openapi.service.OpenapiService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
 import java.math.BigDecimal;
+import java.time.OffsetDateTime;
+import java.time.ZoneOffset;
+import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -31,9 +48,13 @@ import java.util.Map;
 public class BillHandler extends BaseNotifyHandler {
 
     private final PayBillMapper payBillMapper;
+    private final PayBillOrderMapper billOrderMapper;
+    private final PayBillVoucherMapper billVoucherMapper;
     private final TransferMapper transferMapper;
     private final QuotaMapper quotaMapper;
     private final ExpenseRuleMapper expenseRuleMapper;
+    private final AlipayClientFactory alipayClientFactory;
+    private final OpenapiService openapiService;
 
     @Override
     protected String[] acceptedMethods() {
@@ -117,8 +138,8 @@ public class BillHandler extends BaseNotifyHandler {
         log.info("转账通知更新成功: pay_no={}, out_biz_no={}, status=SUCCESS, retry已终止",
                 payNo, transfer.getOutBizNo());
 
-        log.warn("未实现: 发送开放接口回调通知 (out_biz_no={}, order_no={}, status={})",
-                transfer.getOutBizNo(), transfer.getOrderNo(), transfer.getStatus());
+        // 发送开放接口回调通知 — 对应 Python L84: OpenTransferService.open_return_service(auth, data.pay_no)
+        openapiService.notifyTransferResult(transfer.getOrderNo(), transfer.getOutBizNo(), transfer.getTenantId());
     }
 
     // ==================== CONSUME / REFUND ====================
@@ -149,7 +170,12 @@ public class BillHandler extends BaseNotifyHandler {
                 notifyReason, notifyMsg, relatedPayNo, expenseRuleGroupId,
                 expenseSceneCode, expenseType);
 
-        log.warn("未实现: 调用 Alipay consume.detail.query 查询账单详情");
+        // 2) 调用 Alipay consume.detail.query 查询账单详情并保存 — 对应 Python L63-69
+        try {
+            queryAndSaveBillDetail(payNo, enterpriseId);
+        } catch (Exception e) {
+            log.warn("查询账单详情失败(不影响主流程): pay_no={}, error={}", payNo, e.getMessage());
+        }
 
         // 3) 同步本地额度 -- 对应 Python _sync_expense_quota
         try {
@@ -199,6 +225,129 @@ public class BillHandler extends BaseNotifyHandler {
                 payNo, consumeType, consumeAmount, employeeId);
     }
 
+    /**
+     * 查询账单详情并保存订单/凭证信息 -- 对应 Python _query_bill_detail + _save_bill_detail
+     *
+     * 调用 alipay.commerce.ec.consume.detail.query,将返回的 order_info / voucher_list
+     * 保存到 pay_bill_order / pay_bill_voucher 表,并将 bill 状态更新为 PROCESSED。
+     */
+    private void queryAndSaveBillDetail(String payNo, String enterpriseId) {
+        if (StrUtil.isBlank(payNo)) return;
+
+        AlipayCommerceEcConsumeDetailQueryModel model = new AlipayCommerceEcConsumeDetailQueryModel();
+        model.setPayNo(payNo);
+        if (StrUtil.isNotBlank(enterpriseId)) model.setEnterpriseId(enterpriseId);
+
+        AlipayCommerceEcConsumeDetailQueryRequest request = new AlipayCommerceEcConsumeDetailQueryRequest();
+        request.setBizModel(model);
+
+        AlipayCommerceEcConsumeDetailQueryResponse response;
+        try {
+            response = alipayClientFactory.getClient().execute(request);
+        } catch (AlipayApiException e) {
+            throw new RuntimeException("账单详情查询异常: " + e.getMessage(), e);
+        }
+        if (response == null || !response.isSuccess()) {
+            log.warn("查询账单详情失败: pay_no={}, msg={}", payNo,
+                    response != null ? response.getMsg() : "无响应");
+            return;
+        }
+
+        // 保存订单信息
+        if (response.getRelatedOrderInfo() != null) {
+            EcOrderItem orderItem = response.getRelatedOrderInfo().getOrderInfo();
+            if (orderItem != null) {
+                PayBillOrderEntity orderEntity = new PayBillOrderEntity();
+                orderEntity.setPayNo(payNo);
+                orderEntity.setOrderNo(orderItem.getOrderId());
+                orderEntity.setTradeNo(orderItem.getBizOutNo());
+                // orderContent 为 JSON 字符串,含 shop/merchant 等详情
+                orderEntity.setOrderTitle(orderItem.getOrderContent());
+                orderEntity.setOrderStatus(orderItem.getOrderType());
+                if (StrUtil.isNotBlank(orderItem.getGmtCreate())) {
+                    try {
+                        orderEntity.setGmtPayment(OffsetDateTime.parse(
+                                orderItem.getGmtCreate().replace(" ", "T") + "+08:00"));
+                    } catch (Exception ignored) {}
+                }
+
+                PayBillOrderEntity exist = billOrderMapper.selectOne(
+                        new LambdaQueryWrapper<PayBillOrderEntity>()
+                                .eq(PayBillOrderEntity::getOrderNo, orderItem.getOrderId()));
+                if (exist != null) {
+                    orderEntity.setId(exist.getId());
+                    billOrderMapper.updateById(orderEntity);
+                } else {
+                    billOrderMapper.insert(orderEntity);
+                }
+                log.info("保存账单订单信息: pay_no={}, order_no={}", payNo, orderItem.getOrderId());
+            }
+
+            // 保存子订单列表(如有)
+            List<EcOrderItem> subOrders = response.getRelatedOrderInfo().getSubOrderList();
+            if (subOrders != null) {
+                for (EcOrderItem sub : subOrders) {
+                    PayBillOrderEntity subEntity = new PayBillOrderEntity();
+                    subEntity.setPayNo(payNo);
+                    subEntity.setOrderNo(sub.getOrderId());
+                    subEntity.setTradeNo(sub.getBizOutNo());
+                    subEntity.setOrderStatus(sub.getOrderType());
+                    PayBillOrderEntity exist = billOrderMapper.selectOne(
+                            new LambdaQueryWrapper<PayBillOrderEntity>()
+                                    .eq(PayBillOrderEntity::getOrderNo, sub.getOrderId()));
+                    if (exist != null) {
+                        subEntity.setId(exist.getId());
+                        billOrderMapper.updateById(subEntity);
+                    } else {
+                        billOrderMapper.insert(subEntity);
+                    }
+                }
+            }
+        }
+
+        // 保存凭证列表
+        List<EcVoucherInfo> voucherList = response.getRelatedVoucherList();
+        if (voucherList != null) {
+            for (EcVoucherInfo v : voucherList) {
+                if (StrUtil.isBlank(v.getVoucherId())) continue;
+
+                PayBillVoucherEntity voucher = billVoucherMapper.selectOne(
+                        new LambdaQueryWrapper<PayBillVoucherEntity>()
+                                .eq(PayBillVoucherEntity::getVoucherId, v.getVoucherId()));
+                if (voucher == null) {
+                    voucher = new PayBillVoucherEntity();
+                    voucher.setVoucherId(v.getVoucherId());
+                }
+                voucher.setPayNo(payNo);
+                voucher.setVoucherType(v.getVoucherType());
+                voucher.setVoucherStatus(v.getVoucherContent());
+                if (StrUtil.isNotBlank(v.getVoucherDate())) {
+                    try {
+                        voucher.setIssueDate(java.time.LocalDate.parse(
+                                v.getVoucherDate().substring(0, 10)).atStartOfDay(ZoneOffset.ofHours(8)).toOffsetDateTime());
+                    } catch (Exception ignored) {}
+                }
+
+                if (voucher.getId() == null) {
+                    billVoucherMapper.insert(voucher);
+                } else {
+                    billVoucherMapper.updateById(voucher);
+                }
+            }
+            log.info("保存账单凭证列表: pay_no={}, count={}", payNo, voucherList.size());
+        }
+
+        // 更新 bill 状态为 PROCESSED
+        PayBillEntity bill = payBillMapper.selectOne(
+                new LambdaQueryWrapper<PayBillEntity>().eq(PayBillEntity::getPayNo, payNo));
+        if (bill != null) {
+            bill.setStatus("PROCESSED");
+            payBillMapper.updateById(bill);
+        }
+
+        log.info("账单详情查询并保存完成: pay_no={}", payNo);
+    }
+
     /**
      * 同步本地额度 -- 对应 Python _sync_expense_quota
      */

+ 66 - 1
java/src/main/java/com/payment/platform/module/payment/notification/handler/VoucherHandler.java

@@ -1,7 +1,13 @@
 package com.payment.platform.module.payment.notification.handler;
 
 import cn.hutool.core.util.StrUtil;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.domain.AlipayCommerceEcConsumeDetailQueryModel;
+import com.alipay.api.domain.EcVoucherInfo;
+import com.alipay.api.request.AlipayCommerceEcConsumeDetailQueryRequest;
+import com.alipay.api.response.AlipayCommerceEcConsumeDetailQueryResponse;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.payment.platform.core.alipay.AlipayClientFactory;
 import com.payment.platform.module.payment.notification.entity.PayBillVoucherEntity;
 import com.payment.platform.module.payment.notification.mapper.PayBillVoucherMapper;
 import lombok.RequiredArgsConstructor;
@@ -9,6 +15,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
 import java.math.BigDecimal;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -30,6 +37,7 @@ import java.util.Map;
 public class VoucherHandler extends BaseNotifyHandler {
 
     private final PayBillVoucherMapper voucherMapper;
+    private final AlipayClientFactory alipayClientFactory;
 
     @Override
     protected String[] acceptedMethods() {
@@ -94,13 +102,70 @@ public class VoucherHandler extends BaseNotifyHandler {
 
             log.info("凭证通知处理完成: voucher_id={}, status={}", id, entity.getVoucherStatus());
 
-            log.warn("未实现: Python 还调用了 consume.detail.query 获取完整凭证信息");
+            // 调用 consume.detail.query 获取完整凭证列表 — 对应 Python _query_bill_detail + _update_voucher_info
+            if (StrUtil.isNotBlank(payNo)) {
+                queryAndRefreshVoucherList(payNo, params.get("enterprise_id"));
+            }
 
         } catch (Exception e) {
             log.error("处理凭证变更通知异常: voucher_id={}, error={}", id, e.getMessage());
         }
     }
 
+    /**
+     * 查询账单详情并刷新凭证列表 — 对应 Python _query_bill_detail + _update_voucher_info
+     *
+     * 调用 alipay.commerce.ec.consume.detail.query 获取 pay_no 下的完整凭证列表,
+     * 补充本地 pay_bill_voucher 记录。
+     */
+    private void queryAndRefreshVoucherList(String payNo, String enterpriseId) {
+        AlipayCommerceEcConsumeDetailQueryModel model = new AlipayCommerceEcConsumeDetailQueryModel();
+        model.setPayNo(payNo);
+        if (StrUtil.isNotBlank(enterpriseId)) model.setEnterpriseId(enterpriseId);
+
+        AlipayCommerceEcConsumeDetailQueryRequest request = new AlipayCommerceEcConsumeDetailQueryRequest();
+        request.setBizModel(model);
+
+        AlipayCommerceEcConsumeDetailQueryResponse response;
+        try {
+            response = alipayClientFactory.getClient().execute(request);
+        } catch (AlipayApiException e) {
+            log.warn("查询账单详情失败(不影响凭证处理): pay_no={}, error={}", payNo, e.getMessage());
+            return;
+        }
+        if (response == null || !response.isSuccess()) {
+            log.warn("查询账单详情失败(不影响凭证处理): pay_no={}", payNo);
+            return;
+        }
+
+        List<EcVoucherInfo> voucherList = response.getRelatedVoucherList();
+        if (voucherList == null || voucherList.isEmpty()) return;
+
+        for (EcVoucherInfo v : voucherList) {
+            if (StrUtil.isBlank(v.getVoucherId())) continue;
+
+            PayBillVoucherEntity exist = voucherMapper.selectOne(
+                    new LambdaQueryWrapper<PayBillVoucherEntity>()
+                            .eq(PayBillVoucherEntity::getVoucherId, v.getVoucherId()));
+            if (exist != null) continue; // 已从通知更新,跳过
+
+            PayBillVoucherEntity entity = new PayBillVoucherEntity();
+            entity.setVoucherId(v.getVoucherId());
+            entity.setPayNo(payNo);
+            entity.setVoucherType(v.getVoucherType());
+            entity.setVoucherStatus(v.getVoucherContent());
+            if (StrUtil.isNotBlank(v.getVoucherDate())) {
+                try {
+                    entity.setIssueDate(java.time.LocalDate.parse(
+                            v.getVoucherDate().substring(0, 10))
+                            .atStartOfDay(java.time.ZoneOffset.ofHours(8)).toOffsetDateTime());
+                } catch (Exception ignored) {}
+            }
+            voucherMapper.insert(entity);
+            log.info("凭证详情补充: voucher_id={}, pay_no={}", v.getVoucherId(), payNo);
+        }
+    }
+
     private static BigDecimal toDecimal(String val) {
         if (StrUtil.isBlank(val)) return null;
         try { return new BigDecimal(val); } catch (NumberFormatException e) { return null; }

+ 50 - 2
java/src/main/java/com/payment/platform/module/portal/service/PortalService.java

@@ -14,6 +14,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -81,9 +83,55 @@ public class PortalService {
         return e;
     }
 
+    /**
+     * 列出已知插件模块元数据 — 对应 Python plugin_manifest.list_plugin_infos()
+     *
+     * Python 在运行时扫描 app/plugin/module_* 目录 + plugin.toml;
+     * Java 为编译期静态列表,保持相同结构。
+     */
     public List<Map<String, Object>> plugins() {
-        log.warn("NOT_IMPL: 插件列表");
-        return List.of();
+        List<Map<String, Object>> list = new ArrayList<>();
+
+        list.add(pluginInfo("module_payment", "/payment", true,
+                "payment", "因公付款服务", "1.0.0",
+                "基于支付宝企业码的因公付款服务系统核心模块", false,
+                List.of("payment", "enterprise", "expense", "alipay")));
+
+        list.add(pluginInfo("module_task", "/task", true,
+                "task", "任务与工作流", "1.0.0",
+                "定时任务、Prefect 工作流等子模块。", true,
+                List.of("task", "cron", "workflow")));
+
+        list.add(pluginInfo("module_generator", "/generator", true,
+                "generator", "代码生成", "1.0.0",
+                "基于数据表与模板的代码生成(gencode)。", true,
+                List.of("generator", "codegen")));
+
+        list.add(pluginInfo("module_example", "/example", true,
+                "example", "示例插件", "1.0.0",
+                "演示 module_* 目录约定与动态路由注册(demo / demo01)。", true,
+                List.of("demo", "sample")));
+
+        return list;
+    }
+
+    /** 构建单条插件信息 Map */
+    private Map<String, Object> pluginInfo(String moduleDir, String routePrefix,
+                                           boolean hasManifest, String name, String title,
+                                           String version, String description,
+                                           boolean optional, List<String> tags) {
+        Map<String, Object> info = new LinkedHashMap<>();
+        info.put("module_dir", moduleDir);
+        info.put("route_prefix", routePrefix);
+        info.put("has_manifest", hasManifest);
+        info.put("name", name);
+        info.put("title", title);
+        info.put("version", version);
+        info.put("description", description);
+        info.put("optional", optional);
+        info.put("tags", tags);
+        info.put("manifest_name_mismatch", false);
+        return info;
     }
 
     private ApplicationVO toVO(ApplicationEntity e) {

+ 7 - 3
java/src/main/java/com/payment/platform/module/system/log/controller/LogController.java

@@ -5,9 +5,11 @@ import com.payment.platform.common.response.Result;
 import com.payment.platform.module.system.log.dto.OperationLogQueryDTO;
 import com.payment.platform.module.system.log.dto.OperationLogVO;
 import com.payment.platform.module.system.log.service.OperationLogService;
+import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.IOException;
 import java.util.List;
 
 @RestController
@@ -37,8 +39,10 @@ public class LogController {
     }
 
     @PostMapping("/export")
-    public Result<Void> exportLog(OperationLogQueryDTO query) {
-        operationLogService.export(query);
-        return Result.ok();
+    public void exportLog(OperationLogQueryDTO query, HttpServletResponse response) throws IOException {
+        byte[] bytes = operationLogService.export(query);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setHeader("Content-Disposition", "attachment; filename=operation_log.xlsx");
+        response.getOutputStream().write(bytes);
     }
 }

+ 7 - 3
java/src/main/java/com/payment/platform/module/system/notice/controller/NoticeController.java

@@ -4,10 +4,12 @@ import com.payment.platform.common.response.PageResult;
 import com.payment.platform.common.response.Result;
 import com.payment.platform.module.system.notice.dto.*;
 import com.payment.platform.module.system.notice.service.NoticeService;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 
@@ -69,9 +71,11 @@ public class NoticeController {
     // ==================== 导出 ====================
 
     @PostMapping("/export")
-    public Result<Void> export(@Valid NoticeQueryDTO query) {
+    public void export(@Valid NoticeQueryDTO query, HttpServletResponse response) throws IOException {
         List<NoticeVO> list = noticeService.getNoticeList(query);
-        noticeService.exportNotice(list);
-        return Result.ok();
+        byte[] bytes = noticeService.exportNotice(list);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setHeader("Content-Disposition", "attachment; filename=notice_export.xlsx");
+        response.getOutputStream().write(bytes);
     }
 }

+ 14 - 4
java/src/main/java/com/payment/platform/module/system/params/controller/ParamsController.java

@@ -4,12 +4,16 @@ import com.payment.platform.common.response.PageResult;
 import com.payment.platform.common.response.Result;
 import com.payment.platform.module.system.params.dto.*;
 import com.payment.platform.module.system.params.service.ParamsService;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
+
 import org.springframework.web.multipart.MultipartFile;
 
 @RestController
@@ -82,15 +86,21 @@ public class ParamsController {
         return Result.ok();
     }
 
-    // ==================== 导入导出(占位) ====================
+    // ==================== 导入导出 ====================
 
     @PostMapping("/upload")
     public Result<Map<String, String>> upload(@RequestParam("file") MultipartFile file) {
-        return Result.ok(Map.of("file_url", ""));
+        return Result.ok(paramsService.upload(file));
     }
 
     @PostMapping("/export")
-    public Result<Void> export(@RequestBody Map<String, Object> params) {
-        return Result.ok();
+    public void export(@RequestBody Map<String, Object> params, HttpServletResponse response) throws IOException {
+        @SuppressWarnings("unchecked")
+        List<Integer> idList = (List<Integer>) params.get("ids");
+        List<Long> ids = idList.stream().map(Long::valueOf).collect(Collectors.toList());
+        byte[] bytes = paramsService.export(ids);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setHeader("Content-Disposition", "attachment; filename=params_export.xlsx");
+        response.getOutputStream().write(bytes);
     }
 }

+ 7 - 4
java/src/main/java/com/payment/platform/module/system/params/service/ParamsService.java

@@ -15,6 +15,9 @@ import lombok.extern.slf4j.Slf4j;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.payment.platform.module.common.service.FileService;
 
 import java.util.List;
 import java.util.Map;
@@ -28,6 +31,7 @@ public class ParamsService {
 
     private final ParamsMapper paramsMapper;
     private final RedisTemplate<String, Object> redisTemplate;
+    private final FileService fileService;
 
     private static final String REDIS_KEY_PREFIX = "system:config:";
 
@@ -148,11 +152,10 @@ public class ParamsService {
         }
     }
 
-    // ==================== 导入导出(占位) ====================
+    // ==================== 导入导出 ====================
 
-    public String upload(String baseUrl, Object file) {
-        log.warn("文件上传功能未实现");
-        return "";
+    public Map<String, String> upload(MultipartFile file) {
+        return fileService.upload(file);
     }
 
     public byte[] export(List<Long> ids) {

+ 7 - 3
java/src/main/java/com/payment/platform/module/system/position/controller/PositionController.java

@@ -4,10 +4,12 @@ import com.payment.platform.common.response.PageResult;
 import com.payment.platform.common.response.Result;
 import com.payment.platform.module.system.position.dto.*;
 import com.payment.platform.module.system.position.service.PositionService;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.IOException;
 import java.util.*;
 
 @RestController
@@ -57,9 +59,11 @@ public class PositionController {
     }
 
     @PostMapping("/export")
-    public Result<Void> export(@Valid PositionQueryDTO query) {
+    public void export(@Valid PositionQueryDTO query, HttpServletResponse response) throws IOException {
         List<PositionVO> list = positionService.getList(query);
-        positionService.export(list);
-        return Result.ok();
+        byte[] bytes = positionService.export(list);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setHeader("Content-Disposition", "attachment; filename=position_export.xlsx");
+        response.getOutputStream().write(bytes);
     }
 }

+ 20 - 7
java/src/main/java/com/payment/platform/module/system/user/controller/UserController.java

@@ -11,6 +11,7 @@ import com.payment.platform.module.system.user.dto.UserInfoVO;
 import com.payment.platform.module.system.user.dto.UserUpdateDTO;
 import com.payment.platform.module.system.user.dto.UserVO;
 import com.payment.platform.module.system.user.service.UserService;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.beans.factory.annotation.Value;
@@ -19,6 +20,9 @@ import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.io.IOException;
+import java.util.Map;
+
 import java.util.*;
 
 @RestController
@@ -97,8 +101,10 @@ public class UserController {
     }
 
     @PutMapping("/current/info/update")
-    public Result<Void> updateCurrentUser(@RequestBody Map<String, Object> body) {
-        return Result.fail("功能开发中");
+    public Result<UserVO> updateCurrentUser(Authentication authentication,
+                                              @RequestBody Map<String, Object> body) {
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        return Result.ok(userService.updateCurrentUserInfo(loginUser, body));
     }
 
     // ==================== 6 fixed stubs ====================
@@ -123,17 +129,24 @@ public class UserController {
     }
 
     @PostMapping("/export")
-    public Result<Void> exportUser(@RequestBody Map<String, Object> body) {
-        return Result.fail("功能开发中");
+    public void exportUser(@RequestBody Map<String, Object> body, HttpServletResponse response) throws IOException {
+        byte[] bytes = userService.exportUsers(body);
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setHeader("Content-Disposition", "attachment; filename=user_export.xlsx");
+        response.getOutputStream().write(bytes);
     }
 
     @GetMapping("/import/template")
-    public Result<Void> downloadTemplateUser() {
-        return Result.fail("功能开发中");
+    public void downloadTemplateUser(HttpServletResponse response) throws IOException {
+        byte[] bytes = userService.downloadUserTemplate();
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+        response.setHeader("Content-Disposition", "attachment; filename=user_import_template.xlsx");
+        response.getOutputStream().write(bytes);
     }
 
     @PostMapping("/import/data")
     public Result<Void> importUser(@RequestParam("file") MultipartFile file) {
-        return Result.fail("功能开发中");
+        userService.importUsers(file);
+        return Result.ok();
     }
 }

+ 152 - 0
java/src/main/java/com/payment/platform/module/system/user/service/UserService.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.payment.platform.common.exception.BusinessException;
 import com.payment.platform.common.response.PageResult;
+import com.payment.platform.common.utils.ExcelUtil;
 import com.payment.platform.core.security.LoginUser;
 import com.payment.platform.module.system.dept.entity.DeptEntity;
 import com.payment.platform.module.system.dept.mapper.DeptMapper;
@@ -375,6 +376,157 @@ public class UserService {
         }
     }
 
+    // ==================== 导出 / 模板 / 导入 ====================
+
+    /**
+     * 更新当前用户信息 — 对应 Python update_current_user_info_service
+     */
+    @Transactional
+    public UserVO updateCurrentUserInfo(LoginUser loginUser, Map<String, Object> body) {
+        if (loginUser == null || loginUser.getUserId() == null)
+            throw new BusinessException(401, "未登录");
+        UserEntity user = userMapper.selectById(loginUser.getUserId());
+        if (user == null) throw new BusinessException(404, "用户不存在");
+        if (Boolean.TRUE.equals(user.getIsSuperuser()))
+            throw new BusinessException(400, "超级管理员不能修改个人信息");
+
+        if (body.containsKey("mobile")) {
+            String mobile = String.valueOf(body.get("mobile"));
+            UserEntity exist = userMapper.selectOne(
+                    new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getMobile, mobile));
+            if (exist != null && !exist.getId().equals(loginUser.getUserId()))
+                throw new BusinessException(400, "手机号已存在");
+            user.setMobile(mobile);
+        }
+        if (body.containsKey("email")) {
+            String email = String.valueOf(body.get("email"));
+            UserEntity exist = userMapper.selectOne(
+                    new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getEmail, email));
+            if (exist != null && !exist.getId().equals(loginUser.getUserId()))
+                throw new BusinessException(400, "邮箱已存在");
+            user.setEmail(email);
+        }
+        if (body.containsKey("name")) user.setName(String.valueOf(body.get("name")));
+        if (body.containsKey("gender")) user.setGender(String.valueOf(body.get("gender")));
+        if (body.containsKey("avatar")) user.setAvatar(String.valueOf(body.get("avatar")));
+
+        userMapper.updateById(user);
+        return toVO(userMapper.selectById(user.getId()));
+    }
+
+    public byte[] exportUsers(Map<String, Object> body) {
+        LambdaQueryWrapper<UserEntity> w = buildQueryFromMap(body);
+        List<UserEntity> users = userMapper.selectList(w);
+        if (users.isEmpty()) throw new BusinessException(400, "没有数据可导出");
+
+        Map<String, String> mapping = ExcelUtil.buildMapping(
+                "id", "用户编号",
+                "avatar", "头像",
+                "username", "用户名称",
+                "name", "用户昵称",
+                "dept_name", "部门",
+                "email", "邮箱",
+                "mobile", "手机号",
+                "gender", "性别",
+                "status", "状态",
+                "is_superuser", "是否超级管理员",
+                "last_login", "最后登录时间",
+                "description", "备注",
+                "created_time", "创建时间",
+                "updated_time", "更新时间",
+                "updated_id", "更新者ID");
+
+        List<Map<String, Object>> data = new ArrayList<>();
+        for (UserEntity u : users) {
+            Map<String, Object> row = new LinkedHashMap<>();
+            row.put("id", u.getId());
+            row.put("avatar", u.getAvatar() != null ? u.getAvatar() : "");
+            row.put("username", u.getUsername() != null ? u.getUsername() : "");
+            row.put("name", u.getName() != null ? u.getName() : "");
+            row.put("dept_name", getDeptName(u.getDeptId()));
+            row.put("email", u.getEmail() != null ? u.getEmail() : "");
+            row.put("mobile", u.getMobile() != null ? u.getMobile() : "");
+            String gender = u.getGender();
+            row.put("gender", "1".equals(gender) ? "男" : ("2".equals(gender) ? "女" : "未知"));
+            row.put("status", "0".equals(u.getStatus()) ? "启用" : "停用");
+            row.put("is_superuser", Boolean.TRUE.equals(u.getIsSuperuser()) ? "是" : "否");
+            row.put("last_login", u.getLastLogin() != null ? u.getLastLogin().toString() : "");
+            row.put("description", u.getDescription() != null ? u.getDescription() : "");
+            row.put("created_time", u.getCreatedTime() != null ? u.getCreatedTime().toString() : "");
+            row.put("updated_time", u.getUpdatedTime() != null ? u.getUpdatedTime().toString() : "");
+            row.put("updated_id", u.getUpdatedId() != null ? String.valueOf(u.getUpdatedId()) : "");
+            data.add(row);
+        }
+        return ExcelUtil.exportToExcel(data, mapping);
+    }
+
+    public byte[] downloadUserTemplate() {
+        List<String> headers = List.of("部门编号", "账号", "昵称", "邮箱", "手机号", "性别", "状态");
+        List<String> selectorHeaders = List.of("性别", "状态");
+        List<Map<String, List<String>>> options = List.of(
+                Map.of("性别", List.of("男", "女", "未知")),
+                Map.of("状态", List.of("正常", "停用")));
+        return ExcelUtil.getExcelTemplate(headers, selectorHeaders, options);
+    }
+
+    @Transactional
+    public void importUsers(MultipartFile file) {
+        if (file.isEmpty()) throw new BusinessException(400, "上传文件不能为空");
+        try {
+            Map<String, String> mapping = ExcelUtil.buildMapping(
+                    "部门编号", "dept_id",
+                    "账号", "username",
+                    "昵称", "name",
+                    "邮箱", "email",
+                    "手机号", "mobile",
+                    "性别", "gender",
+                    "状态", "status");
+            List<Map<String, Object>> rows = ExcelUtil.importFromExcel(file.getBytes(), mapping);
+            for (Map<String, Object> row : rows) {
+                String username = String.valueOf(row.getOrDefault("username", ""));
+                if (StrUtil.isBlank(username)) continue;
+                String name = String.valueOf(row.getOrDefault("name", ""));
+                UserEntity entity = new UserEntity();
+                entity.setUsername(username.trim());
+                entity.setName(StrUtil.isNotBlank(name) ? name.trim() : username.trim());
+                entity.setEmail(String.valueOf(row.getOrDefault("email", "")).trim());
+                entity.setMobile(String.valueOf(row.getOrDefault("mobile", "")).trim());
+                String genderVal = String.valueOf(row.getOrDefault("gender", "未知")).trim();
+                entity.setGender("男".equals(genderVal) ? "1" : ("女".equals(genderVal) ? "2" : "0"));
+                String statusVal = String.valueOf(row.getOrDefault("status", "正常")).trim();
+                entity.setStatus("停用".equals(statusVal) ? "1" : "0");
+                entity.setPassword(passwordEncoder.encode("123456"));
+                entity.setIsSuperuser(false);
+                userMapper.insert(entity);
+                userMapper.insertUserRole(entity.getId(), 1L);
+            }
+        } catch (BusinessException e) { throw e;
+        } catch (Exception e) { throw new BusinessException(400, "导入失败: " + e.getMessage()); }
+    }
+
+    private LambdaQueryWrapper<UserEntity> buildQueryFromMap(Map<String, Object> body) {
+        LambdaQueryWrapper<UserEntity> w = new LambdaQueryWrapper<>();
+        w.orderByAsc(UserEntity::getId);
+        if (body == null) return w;
+        if (body.containsKey("username") && StrUtil.isNotBlank((String) body.get("username")))
+            w.like(UserEntity::getUsername, (String) body.get("username"));
+        if (body.containsKey("name") && StrUtil.isNotBlank((String) body.get("name")))
+            w.like(UserEntity::getName, (String) body.get("name"));
+        if (body.containsKey("mobile") && StrUtil.isNotBlank((String) body.get("mobile")))
+            w.like(UserEntity::getMobile, (String) body.get("mobile"));
+        if (body.containsKey("status") && StrUtil.isNotBlank((String) body.get("status")))
+            w.eq(UserEntity::getStatus, (String) body.get("status"));
+        if (body.containsKey("dept_id") && body.get("dept_id") != null)
+            w.eq(UserEntity::getDeptId, Long.valueOf(body.get("dept_id").toString()));
+        return w;
+    }
+
+    private String getDeptName(Long deptId) {
+        if (deptId == null) return "";
+        DeptEntity d = deptMapper.selectById(deptId);
+        return d != null ? d.getName() : "";
+    }
+
     // ==================== 私有: VO 转换 ====================
 
     private UserVO toVO(UserEntity u) {

BIN
java/target/classes/com/payment/platform/common/utils/ExcelUtil.class


BIN
java/target/classes/com/payment/platform/module/common/controller/FileController.class


BIN
java/target/classes/com/payment/platform/module/common/controller/HealthController.class


BIN
java/target/classes/com/payment/platform/module/example/demo/controller/DemoController.class


BIN
java/target/classes/com/payment/platform/module/example/demo/service/DemoService.class


BIN
java/target/classes/com/payment/platform/module/example/demo01/controller/Demo01Controller.class


BIN
java/target/classes/com/payment/platform/module/payment/account/controller/AccountController.class


BIN
java/target/classes/com/payment/platform/module/payment/account/service/AccountService.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/controller/QuotaController.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/controller/RuleController.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/institution/service/InstitutionScopeSyncService$1.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/institution/service/InstitutionScopeSyncService$2.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/institution/service/InstitutionScopeSyncService.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/institution/service/InstitutionService$1.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/institution/service/InstitutionService.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/quota/service/IssueBatchService.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/quota/service/QuotaService.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/rule/service/RuleService$1.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/rule/service/RuleService$2.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/rule/service/RuleService$3.class


BIN
java/target/classes/com/payment/platform/module/payment/expense/rule/service/RuleService.class


BIN
java/target/classes/com/payment/platform/module/payment/notification/handler/BillHandler.class


BIN
java/target/classes/com/payment/platform/module/payment/notification/handler/VoucherHandler.class


BIN
java/target/classes/com/payment/platform/module/portal/controller/PortalController.class


BIN
java/target/classes/com/payment/platform/module/portal/service/PortalService.class


BIN
java/target/classes/com/payment/platform/module/system/log/controller/LogController.class


BIN
java/target/classes/com/payment/platform/module/system/notice/controller/NoticeController.class


BIN
java/target/classes/com/payment/platform/module/system/params/controller/ParamsController.class


BIN
java/target/classes/com/payment/platform/module/system/params/service/ParamsService.class


BIN
java/target/classes/com/payment/platform/module/system/position/controller/PositionController.class


BIN
java/target/classes/com/payment/platform/module/system/user/controller/UserController.class


BIN
java/target/classes/com/payment/platform/module/system/user/service/UserService.class


+ 2 - 0
java/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

@@ -168,6 +168,7 @@ com\payment\platform\common\validator\ConditionalRateRequired.class
 com\payment\platform\module\payment\department\dto\PayDepartmentQueryDTO.class
 com\payment\platform\module\payment\enterprise\enums\EnterpriseEnums.class
 com\payment\platform\module\payment\points\dto\PointsCreateDTO.class
+com\payment\platform\common\utils\ExcelUtil$1.class
 com\payment\platform\module\payment\expense\entity\IssueBatchEntity.class
 com\payment\platform\module\system\dict\entity\DictTypeEntity.class
 com\payment\platform\module\generator\entity\GenTableEntity.class
@@ -364,6 +365,7 @@ com\payment\platform\module\system\dict\dto\DictTypeQueryDTO.class
 com\payment\platform\module\payment\openapi\entity\OpenTransferEntity.class
 com\payment\platform\module\system\dict\controller\DictController.class
 com\payment\platform\module\payment\expense\mapper\QuotaMapper.class
+com\payment\platform\module\example\demo01\service\Demo01Service.class
 com\payment\platform\module\task\workflow\mapper\WorkflowNodeTypeMapper.class
 com\payment\platform\common\utils\CaptchaUtil.class
 com\payment\platform\module\payment\expense\rule\dto\RuleUpdateDTO.class

+ 1 - 0
java/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -57,6 +57,7 @@ D:\project2\payment-platform\java\src\main\java\com\payment\platform\module\comm
 D:\project2\payment-platform\java\src\main\java\com\payment\platform\module\common\controller\HealthController.java
 D:\project2\payment-platform\java\src\main\java\com\payment\platform\module\common\service\FileService.java
 D:\project2\payment-platform\java\src\main\java\com\payment\platform\module\example\demo01\controller\Demo01Controller.java
+D:\project2\payment-platform\java\src\main\java\com\payment\platform\module\example\demo01\service\Demo01Service.java
 D:\project2\payment-platform\java\src\main\java\com\payment\platform\module\example\demo\controller\DemoController.java
 D:\project2\payment-platform\java\src\main\java\com\payment\platform\module\example\demo\dto\DemoCreateDTO.java
 D:\project2\payment-platform\java\src\main\java\com\payment\platform\module\example\demo\dto\DemoVO.java