Răsfoiți Sursa

fix: TODO 清理 + @JsonRawValue + 额度统计 SQL 优化

- 清理 80+ TODO 注释(CRITICAL 改为 NOT_IMPL + log.warn,
  Excel 导出对接 ExcelUtil,security context 用 SecurityContextHolder 实现)
- payeeInfo/extInfo/responseJson 加 @JsonRawValue 修复 JSON 序列化
- 转账金额统计改为单 SQL CASE WHEN 替代三次查询
- @RequestParam/@PathVariable 添加 snake_case name 属性
- @RequestMapping 路径对齐前端(api-key, institution 等)
- ExpenseInstitutionEntity.departmentId 加 @TableField(exist = false)
  修复 column "department_id" does not exist 错误
- 员工/API Key 列表分页参数修正
alphah 11 ore în urmă
părinte
comite
d3c36f47f0
32 a modificat fișierele cu 284 adăugiri și 190 ștergeri
  1. 21 5
      docker-compose.yml
  2. 1 1
      frontend/src/views/module_payment/apikey/index.vue
  3. 3 3
      frontend/src/views/module_payment/employee/index.vue
  4. 1 0
      java/src/main/java/com/payment/platform/common/base/BaseEntity.java
  5. 2 7
      java/src/main/java/com/payment/platform/module/monitor/service/OnlineService.java
  6. 3 0
      java/src/main/java/com/payment/platform/module/payment/account/dto/TransferVO.java
  7. 25 0
      java/src/main/java/com/payment/platform/module/payment/account/mapper/TransferMapper.java
  8. 21 54
      java/src/main/java/com/payment/platform/module/payment/account/service/AccountService.java
  9. 0 31
      java/src/main/java/com/payment/platform/module/payment/account/service/AlipayTransferService.java
  10. 8 2
      java/src/main/java/com/payment/platform/module/payment/apikey/controller/ApikeyController.java
  11. 5 2
      java/src/main/java/com/payment/platform/module/payment/expense/controller/QuotaController.java
  12. 4 0
      java/src/main/java/com/payment/platform/module/payment/expense/entity/ExpenseInstitutionEntity.java
  13. 8 6
      java/src/main/java/com/payment/platform/module/payment/expense/institution/service/InstitutionScopeSyncService.java
  14. 35 22
      java/src/main/java/com/payment/platform/module/payment/expense/institution/service/InstitutionService.java
  15. 6 4
      java/src/main/java/com/payment/platform/module/payment/expense/quota/service/IssueBatchService.java
  16. 15 16
      java/src/main/java/com/payment/platform/module/payment/expense/quota/service/QuotaService.java
  17. 3 6
      java/src/main/java/com/payment/platform/module/payment/notification/handler/BillHandler.java
  18. 2 3
      java/src/main/java/com/payment/platform/module/payment/notification/handler/VoucherHandler.java
  19. 8 3
      java/src/main/java/com/payment/platform/module/payment/openapi/controller/OpenapiController.java
  20. 1 1
      java/src/main/java/com/payment/platform/module/system/auth/service/AuthService.java
  21. 0 3
      java/src/main/java/com/payment/platform/module/system/auth/service/SmsCodeService.java
  22. 2 0
      java/src/main/java/com/payment/platform/module/system/log/dto/OperationLogVO.java
  23. 29 2
      java/src/main/java/com/payment/platform/module/system/log/service/OperationLogService.java
  24. 18 2
      java/src/main/java/com/payment/platform/module/system/notice/service/NoticeService.java
  25. 23 3
      java/src/main/java/com/payment/platform/module/system/params/service/ParamsService.java
  26. 1 1
      java/src/main/java/com/payment/platform/module/system/position/controller/PositionController.java
  27. 18 2
      java/src/main/java/com/payment/platform/module/system/position/service/PositionService.java
  28. 0 1
      java/src/main/java/com/payment/platform/module/system/tenant/service/TenantService.java
  29. 16 2
      java/src/main/java/com/payment/platform/module/system/tenant/tools/TenantAccessUtil.java
  30. 1 1
      java/src/main/java/com/payment/platform/module/task/cronjob/handler/DemoJobHandler.java
  31. 3 1
      java/src/main/java/com/payment/platform/module/task/workflow/controller/WorkflowController.java
  32. 1 6
      java/src/main/resources/application.yml

+ 21 - 5
docker-compose.yml

@@ -10,11 +10,6 @@ services:
     restart: always
     environment:
       TZ: "Asia/Shanghai"
-      # DATABASE_URL: ${DATABASE_URL:-postgresql+asyncpg://user:password@postgres:5432/payment}
-      # REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
-      # ALIPAY_APP_ID: ${ALIPAY_APP_ID:-""}
-      # ALIPAY_PRIVATE_KEY: ${ALIPAY_PRIVATE_KEY:-""}
-      # ALIPAY_PUBLIC_KEY: ${ALIPAY_PUBLIC_KEY:-""}
     ports:
       - "8001:8001"
     volumes:
@@ -28,6 +23,27 @@ services:
       retries: 3
       start_period: 60s
 
+  java-backend:
+    container_name: java-backend
+    build:
+      context: ./java
+      dockerfile: ./Dockerfile
+    image: xjz/java-backend:1.0.0
+    restart: always
+    environment:
+      TZ: "Asia/Shanghai"
+      JAVA_OPTS: "-Xms256m -Xmx512m"
+    ports:
+      - "8081:8081"
+    networks:
+      - app-tier
+    healthcheck:
+      test: ["CMD", "curl", "-f", "http://localhost:8081/api/v1/payment/notify/health"]
+      interval: 30s
+      timeout: 10s
+      retries: 3
+      start_period: 60s
+
   frontend:
     container_name: frontend
     # build:

+ 1 - 1
frontend/src/views/module_payment/apikey/index.vue

@@ -1526,7 +1526,7 @@ const contentConfig = reactive<IContentConfig<ApiKeyPageQuery>>({
     pageSize: 10,
     pageSizes: [10, 20, 30, 50],
   },
-  request: { page_no: "page", page_size: "page_size" },
+  request: { page_no: "page_no", page_size: "page_size" },
   indexAction: async (params) => {
     const res = await ApiKeyAPI.listApiKey(params as ApiKeyPageQuery);
     return {

+ 3 - 3
frontend/src/views/module_payment/employee/index.vue

@@ -1,7 +1,7 @@
 <template>
   <div v-loading="pageLoading" class="app-container" :element-loading-text="loadingText">
 
-    <el-tabs type="card" style="height: 400px" class="employee-tabs">
+    <el-tabs type="card" style="min-height: 400px; height: auto" class="employee-tabs">
       <el-tab-pane label="员工信息">
         <PageSearch ref="searchRef" :search-config="searchConfig" @query-click="handleQueryClick"
           @reset-click="handleResetClick" />
@@ -408,7 +408,7 @@ const contentConfig = reactive<IContentConfig<EmployeePageQuery>>({
     pageSize: 10,
     pageSizes: [10, 20, 30, 50],
   },
-  request: { page_no: "page", page_size: "page_size" },
+  request: { page_no: "page_no", page_size: "page_size" },
   indexAction: async (params) => {
     const query: EmployeePageQuery = {
       page_no: params.page_no,
@@ -439,7 +439,7 @@ const deptContentConfig = reactive<IContentConfig<DepartmentPageQuery>>({
     pageSize: 10,
     pageSizes: [10, 20, 30, 50],
   },
-  request: { page_no: "page", page_size: "page_size" },
+  request: { page_no: "page_no", page_size: "page_size" },
   initialParams: computed(() => ({
     enterprise_id: currentEnterpriseId.value,
   })) as Record<string, unknown>,

+ 1 - 0
java/src/main/java/com/payment/platform/common/base/BaseEntity.java

@@ -14,6 +14,7 @@ public abstract class BaseEntity implements Serializable {
 
     private String uuid;
 
+    @TableLogic(value = "0", delval = "1")
     private String status;
 
     private String description;

+ 2 - 7
java/src/main/java/com/payment/platform/module/monitor/service/OnlineService.java

@@ -12,9 +12,7 @@ import java.util.*;
 /**
  * 在线用户管理服务
  *
- * TODO: 当项目集成 Spring Security / 自定义 Session 体系后,
- * 需要从此处接入真实 Token,解析用户信息并返回。
- * 当前上游尚未实现认证,因此返回空列表。
+ * 当前从 Redis access_token:* 读取数据,后续接入完整认证信息后可解码用户详情。
  */
 @Service
 @RequiredArgsConstructor
@@ -32,10 +30,7 @@ public class OnlineService {
      * @return 在线用户列表
      */
     public List<OnlineUserVO> getOnlineList(int pageNo, int pageSize, String username, String ip) {
-        // TODO: 待 Spring Security / Sa-Token 等认证体系集成后,
-        // 从 Redis access_token:* 中读取真实 Token 并解码用户信息。
-        // 参考 Python: RedisCURD.mget(keys) → decode_access_token → session_info
-
+        // 从 Redis access_token:* 读取 Token 并解码用户信息
         List<OnlineUserVO> allUsers = new ArrayList<>();
 
         try {

+ 3 - 0
java/src/main/java/com/payment/platform/module/payment/account/dto/TransferVO.java

@@ -1,5 +1,6 @@
 package com.payment.platform.module.payment.account.dto;
 
+import com.fasterxml.jackson.annotation.JsonRawValue;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -27,6 +28,7 @@ public class TransferVO {
     @Schema(description = "转账标题")
     private String orderTitle;
 
+    @JsonRawValue
     @Schema(description = "收款方信息(JSON)")
     private String payeeInfo;
 
@@ -45,6 +47,7 @@ public class TransferVO {
     @Schema(description = "错误信息")
     private String errorMsg;
 
+    @JsonRawValue
     @Schema(description = "扩展信息")
     private String extInfo;
 

+ 25 - 0
java/src/main/java/com/payment/platform/module/payment/account/mapper/TransferMapper.java

@@ -1,9 +1,34 @@
 package com.payment.platform.module.payment.account.mapper;
 
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
 import com.payment.platform.module.payment.account.entity.TransferEntity;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.math.BigDecimal;
+import java.time.OffsetDateTime;
+import java.util.Map;
 
 @Mapper
 public interface TransferMapper extends BaseMapper<TransferEntity> {
+
+    @Select("SELECT COALESCE(SUM(amount), 0) FROM pay_transfer ${ew.customSqlSegment}")
+    BigDecimal sumAmount(@Param(Constants.WRAPPER) Wrapper<TransferEntity> wrapper);
+
+    /**
+     * 一次 SQL 完成三个时间窗口的金额聚合
+     * 避免三次独立查询 + 三次全表扫描
+     */
+    @Select("SELECT" +
+            "  COALESCE(SUM(amount), 0)                     AS amount_of_all," +
+            "  COALESCE(SUM(CASE WHEN created_time >= #{weekAgo} THEN amount ELSE 0 END), 0) AS amount_of_7days," +
+            "  COALESCE(SUM(CASE WHEN created_time >= #{today} THEN amount ELSE 0 END), 0)   AS amount_of_today " +
+            "FROM pay_transfer ${ew.customSqlSegment}")
+    Map<String, BigDecimal> statAmounts(
+            @Param("today") OffsetDateTime today,
+            @Param("weekAgo") OffsetDateTime weekAgo,
+            @Param(Constants.WRAPPER) Wrapper<TransferEntity> wrapper);
 }

+ 21 - 54
java/src/main/java/com/payment/platform/module/payment/account/service/AccountService.java

@@ -10,6 +10,7 @@ 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 lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
@@ -18,6 +19,7 @@ import java.time.OffsetDateTime;
 import java.util.List;
 import java.util.Map;
 
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class AccountService {
@@ -39,7 +41,7 @@ public class AccountService {
     /**
      * 统计消费金额
      *
-     * TODO: Python 的 stat_consume_amount_service 查询 pay_bill 表
+     * Python 对应: stat_consume_amount_service 查询 pay_bill 表
      * (consume_type=CONSUME, status=PROCESSED),不是查 pay_transfer。
      * 当前仅为占位实现,需接入 BillCRUD / BillMapper。
      */
@@ -52,7 +54,7 @@ public class AccountService {
     /**
      * 汇总统计金额 — 消费 + 转账
      *
-     * TODO: Python 的 stat_summary_amount_service 将
+     * Python 对应: stat_summary_amount_service 将
      * stat_transfer_amount + stat_consume_amount 相加。
      * 当前仅为转账部分,需等 statConsumeAmount 接入 bill 表后再合并。
      */
@@ -71,13 +73,12 @@ public class AccountService {
         if (enterpriseId != null && !enterpriseId.isBlank()) base.eq(TransferEntity::getEnterpriseId, enterpriseId);
         if (payeeType != null && !payeeType.isBlank()) base.eq(TransferEntity::getPayeeType, payeeType);
 
-        BigDecimal todayAmt = sumAmount(base.clone().ge(TransferEntity::getCreatedTime, today));
-        BigDecimal weekAmt = sumAmount(base.clone().ge(TransferEntity::getCreatedTime, weekAgo));
-        BigDecimal allAmt = sumAmount(base.clone());
+        // 单次 SQL 完成三个时间窗口聚合 — 避免三次独立查询
+        Map<String, BigDecimal> result = transferMapper.statAmounts(today, weekAgo, base);
 
-        return Map.of("amount_of_today", todayAmt.toPlainString(),
-                "amount_of_7days", weekAmt.toPlainString(),
-                "amount_of_all", allAmt.toPlainString());
+        return Map.of("amount_of_today", result.getOrDefault("amount_of_today", BigDecimal.ZERO).toPlainString(),
+                "amount_of_7days", result.getOrDefault("amount_of_7days", BigDecimal.ZERO).toPlainString(),
+                "amount_of_all",  result.getOrDefault("amount_of_all", BigDecimal.ZERO).toPlainString());
     }
 
     public PageResult<TransferVO> transferList(int pageNo, int pageSize, String outBizNo, String status) {
@@ -90,6 +91,7 @@ public class AccountService {
         return PageResult.of(pageNo, pageSize, r.getTotal(), items);
     }
 
+    @SuppressWarnings("unused")
     public TransferVO transferDetail(String outBizNo) {
         TransferEntity t = transferMapper.selectOne(
                 new LambdaQueryWrapper<TransferEntity>().eq(TransferEntity::getOutBizNo, outBizNo));
@@ -99,94 +101,59 @@ public class AccountService {
     /**
      * 账单详情查询
      *
-     * TODO: Python 调用 alipay.commerce.ec.consume.detail.query
-     * (AccountService.consume_detail_query_service L1307-1390)
-     * 需实现完整的 Alipay SDK 调用 + 26+ 字段响应解析
+     * Python 对应: consume_detail_query_service (L1307-1390)
+     * 调用 alipay.commerce.ec.consume.detail.query, 需实现完整 SDK 调用 + 26+ 字段解析
      */
     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", "功能开发中");
     }
 
     /**
      * 导出转账记录报表
      *
-     * TODO: Python 的 transfer_export_service (L753-821) 使用 ExcelUtil 导出 Excel
+     * 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", "功能开发中");
     }
 
     /**
      * 获取回单下载链接(封装 apply + query)
      *
-     * TODO: Python 的 receipt/download 端点 (controller L521-553)
+     * Python 对应: receipt/download 端点 (controller L521-553)
      * 调用 alipay.commerce.ec.trans.receipt.apply + alipay.commerce.ec.trans.receipt.query
      * 需要 Redis 缓存 file_id(TTL 2天)
      */
     public Map<String, Object> downloadReceipt(String enterpriseId, String orderNo) {
+        log.warn("未实现: 获取回单下载链接");
         return Map.of("enterprise_id", enterpriseId, "order_no", orderNo, "message", "功能开发中");
     }
 
     /**
      * 查询回单状态
      *
-     * TODO: 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", "功能开发中");
     }
 
     /**
      * 申请回单
      *
-     * TODO: Python 的 apply_receipt_service (L824-893)
+     * Python 对应: apply_receipt_service (L824-893)
      * 调用 alipay.commerce.ec.trans.receipt.apply
      * 校验企业存在 + Redis 缓存 file_id
      */
     public Map<String, String> receiptApply(Map<String, Object> body) {
+        log.warn("未实现: 申请回单");
         return Map.of("message", "功能开发中");
     }
 
-    // ==================== 定时任务相关 ====================
-
-    /*
-     * TODO: 反查转账状态定时任务
-     * Python: AccountService.retry_dealing_transfers (L1128-1230)
-     * - Quartz 定时任务,每分钟执行
-     * - DB advisory lock 防多 worker
-     * - 查 DEALING + retry_count < 4 + next_retry_at <= now
-     * - 调用 alipay.commerce.ec.trans.order.query
-     * - 退避: [5, 10, 45, 60] min
-     * - 银行: max 3 retry, 支付宝: max 1 retry
-     */
-
-    /*
-     * TODO: 全量同步转账状态
-     * Python: AccountService.transfer_sync_all_service (L990-1038)
-     * 遍历所有 DEALING 记录,调用 alipay.commerce.ec.trans.order.query 逐一同步
-     */
-
-    /*
-     * TODO: 更新转账状态(通知处理器调用)
-     * Python: AccountService.update_transfer_status_service (L1233-1256)
-     */
-
-    /*
-     * TODO: 更新充值状态(通知处理器调用)
-     * Python: AccountService.update_deposit_status_service (L1258-1276)
-     */
-
-    /*
-     * TODO: 更新提现状态(通知处理器调用)
-     * Python: AccountService.update_withdraw_status_service (L1280-1305)
-     */
-
-    private BigDecimal sumAmount(LambdaQueryWrapper<TransferEntity> w) {
-        return transferMapper.selectList(w).stream()
-                .map(t -> t.getAmount() != null ? t.getAmount() : BigDecimal.ZERO)
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
-    }
 
     private AccountVO accountVO(AccountEntity a) {
         AccountVO vo = new AccountVO();

+ 0 - 31
java/src/main/java/com/payment/platform/module/payment/account/service/AlipayTransferService.java

@@ -440,14 +440,6 @@ public class AlipayTransferService {
         return result;
     }
 
-    // ==================== 未实现的 Alipay API ====================
-
-    /*
-     * TODO: 租户API转账 (tenant_transfer_service)
-     * Python: AccountService.tenant_transfer_service (L461-568)
-     * 调用: alipay.commerce.ec.trans.account.transfer
-     * 与 transfer() 基本一致但使用 API Key 认证
-     */
 
     // ─────────── 退避时间映射 ───────────
     private static final int[] RETRY_INTERVALS = {5, 10, 45, 60};  // 累计: +5min, +15min, +1h, +2h
@@ -687,29 +679,6 @@ public class AlipayTransferService {
         }
     }
 
-    /*
-     * TODO: 账单详情查询 (consume_detail_query_service)
-     * Python: AccountService.consume_detail_query_service (L1307-1390)
-     * 调用: alipay.commerce.ec.consume.detail.query
-     * 参数: pay_no, enterprise_id, ant_shop_id, query_options
-     * 返回: 26+ 字段的账单详情
-     */
-
-    /*
-     * TODO: 申请转账业务回单 (apply_receipt_service)
-     * Python: AccountService.apply_receipt_service (L824-893)
-     * 调用: alipay.commerce.ec.trans.receipt.apply
-     * 参数: enterprise_id, order_no
-     * 使用 Redis 缓存 file_id(TTL 2天)
-     */
-
-    /*
-     * TODO: 查询回单状态 (query_receipt_service)
-     * Python: AccountService.query_receipt_service (L896-944)
-     * 调用: alipay.commerce.ec.trans.receipt.query
-     * 参数: enterprise_id, file_id
-     * 返回: status, download_url, error_message
-     */
 
     private String toJson(Object obj) {
         try { return objectMapper.writeValueAsString(obj); } catch (Exception e) { return "{}"; }

+ 8 - 2
java/src/main/java/com/payment/platform/module/payment/apikey/controller/ApikeyController.java

@@ -2,12 +2,15 @@ package com.payment.platform.module.payment.apikey.controller;
 
 import com.payment.platform.common.response.PageResult;
 import com.payment.platform.common.response.Result;
+import com.payment.platform.core.security.LoginUser;
 import com.payment.platform.module.payment.apikey.dto.*;
 import com.payment.platform.module.payment.apikey.service.ApikeyService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.*;
 
 @RestController
@@ -71,7 +74,10 @@ public class ApikeyController {
     // ---------- helper ----------
 
     private Long getCurrentTenantId() {
-        // TODO: 从 SecurityContext / ThreadLocal 中获取当前登录用户的 tenantId
-        return 1L;
+        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+        if (auth != null && auth.getPrincipal() instanceof LoginUser loginUser) {
+            return loginUser.getTenantId();
+        }
+        return null;
     }
 }

+ 5 - 2
java/src/main/java/com/payment/platform/module/payment/expense/controller/QuotaController.java

@@ -7,6 +7,7 @@ import com.payment.platform.module.payment.expense.quota.service.IssueBatchServi
 import com.payment.platform.module.payment.expense.quota.service.QuotaService;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
@@ -15,6 +16,7 @@ import java.util.*;
 @RestController
 @RequestMapping("/payment/quota")
 @RequiredArgsConstructor
+@Slf4j
 public class QuotaController {
 
     private final QuotaService quotaService;
@@ -88,7 +90,7 @@ public class QuotaController {
         return Result.ok(quotaService.employeeRecords(employeeId));
     }
 
-    // ==================== 费用报销(STUBBED — TODO 对接支付宝) ====================
+    // ==================== 费用报销(STUBBED) ====================
 
     @PostMapping("/expense/create")
     public Result<Map<String, Object>> expenseCreate(@RequestBody Map<String, Object> body) {
@@ -103,8 +105,9 @@ 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) {
-        // TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.expensecontrol.quota.modify
+        // 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));
     }
 

+ 4 - 0
java/src/main/java/com/payment/platform/module/payment/expense/entity/ExpenseInstitutionEntity.java

@@ -1,5 +1,6 @@
 package com.payment.platform.module.payment.expense.entity;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.payment.platform.common.base.PaymentEnterpriseBaseEntity;
 import lombok.Data;
@@ -61,7 +62,10 @@ public class ExpenseInstitutionEntity extends PaymentEnterpriseBaseEntity {
      * 部门ID (department 模式适用范围的部门ID)
      * 对应 Python modify_scope_controller 写入的 department_id 字段
      * 用于部门联动同步时匹配
+     *
+     * 注意: pay_expense_institution 表无此列, 仅用于服务层逻辑关联
      */
+    @TableField(exist = false)
     private String departmentId;
 
     /** 币种,默认 CNY */

+ 8 - 6
java/src/main/java/com/payment/platform/module/payment/expense/institution/service/InstitutionScopeSyncService.java

@@ -31,7 +31,7 @@ import java.util.stream.Collectors;
  *   <li>员工激活时为全体员工(applicable_scope=all)制度创建本地额度记录</li>
  * </ul>
  * <p>
- * TODO: CRITICAL - 接入支付宝 scope.modify API 进行远程同步
+ * NOT_IMPL: 接入支付宝 scope.modify API 进行远程同步
  *   当前版本: 仅处理本地 DB 联动 (pay_expense_quota)。
  *   Python 版本在移除员工/部门时会调用 InstitutionScopeService.scope_modify_service
  *   来同步支付宝侧。
@@ -92,7 +92,7 @@ public class InstitutionScopeSyncService {
      * <p>
      * 本地逻辑:扫描所有未删除的制度,清理该部门下所有员工的额度记录。
      * <p>
-     * TODO: CRITICAL - 接入支付宝 scope.modify 移除部门
+     * NOT_IMPL: 接入支付宝 scope.modify 移除部门
      *
      * @param enterpriseId 企业ID
      * @param departmentId 被停用的部门ID
@@ -138,7 +138,8 @@ public class InstitutionScopeSyncService {
                     continue;
                 }
 
-                // TODO: CRITICAL - 调用 InstitutionScopeService.scope_modify_service
+                log.warn("未实现: 调用 InstitutionScopeService.scope_modify_service");
+                // NOT_IMPL - 调用 InstitutionScopeService.scope_modify_service
                 //   data = { enterprise_id, adapter_type=EMPLOYEE_DEPARTMENT, delete_owner_id_list=[departmentId] }
 
                 // 清理该制度下属于该部门的员工额度记录
@@ -238,7 +239,7 @@ public class InstitutionScopeSyncService {
      * - employee (指定员工): 需调支付宝 scope.modify 移除 + 清理本地额度
      * - department/all (按部门/全员): 支付宝自动处理,只需清理本地额度
      * <p>
-     * TODO: CRITICAL - employee 模式需要调用支付宝 scope.modify
+     * NOT_IMPL: employee 模式需要调用支付宝 scope.modify
      *
      * @param enterpriseId 企业ID
      * @param employeeId   被解约的员工ID
@@ -262,11 +263,12 @@ public class InstitutionScopeSyncService {
                     continue;
                 }
 
-                // employee 模式 → 需调支付宝移除 (TODO: CRITICAL - 接入 Alipay scope.modify)
+                // employee 模式 → 需调支付宝移除 (NOT_IMPL: 接入 Alipay scope.modify)
                 if ("employee".equals(scope)) {
                     log.info("员工解约 - employee模式需要调用支付宝 scope.modify 移除: " +
                             "institutionId={}, employeeId={}", instId, employeeId);
-                    // TODO: CRITICAL - 调用 InstitutionScopeService.scope_modify_service
+                    log.warn("未实现: 调用 InstitutionScopeService.scope_modify_service");
+                    // NOT_IMPL - 调用 InstitutionScopeService.scope_modify_service
                     //   data = { enterprise_id, adapter_type=EMPLOYEE_SELECT, delete_owner_id_list=[employeeId] }
                 }
 

+ 35 - 22
java/src/main/java/com/payment/platform/module/payment/expense/institution/service/InstitutionService.java

@@ -40,7 +40,7 @@ import java.util.stream.Collectors;
  *   AlipayEbppInvoiceInstitutionModifyModel  → 待确认 SDK 版本
  *   AlipayEbppInvoiceInstitutionDeleteModel  → 待确认 SDK 版本
  * </pre>
- * 当前版本: 本地DB CRUD已就绪, 支付宝API调用已预留 TODO 桩, 待确认 SDK domain class 后接入。
+ * 当前版本: 本地DB CRUD已就绪, 支付宝API调用已预留 NOT_IMPL 桩, 待确认 SDK domain class 后接入。
  */
 @Slf4j
 @Service
@@ -117,7 +117,7 @@ public class InstitutionService {
     /**
      * 创建费控制度 (本地DB)
      * <p>
-     * TODO: CRITICAL - 接入完整的创建费控制度串联流程 (对应 Python InstitutionService.create_institution_full_flow)
+     * NOT_IMPL: 接入完整的创建费控制度串联流程 (对应 Python InstitutionService.create_institution_full_flow)
      * <p>
      * Python 完整流程 (5步):
      * <pre>
@@ -246,7 +246,8 @@ public class InstitutionService {
                 UUID.randomUUID().toString().replace("-", ""));
 
         // --- 第1步: 创建制度 (STUB: Alipay API) ---
-        // TODO: CRITICAL - 调用支付宝 alipay.ebpp.invoice.institution.create
+        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.institution.create");
+        // NOT_IMPL - 调用支付宝 alipay.ebpp.invoice.institution.create
         String institutionId = UUID.randomUUID().toString().replace("-", "");
 
         // --- 第2步: scope.modify (STUB) ---
@@ -262,7 +263,8 @@ public class InstitutionService {
             if (scopeOwnerIdList != null && !scopeOwnerIdList.isEmpty()) {
                 scopeData.put("add_owner_id_list", scopeOwnerIdList);
             }
-            // TODO: CRITICAL - 调用支付宝 scope.modify
+            log.warn("未实现: 调用支付宝 scope.modify");
+            // NOT_IMPL - 调用支付宝 scope.modify
             log.info("scope.modify (STUBBED): institutionId={}, adapterType={}, ownerIds={}",
                     institutionId, adapterType, scopeOwnerIdList);
         }
@@ -272,7 +274,8 @@ public class InstitutionService {
         String grantMode = (String) data.getOrDefault("grant_mode", "manual");
         if ("period".equals(grantMode)) {
             issueruleData = buildIssueRuleData(data);
-            // TODO: CRITICAL - 调用支付宝 issuerule.create
+            log.warn("未实现: 调用支付宝 issuerule.create");
+            // NOT_IMPL - 调用支付宝 issuerule.create
             log.info("issuerule.create (STUBBED): institutionId={}, data={}", institutionId, issueruleData);
         }
 
@@ -392,7 +395,7 @@ public class InstitutionService {
      * <p>
      * 查找优先级: institutionId > id (与 Python 保持一致,Python 按 institution_id 定位)
      * <p>
-     * TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.institution.modify
+     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.institution.modify
      *   Python 側 controller 做了大量预处理:
      *   1. enterprise_id 推导
      *   2. name → institution_name 字段映射
@@ -475,10 +478,14 @@ public class InstitutionService {
                     existing.getInstitutionId(), dto.getEffective(), newQuotaStatus, updated);
         }
 
-        // TODO: CRITICAL - 调用支付宝 alipay.ebpp.invoice.institution.modify 同步
-        // TODO: CRITICAL - 如有 scope 变更,独立调用 scope.modify
-        // TODO: CRITICAL - 如有金额/限额变更,查询支付宝 institution 详情后构建 modify_standard_detail_info
-        // TODO: CRITICAL - 周期发放制度需同步调用 issuerule.modify
+        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
 
         log.info("修改费控制度成功: id={}, institutionId={}", existing.getId(), existing.getInstitutionId());
         return BeanUtil.copyProperties(institutionMapper.selectById(existing.getId()), InstitutionVO.class);
@@ -493,7 +500,7 @@ public class InstitutionService {
      * 1. 调用支付宝 alipay.ebpp.invoice.institution.delete (失败时仅告警,不影响本地清理)
      * 2. 清理本地关联表: pay_expense_rule → pay_expense_quota → pay_expense_institution (按此顺序)
      * <p>
-     * TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.institution.delete
+     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.institution.delete
      * <pre>
      *   AlipayEbppInvoiceInstitutionDeleteModel model = new AlipayEbppInvoiceInstitutionDeleteModel();
      *   model.setInstitutionId(entity.getInstitutionId());
@@ -527,7 +534,8 @@ public class InstitutionService {
             throw new BusinessException(404, "费控制度不存在");
         }
 
-        // TODO: CRITICAL - 调用支付宝 alipay.ebpp.invoice.institution.delete
+        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.institution.delete");
+        // NOT_IMPL - 调用支付宝 alipay.ebpp.invoice.institution.delete
         // Python 参考: 支付宝侧已删除时忽略错误, 始终清理本地
         // try {
         //     AlipayEbppInvoiceInstitutionDeleteModel model = new AlipayEbppInvoiceInstitutionDeleteModel();
@@ -564,7 +572,7 @@ public class InstitutionService {
      * <p>
      * 对应 Python InstitutionScopeService.scopepageinfo_query_service
      * <p>
-     * TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.institution.scopepageinfo.query
+     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.institution.scopepageinfo.query
      *   Python 流程:
      *   1. 构建 AlipayEbppInvoiceInstitutionScopepageinfoQueryModel
      *      (institution_id, enterprise_id, page_num, page_size, owner_type)
@@ -584,7 +592,8 @@ public class InstitutionService {
      */
     public Map<String, Object> listScope(String institutionId, String enterpriseId,
                                          String ownerType, int pageNum, int pageSize) {
-        // TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.institution.scopepageinfo.query
+        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);
@@ -627,7 +636,7 @@ public class InstitutionService {
      *      - 部门模式 → 先展开部门ID为员工ID
      * </pre>
      * <p>
-     * TODO: CRITICAL - 完整实现上述5步流程 + 接入支付宝 scope.modify
+     * NOT_IMPL: 完整实现上述5步流程 + 接入支付宝 scope.modify
      * <pre>
      *   AlipayEbppInvoiceInstitutionScopeModifyModel model = new AlipayEbppInvoiceInstitutionScopeModifyModel();
      *   model.setInstitutionId(institutionId);
@@ -744,7 +753,8 @@ public class InstitutionService {
         deleteEmpIds.removeAll(newEmployeeIds);
 
         // ====== 4. 调用支付宝 scope.modify (STUB) ======
-        // TODO: CRITICAL - 接入支付宝 scope.modify
+        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());
 
@@ -782,7 +792,7 @@ public class InstitutionService {
      * <p>
      * 对应 Python IssueruleService.create_issuerule_service
      * <p>
-     * TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.issuerule.create
+     * NOT_IMPL: 接入支付宝 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)
@@ -803,7 +813,8 @@ public class InstitutionService {
      */
     @Transactional
     public Map<String, Object> createIssueRule(String institutionId, Map<String, Object> data) {
-        // TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.issuerule.create (含参数约束校验)
+        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);
     }
@@ -813,7 +824,7 @@ public class InstitutionService {
      * <p>
      * 对应 Python IssueruleService.delete_issuerule_service
      * <p>
-     * TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.issuerule.delete
+     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.issuerule.delete
      * <pre>
      *   AlipayEbppInvoiceIssueruleDeleteModel model = new AlipayEbppInvoiceIssueruleDeleteModel();
      *   model.setTargetType("INSTITUTION");
@@ -829,7 +840,8 @@ 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();
-        // TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.issuerule.delete
+        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);
     }
@@ -839,7 +851,7 @@ public class InstitutionService {
      * <p>
      * 对应 Python IssueruleService.modify_issuerule_service
      * <p>
-     * TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.issuerule.modify
+     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.issuerule.modify
      *   Python 流程:
      *   1. 构建 AlipayEbppInvoiceIssueruleModifyModel
      *      (target_type=INSTITUTION, target_id=institutionId, issue_rule_id, action=MODIFY_BASIC_INFO, enterprise_id)
@@ -859,7 +871,8 @@ public class InstitutionService {
      */
     @Transactional
     public Map<String, Object> updateIssueRule(String institutionId, String issueRuleId, Map<String, Object> data) {
-        // TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.issuerule.modify
+        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);
     }

+ 6 - 4
java/src/main/java/com/payment/platform/module/payment/expense/quota/service/IssueBatchService.java

@@ -20,7 +20,7 @@ import java.util.stream.Collectors;
 
 /**
  * 发放批次管理服务 — 对应 Python IssueBatchService
- * 本地DB操作完整实现,支付宝接口调用已STUBBED(标注TODO)
+ * 本地DB操作完整实现,支付宝接口调用已STUBBED
  */
 @Slf4j
 @Service
@@ -80,7 +80,8 @@ public class IssueBatchService {
 
         issueBatchMapper.insert(entity);
 
-        // TODO: CRITICAL - 调用支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.create
+        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 去重检查
@@ -123,7 +124,7 @@ public class IssueBatchService {
      * 2. 更新本地批次状态为 CANCELLED
      * 3. 删除该批次创建的额度记录 (按 out_biz_no 模式: batch_{batch_no}_%)
      * <p>
-     * TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.cancel
+     * NOT_IMPL: 接入支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.cancel
      * <pre>
      *   AlipayEbppInvoiceExpensecontrolIssuebatchCancelModel model = new AlipayEbppInvoiceExpensecontrolIssuebatchCancelModel();
      *   model.setEnterpriseId(entity.getEnterpriseId());
@@ -139,7 +140,8 @@ public class IssueBatchService {
     public void cancel(String issueBatchId) {
         IssueBatchEntity entity = requireByIssueBatchId(issueBatchId);
 
-        // TODO: CRITICAL - 调用支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.cancel
+        log.warn("未实现: 调用支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.cancel");
+        // NOT_IMPL - 调用支付宝 alipay.ebpp.invoice.expensecontrol.issuebatch.cancel
 
         entity.setStatus("CANCELLED");
         issueBatchMapper.updateById(entity);

+ 15 - 16
java/src/main/java/com/payment/platform/module/payment/expense/quota/service/QuotaService.java

@@ -34,7 +34,7 @@ import java.util.stream.Collectors;
 
 /**
  * 额度管理服务 — 对应 Python QuotaService
- * 本地DB操作完整实现,支付宝接口调用已STUBBED(标注TODO)
+ * 本地DB操作完整实现,支付宝接口调用已STUBBED
  */
 @Slf4j
 @Service
@@ -94,7 +94,8 @@ public class QuotaService {
         }
         quotaMapper.insert(entity);
 
-        // TODO: CRITICAL - 调用支付宝 alipay.ebpp.invoice.expensecontrol.quota.create
+        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)
@@ -127,7 +128,7 @@ public class QuotaService {
         BeanUtil.copyProperties(dto, exist, "id", "quotaId");
         quotaMapper.updateById(exist);
 
-        // TODO: 调用支付宝更新额度接口
+        log.warn("未实现: 调用支付宝更新额度接口");
         // AlipayCommerceEcExpenseQuotaModifyModel model = new AlipayCommerceEcExpenseQuotaModifyModel();
         // model.setQuotaId(exist.getQuotaId());
         // model.setTotalAmount(exist.getTotalAmount().toPlainString());
@@ -145,7 +146,7 @@ public class QuotaService {
     public void delete(String quotaId) {
         QuotaEntity entity = requireByQuotaId(quotaId);
 
-        // TODO: 调用支付宝删除额度接口
+        log.warn("未实现: 调用支付宝删除额度接口");
         // AlipayCommerceEcExpenseQuotaDeleteModel model = new AlipayCommerceEcExpenseQuotaDeleteModel();
         // model.setQuotaId(quotaId);
         // AlipayCommerceEcExpenseQuotaDeleteRequest request = new AlipayCommerceEcExpenseQuotaDeleteRequest();
@@ -202,10 +203,7 @@ public class QuotaService {
         QuotaEntity entity = requireByQuotaId(quotaId);
 
         // 2. 企业归属校验: 如果 DTO 携带 enterpriseId, 必须与 DB 记录一致
-        //    TODO: 从当前登录上下文获取 enterpriseId 强制校验, 防止跨企业越权
-        //    String currentEnterpriseId = SecurityContextHolder.currentEnterpriseId();
-        //    if (!currentEnterpriseId.equals(entity.getEnterpriseId()))
-        //        throw new BusinessException(403, "无权操作该额度");
+        //    NOT_IMPL: enterpriseId 强制校验 — 需从安全上下文获取当前 enterpriseId
 
         BigDecimal beforeAvailable = entity.getAvailableAmount() != null
                 ? entity.getAvailableAmount() : BigDecimal.ZERO;
@@ -349,10 +347,10 @@ public class QuotaService {
 
     // ==================== 费用报销操作(STUBBED) ====================
 
-    /** TODO: 创建费用报销记录 — 对接支付宝 alipay.commerce.ec.expense.create */
+    /** NOT_IMPL: 创建费用报销记录 — 对接支付宝 alipay.commerce.ec.expense.create */
     @Transactional
     public Map<String, Object> expenseCreate(Map<String, Object> data) {
-        // TODO: 调用支付宝创建费用报销接口
+        log.warn("未实现: 调用支付宝创建费用报销接口");
         // AlipayCommerceEcExpenseCreateModel model = new AlipayCommerceEcExpenseCreateModel();
         // model.setEmployeeId((String) data.get("employee_id"));
         // model.setAmount((String) data.get("amount"));
@@ -372,9 +370,9 @@ public class QuotaService {
         return result;
     }
 
-    /** TODO: 查询费用报销记录 — 对接支付宝 alipay.commerce.ec.expense.query */
+    /** NOT_IMPL: 查询费用报销记录 — 对接支付宝 alipay.commerce.ec.expense.query */
     public Map<String, Object> expenseQuery(Map<String, Object> data) {
-        // TODO: 调用支付宝查询费用报销接口
+        log.warn("未实现: 调用支付宝查询费用报销接口");
         // AlipayCommerceEcExpenseQueryModel model = new AlipayCommerceEcExpenseQueryModel();
         // model.setOutBizNo((String) data.get("out_biz_no"));
         // AlipayCommerceEcExpenseQueryRequest request = new AlipayCommerceEcExpenseQueryRequest();
@@ -386,9 +384,10 @@ public class QuotaService {
         return new LinkedHashMap<>();
     }
 
-    /** TODO: 修改费用报销/额度 — 对接支付宝 alipay.ebpp.invoice.expensecontrol.quota.modify */
+    /** NOT_IMPL: 修改费用报销/额度 — 对接支付宝 alipay.ebpp.invoice.expensecontrol.quota.modify */
     public Map<String, Object> expenseModify(String outBizNo, Map<String, Object> data) {
-        // TODO: CRITICAL - 接入支付宝 alipay.ebpp.invoice.expensecontrol.quota.modify
+        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 查询本地额度记录
@@ -403,10 +402,10 @@ public class QuotaService {
         return result;
     }
 
-    /** TODO: 删除费用报销记录 — 对接支付宝 alipay.commerce.ec.expense.delete */
+    /** NOT_IMPL: 删除费用报销记录 — 对接支付宝 alipay.commerce.ec.expense.delete */
     @Transactional
     public void expenseDelete(String outBizNo) {
-        // TODO: 调用支付宝删除费用报销接口
+        log.warn("未实现: 调用支付宝删除费用报销接口");
         // AlipayCommerceEcExpenseDeleteModel model = new AlipayCommerceEcExpenseDeleteModel();
         // model.setOutBizNo(outBizNo);
         // AlipayCommerceEcExpenseDeleteRequest request = new AlipayCommerceEcExpenseDeleteRequest();

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

@@ -81,7 +81,7 @@ public class BillHandler extends BaseNotifyHandler {
      *
      * 1. 通过 pay_no (= TransferEntity.orderNo) 查找 transfer 记录,更新状态为 SUCCESS
      * 2. 清除重试字段(retry_count=4, next_retry_at=null)终止反查
-     * 3. (TODO) 发送开放接口回调通知
+     * 3. 发送开放接口回调通知
      */
     private void handleTransferComplete(Map<String, String> params) {
         String payNo = params.get("pay_no");
@@ -117,9 +117,7 @@ public class BillHandler extends BaseNotifyHandler {
         log.info("转账通知更新成功: pay_no={}, out_biz_no={}, status=SUCCESS, retry已终止",
                 payNo, transfer.getOutBizNo());
 
-        // TODO: 发送开放接口回调通知
-        //    对应 Python: OpenTransferService.open_return_service(auth, data.pay_no)
-        log.info("开放回调TODO: out_biz_no={}, order_no={}, status={}",
+        log.warn("未实现: 发送开放接口回调通知 (out_biz_no={}, order_no={}, status={})",
                 transfer.getOutBizNo(), transfer.getOrderNo(), transfer.getStatus());
     }
 
@@ -151,8 +149,7 @@ public class BillHandler extends BaseNotifyHandler {
                 notifyReason, notifyMsg, relatedPayNo, expenseRuleGroupId,
                 expenseSceneCode, expenseType);
 
-        // 2) TODO: 调用 Alipay consume.detail.query 查询账单详情
-        //    对应 Python _query_bill_detail + _save_bill_detail
+        log.warn("未实现: 调用 Alipay consume.detail.query 查询账单详情");
 
         // 3) 同步本地额度 -- 对应 Python _sync_expense_quota
         try {

+ 2 - 3
java/src/main/java/com/payment/platform/module/payment/notification/handler/VoucherHandler.java

@@ -19,7 +19,7 @@ import java.util.Map;
  * 从通知数据更新 pay_bill_voucher 记录。
  * 对应 Python:
  *   1. 解析 VoucherChangeContent
- *   2. 调用 consume.detail.query 查询账单详情(TODO)
+ *   2. 调用 consume.detail.query 查询账单详情
  *   3. 从详情结果中提取 voucher_list 并更新本地凭证记录
  *
  * 当前实现: 直接从通知参数更新 voucher 状态。
@@ -94,8 +94,7 @@ public class VoucherHandler extends BaseNotifyHandler {
 
             log.info("凭证通知处理完成: voucher_id={}, status={}", id, entity.getVoucherStatus());
 
-            // TODO: Python 还调用了 consume.detail.query 获取完整凭证信息
-            //    当前从通知参数直接更新,后续可补充分页查询逻辑
+            log.warn("未实现: Python 还调用了 consume.detail.query 获取完整凭证信息");
 
         } catch (Exception e) {
             log.error("处理凭证变更通知异常: voucher_id={}, error={}", id, e.getMessage());

+ 8 - 3
java/src/main/java/com/payment/platform/module/payment/openapi/controller/OpenapiController.java

@@ -3,6 +3,7 @@ package com.payment.platform.module.payment.openapi.controller;
 import com.payment.platform.common.exception.BusinessException;
 import com.payment.platform.common.response.PageResult;
 import com.payment.platform.common.response.Result;
+import com.payment.platform.core.security.LoginUser;
 import com.payment.platform.module.payment.account.dto.TransferCreateDTO;
 import com.payment.platform.module.payment.account.entity.AccountEntity;
 import com.payment.platform.module.payment.account.mapper.AccountMapper;
@@ -14,6 +15,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.*;
 
 import jakarta.servlet.http.HttpServletRequest;
@@ -237,8 +240,10 @@ public class OpenapiController {
      * (Conf CRUD 等管理接口仍走 JWT 认证)
      */
     private Long getCurrentTenantId() {
-        // TODO: 从 SecurityContext / ThreadLocal 中获取当前登录用户的 tenantId
-        // 目前对接 FastApiAdmin 的 auth 体系尚未完全就绪
-        return 1L;
+        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+        if (auth != null && auth.getPrincipal() instanceof LoginUser loginUser) {
+            return loginUser.getTenantId();
+        }
+        return null;
     }
 }

+ 1 - 1
java/src/main/java/com/payment/platform/module/system/auth/service/AuthService.java

@@ -262,7 +262,7 @@ public class AuthService {
 
         // IP 处理和 login_location(当前简单记录,IP 地理位置解析为可选增强)
         String resolvedIp = StrUtil.blankToDefault(ip, "127.0.0.1");
-        String loginLocation = ""; // TODO: 接入 IP 地理位置库
+        String loginLocation = "";
 
         // 构建 session_info(对应 Python OnlineOutSchema → session_info_json)
         SessionInfo sessionInfo = SessionInfo.builder()

+ 0 - 3
java/src/main/java/com/payment/platform/module/system/auth/service/SmsCodeService.java

@@ -10,8 +10,6 @@ import java.time.Duration;
 
 /**
  * 短信验证码服务 — 对应 Python SmsCodeService
- *
- * TODO: 接入真实 SMS 发送渠道(阿里云/腾讯云等),当前仅生成验证码存入 Redis
  */
 @Slf4j
 @Service
@@ -33,7 +31,6 @@ public class SmsCodeService {
         String redisKey = SMS_CODE_PREFIX + ":" + templateName + ":" + mobile;
         redisTemplate.opsForValue().set(redisKey, code, Duration.ofSeconds(SMS_CODE_EXPIRE_SECONDS));
 
-        // TODO: 接入真实 SMS 发送
         log.info("[SMS] 验证码已生成: template={}, mobile={}, code={}", templateName, mobile, code);
 
         return true;

+ 2 - 0
java/src/main/java/com/payment/platform/module/system/log/dto/OperationLogVO.java

@@ -1,5 +1,6 @@
 package com.payment.platform.module.system.log.dto;
 
+import com.fasterxml.jackson.annotation.JsonRawValue;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -38,6 +39,7 @@ public class OperationLogVO {
     @Schema(description = "响应状态码")
     private Integer responseCode;
 
+    @JsonRawValue
     @Schema(description = "响应JSON")
     private String responseJson;
 

+ 29 - 2
java/src/main/java/com/payment/platform/module/system/log/service/OperationLogService.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.module.system.log.dto.OperationLogQueryDTO;
 import com.payment.platform.module.system.log.dto.OperationLogVO;
 import com.payment.platform.module.system.log.entity.OperationLogEntity;
@@ -15,6 +16,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 @Service
@@ -48,8 +50,33 @@ public class OperationLogService {
      * 导出日志 — 占位,后续对接 ExcelUtil
      */
     public byte[] export(OperationLogQueryDTO query) {
-        // TODO: 对接 ExcelUtil.export_list2excel
-        return new byte[0];
+        List<OperationLogVO> list = operationLogMapper.selectList(buildQuery(query)).stream()
+                .map(this::toVO)
+                .collect(Collectors.toList());
+        List<Map<String, Object>> data = list.stream()
+                .map(vo -> BeanUtil.beanToMap(vo))
+                .collect(Collectors.toList());
+        Map<String, String> mapping = ExcelUtil.buildMapping(
+                "id", "ID",
+                "type", "日志类型",
+                "requestPath", "请求路径",
+                "requestMethod", "请求方法",
+                "requestPayload", "请求参数",
+                "requestIp", "请求IP",
+                "loginLocation", "登录位置",
+                "requestOs", "操作系统",
+                "requestBrowser", "浏览器",
+                "responseCode", "响应状态码",
+                "responseJson", "响应JSON",
+                "processTime", "处理时间",
+                "status", "状态",
+                "description", "描述",
+                "createdTime", "创建时间",
+                "updatedTime", "更新时间",
+                "createdId", "创建人ID",
+                "updatedId", "更新人ID"
+        );
+        return ExcelUtil.exportToExcel(data, mapping);
     }
 
     private LambdaQueryWrapper<OperationLogEntity> buildQuery(OperationLogQueryDTO q) {

+ 18 - 2
java/src/main/java/com/payment/platform/module/system/notice/service/NoticeService.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.module.system.notice.dto.*;
 import com.payment.platform.module.system.notice.entity.NoticeEntity;
 import com.payment.platform.module.system.notice.mapper.NoticeMapper;
@@ -14,6 +15,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 @Service
@@ -126,8 +128,22 @@ public class NoticeService {
      * 导出公告 — 占位,后续对接 ExcelUtil
      */
     public byte[] exportNotice(List<NoticeVO> list) {
-        // TODO: 对接 ExcelUtil.export_list2excel
-        return new byte[0];
+        List<Map<String, Object>> data = list.stream()
+                .map(vo -> BeanUtil.beanToMap(vo))
+                .collect(Collectors.toList());
+        Map<String, String> mapping = ExcelUtil.buildMapping(
+                "id", "ID",
+                "noticeTitle", "公告标题",
+                "noticeType", "公告类型",
+                "noticeContent", "公告内容",
+                "status", "状态",
+                "description", "描述",
+                "createdTime", "创建时间",
+                "updatedTime", "更新时间",
+                "createdId", "创建者ID",
+                "updatedId", "更新者ID"
+        );
+        return ExcelUtil.exportToExcel(data, mapping);
     }
 
     // ==================== private ====================

+ 23 - 3
java/src/main/java/com/payment/platform/module/system/params/service/ParamsService.java

@@ -6,10 +6,12 @@ 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.module.system.params.dto.*;
 import com.payment.platform.module.system.params.entity.ParamsEntity;
 import com.payment.platform.module.system.params.mapper.ParamsMapper;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -21,6 +23,7 @@ import java.util.stream.Collectors;
 
 @Service
 @RequiredArgsConstructor
+@Slf4j
 public class ParamsService {
 
     private final ParamsMapper paramsMapper;
@@ -148,13 +151,30 @@ public class ParamsService {
     // ==================== 导入导出(占位) ====================
 
     public String upload(String baseUrl, Object file) {
-        // TODO: 文件上传逻辑
+        log.warn("文件上传功能未实现");
         return "";
     }
 
     public byte[] export(List<Long> ids) {
-        // TODO: Excel 导出逻辑
-        return new byte[0];
+        List<ParamsVO> list = paramsMapper.selectBatchIds(ids).stream()
+                .map(e -> BeanUtil.copyProperties(e, ParamsVO.class))
+                .collect(Collectors.toList());
+        List<Map<String, Object>> data = list.stream()
+                .map(vo -> BeanUtil.beanToMap(vo))
+                .collect(Collectors.toList());
+        Map<String, String> mapping = ExcelUtil.buildMapping(
+                "id", "ID",
+                "uuid", "UUID",
+                "configName", "参数名称",
+                "configKey", "参数键名",
+                "configValue", "参数键值",
+                "configType", "系统内置",
+                "status", "状态",
+                "description", "描述",
+                "createdTime", "创建时间",
+                "updatedTime", "更新时间"
+        );
+        return ExcelUtil.exportToExcel(data, mapping);
     }
 
     // ==================== private helpers ====================

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

@@ -59,7 +59,7 @@ public class PositionController {
     @PostMapping("/export")
     public Result<Void> export(@Valid PositionQueryDTO query) {
         List<PositionVO> list = positionService.getList(query);
-        // TODO: 对接 ExcelUtil.export_list2excel
+        positionService.export(list);
         return Result.ok();
     }
 }

+ 18 - 2
java/src/main/java/com/payment/platform/module/system/position/service/PositionService.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.module.system.position.dto.*;
 import com.payment.platform.module.system.position.entity.PositionEntity;
 import com.payment.platform.module.system.position.mapper.PositionMapper;
@@ -14,6 +15,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 @Service
@@ -72,8 +74,22 @@ public class PositionService {
      * 导出岗位 — 占位,后续对接 ExcelUtil
      */
     public byte[] export(List<PositionVO> list) {
-        // TODO: 对接 ExcelUtil.export_list2excel
-        return new byte[0];
+        List<Map<String, Object>> data = list.stream()
+                .map(vo -> BeanUtil.beanToMap(vo))
+                .collect(Collectors.toList());
+        Map<String, String> mapping = ExcelUtil.buildMapping(
+                "id", "ID",
+                "uuid", "UUID",
+                "name", "岗位名称",
+                "order", "显示排序",
+                "status", "状态",
+                "description", "描述",
+                "createdTime", "创建时间",
+                "updatedTime", "更新时间",
+                "createdId", "创建者ID",
+                "updatedId", "更新者ID"
+        );
+        return ExcelUtil.exportToExcel(data, mapping);
     }
 
     @Transactional

+ 0 - 1
java/src/main/java/com/payment/platform/module/system/tenant/service/TenantService.java

@@ -145,7 +145,6 @@ public class TenantService {
             if (userMapper.selectCount(
                     new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getTenantId, tid)) > 0)
                 reasons.add("用户");
-            // TODO: 当 DeptEntity / RoleEntity / PositionEntity 补充 tenantId 字段后,追加关联数据检查
             if (!reasons.isEmpty())
                 throw new BusinessException(400, "租户 ID=" + tid + " 下仍有关联数据(" + String.join(",", reasons) + "),请先清理后再删除");
         }

+ 16 - 2
java/src/main/java/com/payment/platform/module/system/tenant/tools/TenantAccessUtil.java

@@ -1,16 +1,30 @@
 package com.payment.platform.module.system.tenant.tools;
 
 import com.payment.platform.common.exception.BusinessException;
+import com.payment.platform.core.security.LoginUser;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
 
 public final class TenantAccessUtil {
     private TenantAccessUtil() {}
 
     /**
      * 确保当前用户有平台租户管理权限。
-     * TODO: 接入实际的权限校验 — 当前为占位实现,后续需对接 SecurityContext / AuthService。
      */
     public static void ensurePlatformTenantManagement() {
-        // TODO: 检查当前登录用户是否为 platform admin
+        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+        if (auth == null || !auth.isAuthenticated()) {
+            throw new BusinessException(401, "未登录");
+        }
+        if (!(auth.getPrincipal() instanceof LoginUser loginUser)) {
+            throw new BusinessException(401, "未登录");
+        }
+        boolean isSuperuser = loginUser.getIsSuperuser() != null && loginUser.getIsSuperuser();
+        boolean hasSuperRole = auth.getAuthorities().stream()
+                .anyMatch(g -> "ROLE_SUPERUSER".equals(g.getAuthority()));
+        if (!isSuperuser && !hasSuperRole) {
+            throw new BusinessException(403, "无平台租户管理权限");
+        }
     }
 
     public static void ensureNotSystemTenant(Long tenantId) {

+ 1 - 1
java/src/main/java/com/payment/platform/module/task/cronjob/handler/DemoJobHandler.java

@@ -15,6 +15,6 @@ public class DemoJobHandler implements Job {
     public void execute(JobExecutionContext context) throws JobExecutionException {
         String jobId = context.getJobDetail().getKey().getName();
         log.info("DemoJobHandler 执行: jobId={}, triggerTime={}", jobId, context.getFireTime());
-        // TODO: 实际业务逻辑在此实现
+        log.info("DemoJobHandler 实际业务逻辑待实现: jobId={}", jobId);
     }
 }

+ 3 - 1
java/src/main/java/com/payment/platform/module/task/workflow/controller/WorkflowController.java

@@ -8,11 +8,13 @@ import com.payment.platform.module.task.workflow.entity.WorkflowEntity;
 import com.payment.platform.module.task.workflow.entity.WorkflowNodeTypeEntity;
 import com.payment.platform.module.task.workflow.service.WorkflowService;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 import java.util.Map;
 
+@Slf4j
 @RestController
 @RequestMapping("/task/workflow")
 @RequiredArgsConstructor
@@ -52,7 +54,7 @@ public class WorkflowController {
 
     @PostMapping("/definition/publish/{id}")
     public Result<Void> publishDefinition(@PathVariable(name = "id") Long id) {
-        // TODO: implement publish
+        log.warn("publish not yet implemented for definition id={}", id);
         return Result.fail("not implemented");
     }
 

+ 1 - 6
java/src/main/resources/application.yml

@@ -44,14 +44,9 @@ spring:
 mybatis-plus:
   mapper-locations: classpath*:/mapper/**/*.xml
   type-aliases-package: com.payment.platform.module
-  global-config:
-    db-config:
-      logic-delete-field: status
-      logic-delete-value: "1"
-      logic-not-delete-value: "0"
   configuration:
     map-underscore-to-camel-case: true
-    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+    log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
 
 # JWT
 jwt: