Просмотр исходного кода

新增
1、获取机构长者周运营统计
2、账单详情增加合同号
修改
1、库存查询逻辑修改
BUGFIX
1、解决长者推送金蝶客户组织填写错误问题
2、康复训练记录查询报错

liangwenxuan 2 месяцев назад
Родитель
Сommit
0c30b279ff
19 измененных файлов с 559 добавлено и 129 удалено
  1. 5 0
      yudao-module-system/yudao-module-system-biz/pom.xml
  2. 4 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/kingdee/KingdeeApiImpl.java
  3. 7 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/ElderlyInfoController.java
  4. 17 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/MaterialIoController.java
  5. 34 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/ElderlyWeeklyOperationSummaryRespVO.java
  6. 3 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/ExpenseBillRespVO.java
  7. 5 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/TrainingRecordsPageReqVO.java
  8. 39 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/materialio/inbound/MaterialInboundImportExcelVO.java
  9. 48 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/materialio/outbound/MaterialOutboundImportExcelVO.java
  10. 149 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/pay/WeChatPayController.java
  11. 1 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/biz/OverheadChargeDO.java
  12. 5 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/biz/TrainingRecordsMapper.java
  13. 2 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ElderlyInfoService.java
  14. 72 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ElderlyInfoServiceImpl.java
  15. 11 2
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ExpenseOrderServiceImpl.java
  16. 15 117
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/MaterialIoService.java
  17. 103 4
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/MaterialIoServiceImpl.java
  18. 4 1
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/SysOverheadChargeServiceImpl.java
  19. 35 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/util/pay/CertUtils.java

+ 5 - 0
yudao-module-system/yudao-module-system-biz/pom.xml

@@ -19,6 +19,11 @@
 
     <dependencies>
         <dependency>
+            <groupId>com.github.wechatpay-apiv3</groupId>
+            <artifactId>wechatpay-apache-httpclient</artifactId>
+            <version>0.4.7</version>
+        </dependency>
+        <dependency>
             <groupId>cn.iocoder.boot</groupId>
             <artifactId>yudao-module-system-api</artifactId>
             <version>${revision}</version>

+ 4 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/kingdee/KingdeeApiImpl.java

@@ -715,7 +715,10 @@ public class KingdeeApiImpl implements KingdeeApi {
         }
 
         TenantDO tenantDO = tenantMapper.selectById(tenantId);
-        String orgId = StringUtil.isNotEmptyORNull(tenantDO.getKingdeeOrgid()) ? tenantDO.getKingdeeOrgid() : "106";
+        String orgId = StringUtil.isNotEmptyORNull(tenantDO.getKingdeeOrgid()) ? tenantDO.getKingdeeOrgid() : null;
+        if(orgId == null){
+            return 0;
+        }
 
         elderlyInfoDOS.forEach(elderlyInfoDO -> {
             BDCustomerModelDTO modelDTO = BDCustomerModelDTO.builder()

+ 7 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/ElderlyInfoController.java

@@ -301,6 +301,13 @@ public class ElderlyInfoController {
         return success(url);
     }
 
+    @GetMapping("/getWeeklyOperationSummary")
+    @Operation(summary = "获取机构长者周运营统计")
+    @PermitAll
+    public CommonResult<ElderlyWeeklyOperationSummaryRespVO> getWeeklyOperationSummary(@RequestParam(value = "tenantId", required = false) Long tenantId) {
+        return success(elderlyInfoService.getWeeklyOperationSummary(tenantId));
+    }
+
     @PostMapping("/getHomeDataElderlyPortrait")
     @Operation(summary = "获取大屏长者画像")
     public CommonResult<ElderlyHomeRespVO> getHomeDataElderlyPortrait(@Valid ElderlyInfoHomeDataReqVO reqVO) {

+ 17 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/MaterialIoController.java

@@ -42,6 +42,9 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
+import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ImportResultVO;
+import org.springframework.web.multipart.MultipartFile;
+
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
 /**
@@ -292,4 +295,18 @@ public class MaterialIoController {
     public CommonResult<MaterialInventoryFlowRespVO> getInventoryFlow(@Valid MaterialInventoryFlowReqVO reqVO) {
         return success(materialIoService.getInventoryFlow(reqVO));
     }
+
+    @GetMapping("/import-template")
+    @Operation(summary = "下载出入库导入模板")
+    @TenantIgnore
+    public void importTemplate(HttpServletResponse response) throws IOException {
+        materialIoService.exportImportTemplate(response);
+    }
+
+    @PostMapping("/import")
+    @Operation(summary = "导入出入库数据")
+    @TenantIgnore
+    public CommonResult<ImportResultVO> importMaterialIo(@RequestParam("file") MultipartFile file) {
+        return success(materialIoService.importMaterialIo(file));
+    }
 }

+ 34 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/ElderlyWeeklyOperationSummaryRespVO.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.system.controller.admin.biz.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 机构长者周运营统计 Response VO")
+@Data
+public class ElderlyWeeklyOperationSummaryRespVO {
+
+    @Schema(description = "院名", example = "天河区老人院")
+    private String institutionName;
+
+    @Schema(description = "本周期初数", example = "606")
+    private Long periodBeginCount;
+
+    @Schema(description = "本周入住数", example = "2")
+    private Long weeklyCheckInCount;
+
+    @Schema(description = "本周退住数", example = "1")
+    private Long weeklyCheckOutCount;
+
+    @Schema(description = "本周期末数", example = "607")
+    private Long periodEndCount;
+
+    @Schema(description = "本周净增长长者数", example = "1")
+    private Long weeklyNetGrowthCount;
+
+    @Schema(description = "本月净增长长者数合计", example = "25")
+    private Long monthlyNetGrowthCount;
+
+    @Schema(description = "本周期末院内空余床位数", example = "193")
+    private Long periodEndEmptyBedCount;
+}
+

+ 3 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/ExpenseBillRespVO.java

@@ -100,4 +100,7 @@ public class ExpenseBillRespVO {
     @Schema(description = "发票号")
     private String invoiceNumber;
 
+    @Schema(description = "合同号")
+    private String contractNumber;
+
 }

+ 5 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/TrainingRecordsPageReqVO.java

@@ -1,12 +1,15 @@
 package cn.iocoder.yudao.module.system.controller.admin.biz.vo;
 
 import lombok.*;
+
+import java.time.LocalDate;
 import java.util.*;
 import io.swagger.v3.oas.annotations.media.Schema;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import org.springframework.format.annotation.DateTimeFormat;
 import java.time.LocalDateTime;
 
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
 @Schema(description = "管理后台 - 康复训练记录分页 Request VO")
@@ -22,8 +25,8 @@ public class TrainingRecordsPageReqVO extends PageParam {
     private String elderName;
 
     @Schema(description = "训练时间")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime[] trainingTime;
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+    private LocalDate[] trainingTime;
 
     @Schema(description = "康复师")
     private String therapist;

+ 39 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/materialio/inbound/MaterialInboundImportExcelVO.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inbound;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Data
+public class MaterialInboundImportExcelVO {
+
+    @ExcelProperty("单据号(同单号多行自动合并)")
+    private String orderNo;
+
+    @ExcelProperty("单据日期(yyyy-MM-dd)")
+    private LocalDate orderDate;
+
+    @ExcelProperty("备注")
+    private String remark;
+
+    @ExcelProperty("物资ID")
+    private Long materialId;
+
+    @ExcelProperty("入库仓库ID")
+    private Long refInStoreId;
+
+    @ExcelProperty("数量")
+    private Integer quantity;
+
+    @ExcelProperty("入库单价")
+    private BigDecimal inUnitPrice;
+
+    @ExcelProperty("金额")
+    private BigDecimal amount;
+
+    @ExcelProperty("生产日期(yyyy-MM-dd)")
+    private LocalDate produceDate;
+}
+

+ 48 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/biz/vo/materialio/outbound/MaterialOutboundImportExcelVO.java

@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.outbound;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+
+@Data
+public class MaterialOutboundImportExcelVO {
+
+    @ExcelProperty("单据号(同单号多行自动合并)")
+    private String orderNo;
+
+    @ExcelProperty("单据日期(yyyy-MM-dd)")
+    private LocalDate orderDate;
+
+    @ExcelProperty("领用部门ID")
+    private Long outDeptId;
+
+    @ExcelProperty("领用人")
+    private String outUser;
+
+    @ExcelProperty("出库原因")
+    private String outReason;
+
+    @ExcelProperty("备注")
+    private String remark;
+
+    @ExcelProperty("物资ID")
+    private Long materialId;
+
+    @ExcelProperty("入库批次明细ID")
+    private Long inboundItemId;
+
+    @ExcelProperty("数量")
+    private Integer quantity;
+
+    @ExcelProperty("销售单价")
+    private BigDecimal saleUnitPrice;
+
+    @ExcelProperty("金额")
+    private BigDecimal amount;
+
+    @ExcelProperty("生产日期(yyyy-MM-dd)")
+    private LocalDate produceDate;
+}
+

+ 149 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/pay/WeChatPayController.java

@@ -0,0 +1,149 @@
+package cn.iocoder.yudao.module.system.controller.admin.pay;
+
+import cn.iocoder.yudao.module.system.util.pay.CertUtils;
+import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
+import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
+import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
+import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
+import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
+import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
+import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
+import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
+import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
+import com.wechat.pay.contrib.apache.httpclient.util.RsaCryptoUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.PrivateKey;
+
+/**
+ * 微信小程序统一下单控制器
+ */
+@Slf4j
+@RestController
+@RequestMapping("/pay/wx")
+public class WeChatPayController {
+
+    private CloseableHttpClient httpClient;
+    private CertificatesManager certificatesManager;
+    private Verifier verifier;
+
+    private static final String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzbDDaK6pFz8fEBIM/pFlIV4uApuhr/TDm8mlK0+Cw1+om1p2s2RFItgBw7Gk9qTN72L68utj5bEDJRQgdr1ixAjc4AH1AOYeV4YZnWTQkcjsRiEimqo+bkIm+osTpFEO9kdvfFMILOl1MDipIAcUrNq9CMs6s6nFDLxLlwy8Ap4VrP4N+FF04efG+8dj35z8dUdi/8WrAe4p7GYGojHHoZ/mIqEDnLdt+PM5pRizCfUCtmSF5UxnmNUlUAutIedvtOAT9rs9E71aO+ldhXNMfA+jLxuBmyp+oy3hWS4Vqp4RbC1a2xRSULvEgSUwrdNq/rytpK66/XY7Vk2f/onCdAgMBAAECggEAXtI/ox1OeGSN01b7MggeMzApBo2u6Vs+m97irGv7JBqHoYzseWuiScX9x6/5EmA4WCw86Srp/i8qsfOsjVucyLc+DXecufNtZ1VvnXC0MMq50tMuaf5btMAXO8tzuyY9gpLyGxwyTJLEuawGxlVBKUxWJOsK9LFVuEbJeunDDlmWsn+euRw3F7jxtNnjVjfqXsY9O+TtB8p0bOduWZc7OvDHoIZQL4NnDivnAR5q1dSLt77pzdinvGiXNYNuTTULstR+KVhJy5Kmsldtj3nrP/fcy/N+JsXm9+3xgaJ2edKwmfnwvQjJAWOtltOSoHx4RK3kzbC8UhKs/E3iaUEUgQKBgQDp0qoNKHbTCiwDhmrnnCyaoW16qEOj0MqHOy5mKYmJkcSDGgNQM/44gN7vOu5BBJHXdkizxaoX3w/ufW2ahhJ/LcjITjFIq8gpiFKbLNfhkCw47ZDtmhB9jAnaB+9+rLeAFXkfXKi+PYJGZIIzbgn0u5LADzRjvzaxe/Z+Ed5ooQKBgQDEcKkk0SmcsHdTkLs2kf6fu8z7MAg9l42fCO18TPt3PeoRcvf8AKw+WDjARSo0ojqL23T9U0IydKdsbcxrYsHETUBS+MAP09Ckpd8p7F5evJghXwhhQRx0gzVuNMkbeP1a3xnC3yL7V5fluGQqz7Xiv3sg9dxYDh8cVQBu6ckafQKBgQCcoF0Ay2YtH9cz1UqvMtI+Enw/eY81oJrJ0z7VeGWFHXvBRh+KDgnw14J+Rb9rFiCLb9Rrd7DkpKsLWkGdDMo/HvAsHRSuVUOTbpnHEFbb5bN5vskiH92D+9Ztkns/I3sX9UpZU7xFEva9KH5+7OsGYM+Aj67MUj3UzfDjqhyNgQKBgFcNojeZpbo1jbvvsLd/PXqmLDHI2G4LIoyu1Se3qdzvCDLRY0o/NhWu3P9/5zNKDW37RD4bToOzpJptkiCotDv9DBt49wxMjvLYOyyF/lA3faeUSM9onmaX2u7K37CYDpbdtbnhTsxZxgvcii9auz0QJE24BvzSzUCt/rIoUqG1AoGAX9sTEp77kdVrM8ECEKgLsOtRWEAgeNkWLgHZKYcEYyu3D4XEVScYNtTBLBHXs3n1GIYdLjWEAWWb55sqKSlVzVWfOhpnOqoFq51xi2TGr3pkp9cqZf0lxYKs7P4tlgeL0icXfb+lFTPmgfgE5vjHBvYQcj7o9lO5hRe46fVoxT8=";
+
+    private static final String merchantId = "1105835366";
+
+    private static final String certPem = "MIIELjCCAxagAwIBAgIUOjdSdMmYC2qOKZOM68wXFlQcfbEwDQYJKoZIhvcNAQEL\n" +
+            "BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT\n" +
+            "FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg\n" +
+            "Q0EwHhcNMjYwMzAyMDM0MzM5WhcNMzEwMzAxMDM0MzM5WjCBhzETMBEGA1UEAwwK\n" +
+            "MTEwNTgzNTM2NjEbMBkGA1UECgwS5b6u5L+h5ZWG5oi357O757ufMTMwMQYDVQQL\n" +
+            "DCrpopDlubTlgaXlurfkuqfkuJrvvIjpm4blm6LvvInmnInpmZDlhazlj7gxCzAJ\n" +
+            "BgNVBAYTAkNOMREwDwYDVQQHDAhTaGVuWmhlbjCCASIwDQYJKoZIhvcNAQEBBQAD\n" +
+            "ggEPADCCAQoCggEBALNsMNorqkXPx8QEgz+kWUhXi4Cm6Gv9MObyaUrT4LDX6ibW\n" +
+            "nazZEUi2AHDsaT2pM3vYvry62PlsQMlFCB2vWLECNzgAfUA5h5XhhmdZNCRyOxGI\n" +
+            "SKaqj5uQib6ixOkUQ72R298Uwgs6XUwOKkgBxSs2r0IyzqzqcUMvEuXDLwCnhWs/\n" +
+            "g34UXTh58b7x2PfnPx1R2L/xasB7insZgaiMcehn+YioQOct2348zmlGLMJ9QK2Z\n" +
+            "IXlTGeY1SVQC60h52+04BP2uz0TvVo76V2Fc0x8D6MvG4GbKn6jLeFZLhWqnhFsL\n" +
+            "VrbFFJQu8SBJTCt02r+vK2krrr9djtWTZ/+icJ0CAwEAAaOBuTCBtjAJBgNVHRME\n" +
+            "AjAAMAsGA1UdDwQEAwID+DCBmwYDVR0fBIGTMIGQMIGNoIGKoIGHhoGEaHR0cDov\n" +
+            "L2V2Y2EuaXRydXMuY29tLmNuL3B1YmxpYy9pdHJ1c2NybD9DQT0xQkQ0MjIwRTUw\n" +
+            "REJDMDRCMDZBRDM5NzU0OTg0NkMwMUMzRThFQkQyJnNnPUhBQ0M0NzFCNjU0MjJF\n" +
+            "MTJCMjdBOUQzM0E4N0FEMUNERjU5MjZFMTQwMzcxMA0GCSqGSIb3DQEBCwUAA4IB\n" +
+            "AQAVEO4RR9o+W0SySh9S6RwLV9e+GNl96K8BnfswLMeTcZTA5XJQRTfCSlE5v/mf\n" +
+            "NQw5HdDigF2BeIDJqWSDyApbILfngsZadH0I6fDk1u2zifeIQcN0C9yFRPn2uf3O\n" +
+            "YRc2sX7CJdPSbMGFaozaH40uZDl86/63qVFMuaOa3iAwiqQVVsSOcnhUhLBL1fh/\n" +
+            "0AauGFk5fyGgNqMIJ+7+z+nBtWfq1i2quRda66Ao0ytQP+L5CV0jePmQ0uOiv8AE\n" +
+            "EcqAqJ+/d0nqhFd7hVcmW+uzQ3eTTeuHnsAZt7bkUhTF5PJBvZipI+CYtUhZFlaT\n" +
+            "rXbHeFgiQcZb/sR8sTBV0KXR";
+
+    private static final String apiV3Key = "W2sE4rF6tY8uI0oP1aQ2wS3dF4gH5jK6";
+    public void setup() throws Exception {
+        String merchantSerialNumber = CertUtils.getMerchantSerialNumberFromPem(certPem);
+        PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey);
+        certificatesManager = CertificatesManager.getInstance();
+        certificatesManager.putMerchant(merchantId, new WechatPay2Credentials(merchantId,
+                        new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),
+                apiV3Key.getBytes(StandardCharsets.UTF_8));
+        verifier = certificatesManager.getVerifier(merchantId);
+        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
+                .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey)
+                .withValidator(new WechatPay2Validator(verifier));
+        httpClient = builder.build();
+    }
+
+    public void encryptedTest() throws IllegalBlockSizeException {
+        String text = "helloWorld";
+        String transformation = "RSA/ECB/PKCS1Padding";
+        String encryptedText = RsaCryptoUtil.encrypt(text, verifier.getValidCertificate(), transformation);
+        System.out.println(encryptedText);
+    }
+
+    public void decryptedTest() throws BadPaddingException {
+        String encryptedText = "";
+        String transformation = "RSA/ECB/PKCS1Padding";
+        String decryptedText = RsaCryptoUtil.decrypt(encryptedText, PemUtil.loadPrivateKey(privateKey), transformation);
+        assert("helloWorld".equals(decryptedText));
+    }
+
+
+    //Call Unified Order API
+    public void unifiedOrderTest() throws IOException {
+        String unifiedOrderBody = String.join("\n" ,
+                "{" ,
+                "'sp_appid': 'wx2421b1c4370ec43b'," ,
+                "'sp_mchid': '10000100'," ,
+                "'sub_mchid': '20000100'," ,
+                "'sub_appid': 'wx352671b037b437ec'," ,
+                "'out_trade_no': '20150806125346'," ,
+                "'merchant_category_code': '1011'," ,
+                "'payer': {" ,
+                "'sub_openid': 'oUpF8uMuAJO_M2pxb1Q9zNjWeS6o'" ,
+                "}," ,
+                "'notify_url': 'https://wxpay.wxutil.com/pub_v2/pay/notify.v2.php'," ,
+                "'trade_type': 'JSAPI'," ,
+                "'amount': {" ,
+                "'total': 10000," ,
+                "'currency': 'HKD'" ,
+                "}," ,
+                "'attach': 'Payment test'," ,
+                "'description': 'Miniprogramm Pay test'," ,
+                "'detail': {" ,
+                "'cost_price': 10000," ,
+                "'receipt_id': '1234'," ,
+                "'goods_detail': [{" ,
+                "'goods_id': 'iphone6s_16G'," ,
+                "'wxpay_goods_id': '1001'," ,
+                "'goods_name': 'iPhone6s 16G'," ,
+                "'quantity': 1," ,
+                "'price': 528800" ,
+                "}]" ,
+                "}," ,
+                "'scene_info': {" ,
+                "'payer_client_ip': '14.23.150.211'," ,
+                "'device_ip': '59.37.125.32'," ,
+                "'device_id': '013467007045764'," ,
+                "'operator_id': 'P001'," ,
+                "'store_info': {" ,
+                "'id': 'SZTX001'" ,
+                "}" ,
+                "}" ,
+                "}").replace("'","\"");
+        HttpPost httpPost = new HttpPost("https://apihk.mch.weixin.qq.com/v3/global/transactions/jsapi");
+        httpPost.addHeader("Accept", "application/json");
+        httpPost.addHeader("Content-type", "application/json; charset=utf-8");
+        httpPost.setEntity(new StringEntity(unifiedOrderBody));
+        CloseableHttpResponse response = httpClient.execute(httpPost);
+        //Process the response
+    }
+}

+ 1 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/biz/OverheadChargeDO.java

@@ -100,7 +100,7 @@ public class OverheadChargeDO implements Serializable {
     private String startUsing;
 
     /**
-     * 状态(0正常,1删除)
+     * 状态(0禁用,1启用)
      */
     @TableField("status")
     private String status;

+ 5 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/biz/TrainingRecordsMapper.java

@@ -5,8 +5,11 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.TrainingRecordsPageReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.biz.TrainingRecordsDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.biz.WardRoundRecordDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.time.LocalTime;
+
 /**
  * 康复训练记录 Mapper
  *
@@ -18,11 +21,12 @@ public interface TrainingRecordsMapper extends BaseMapperX<TrainingRecordsDO> {
     default PageResult<TrainingRecordsDO> selectPage(TrainingRecordsPageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<TrainingRecordsDO>()
                 .eqIfPresent(TrainingRecordsDO::getElderId, reqVO.getElderId())
-                .betweenIfPresent(TrainingRecordsDO::getTrainingTime, reqVO.getTrainingTime())
                 .eqIfPresent(TrainingRecordsDO::getTherapist, reqVO.getTherapist())
                 .eqIfPresent(TrainingRecordsDO::getRecordContent, reqVO.getRecordContent())
                 .eqIfPresent(TrainingRecordsDO::getTenantId,reqVO.getTenantId())
                 .likeIfPresent(TrainingRecordsDO::getElderName,reqVO.getElderName())
+                .geIfPresent(TrainingRecordsDO::getTrainingTime, reqVO.getTrainingTime() != null && reqVO.getTrainingTime().length >= 1 ? reqVO.getTrainingTime()[0].atStartOfDay() : null)
+                .leIfPresent(TrainingRecordsDO::getTrainingTime, reqVO.getTrainingTime() != null && reqVO.getTrainingTime().length == 2 ? reqVO.getTrainingTime()[1].atTime(LocalTime.MAX) : null)
                 .orderByDesc(TrainingRecordsDO::getCreateTime));
     }
 

+ 2 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ElderlyInfoService.java

@@ -90,6 +90,8 @@ public interface ElderlyInfoService {
 
     String getMonitorUrl(Long tenantId);
 
+    ElderlyWeeklyOperationSummaryRespVO getWeeklyOperationSummary(Long tenantId);
+
     void deleteElderlyAndRelatedData(Long id);
 }
 

+ 72 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ElderlyInfoServiceImpl.java

@@ -1464,6 +1464,78 @@ public class ElderlyInfoServiceImpl implements ElderlyInfoService {
         return openApi.getOpenAddress(tenantId);
     }
 
+    @Override
+    @TenantIgnore
+    public ElderlyWeeklyOperationSummaryRespVO getWeeklyOperationSummary(Long tenantId) {
+        Long targetTenantId = tenantId;
+        if (targetTenantId == null) {
+            targetTenantId = Optional.ofNullable(SecurityFrameworkUtils.getLoginUser())
+                    .map(user -> user.getTenantId())
+                    .orElse(TenantContextHolder.getTenantId());
+        }
+
+        LocalDate today = LocalDate.now();
+        LocalDate weekStart = today.with(DayOfWeek.MONDAY);
+        LocalDate weekEnd = weekStart.plusDays(6);
+        LocalDate monthStart = today.withDayOfMonth(1);
+
+        LocalDateTime weekStartDateTime = weekStart.atStartOfDay();
+        LocalDateTime weekEndDateTime = weekEnd.plusDays(1).atStartOfDay();
+
+        ElderlyWeeklyOperationSummaryRespVO respVO = new ElderlyWeeklyOperationSummaryRespVO();
+
+        TenantDO tenantDO = tenantMapper.selectById(targetTenantId);
+        respVO.setInstitutionName(tenantDO != null ? tenantDO.getName() : "");
+
+        Long periodEndCount = elderlyInfoMapper.selectCount(new LambdaQueryWrapperX<ElderlyInfoDO>()
+                .eq(ElderlyInfoDO::getTenantId, targetTenantId)
+                .eq(ElderlyInfoDO::getDeleted, 0)
+                .eq(ElderlyInfoDO::getOrgType, 1)
+                .eq(ElderlyInfoDO::getInStatus, 1));
+        respVO.setPeriodEndCount(periodEndCount == null ? 0L : periodEndCount);
+
+        Long weeklyCheckInCount = elderlyCheckInRecordMapper.selectCount(new LambdaQueryWrapperX<ElderlyCheckInRecordDO>()
+                .eq(ElderlyCheckInRecordDO::getTenantId, targetTenantId)
+                .eq(ElderlyCheckInRecordDO::getStatus, 2)
+                .ge(ElderlyCheckInRecordDO::getCheckInTime, weekStartDateTime)
+                .lt(ElderlyCheckInRecordDO::getCheckInTime, weekEndDateTime));
+        respVO.setWeeklyCheckInCount(weeklyCheckInCount == null ? 0L : weeklyCheckInCount);
+
+        Long weeklyCheckOutCount = elderlyRetreatRecordMapper.selectCount(new LambdaQueryWrapperX<ElderlyRetreatRecordDO>()
+                .eq(ElderlyRetreatRecordDO::getTenantId, targetTenantId)
+                .ge(ElderlyRetreatRecordDO::getRetreatDate, weekStart)
+                .le(ElderlyRetreatRecordDO::getRetreatDate, weekEnd));
+        respVO.setWeeklyCheckOutCount(weeklyCheckOutCount == null ? 0L : weeklyCheckOutCount);
+
+        Long monthlyCheckInCount = elderlyCheckInRecordMapper.selectCount(new LambdaQueryWrapperX<ElderlyCheckInRecordDO>()
+                .eq(ElderlyCheckInRecordDO::getTenantId, targetTenantId)
+                .eq(ElderlyCheckInRecordDO::getStatus, 2)
+                .ge(ElderlyCheckInRecordDO::getCheckInTime, monthStart.atStartOfDay())
+                .lt(ElderlyCheckInRecordDO::getCheckInTime, today.plusDays(1).atStartOfDay()));
+        Long monthlyCheckOutCount = elderlyRetreatRecordMapper.selectCount(new LambdaQueryWrapperX<ElderlyRetreatRecordDO>()
+                .eq(ElderlyRetreatRecordDO::getTenantId, targetTenantId)
+                .ge(ElderlyRetreatRecordDO::getRetreatDate, monthStart)
+                .le(ElderlyRetreatRecordDO::getRetreatDate, today));
+
+        long safeWeekIn = weeklyCheckInCount == null ? 0L : weeklyCheckInCount;
+        long safeWeekOut = weeklyCheckOutCount == null ? 0L : weeklyCheckOutCount;
+        long safeMonthIn = monthlyCheckInCount == null ? 0L : monthlyCheckInCount;
+        long safeMonthOut = monthlyCheckOutCount == null ? 0L : monthlyCheckOutCount;
+
+        long weeklyNetGrowth = safeWeekIn - safeWeekOut;
+        respVO.setWeeklyNetGrowthCount(weeklyNetGrowth);
+        respVO.setPeriodBeginCount(Math.max(0L, respVO.getPeriodEndCount() - weeklyNetGrowth));
+        respVO.setMonthlyNetGrowthCount(safeMonthIn - safeMonthOut);
+
+        Long periodEndEmptyBedCount = buildBedMapper.selectCount(new LambdaQueryWrapperX<BuildBedDO>()
+                .eq(BuildBedDO::getTenantId, targetTenantId)
+                .eq(BuildBedDO::getOrgType, 1)
+                .eq(BuildBedDO::getStatus, 0));
+        respVO.setPeriodEndEmptyBedCount(periodEndEmptyBedCount == null ? 0L : periodEndEmptyBedCount);
+
+        return respVO;
+    }
+
 
     private List<TodayUpdate> getTodayUpdate(ElderlyInfoHomeDataReqVO reqVO) {
         Long tenantId = reqVO.getTenantIds()[0];

+ 11 - 2
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/ExpenseOrderServiceImpl.java

@@ -245,7 +245,7 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
                     .eq(ElderlyContractDO::getElderId, elderlyInfo.getId())
                     .orderByDesc(ElderlyContractDO::getCreatedTime));
             ElderlyContractDO elderlyContractDO = elderlyContractList.stream().findFirst().get();
-
+            respVO.setContractNumber(elderlyContractDO.getContractNumber());
             // 加一段逻辑,如果长者当前月份账单过期,就取月头和过期时间生成备注描述,合同未过期就取月头和月尾生成备注描述
             LocalDate startDay = YearMonth.parse(expenseOrder.getBillingMonth(), DateTimeFormatter.ofPattern("yyyy-MM")).atDay(1);
             LocalDate lastDayOfMonth = startDay.with(TemporalAdjusters.lastDayOfMonth());
@@ -2842,6 +2842,7 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
         isExpenseOrderLock(expenseOrderDO);
         BigDecimal totalAmount = BigDecimal.ZERO;
         List<ExpenseOrderItemEditVO> items = saveVO.getItems();
+        List<DailyExpensesDO> updateDailyExpenseList = new ArrayList<>();
         for (ExpenseOrderItemEditVO editVO : items) {
             ExpenseOrderItemDO orderItemDO = expenseOrderItemMapper.selectById(editVO.getId());
             orderItemDO.setIsShow(editVO.getIsShow());
@@ -2852,11 +2853,19 @@ public class ExpenseOrderServiceImpl implements ExpenseOrderService {
             orderItemDO.setRoundTwoDecimalAmount(orderItemDO.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
             orderItemDO.setDescription(editVO.getDescription() == null ? "" : editVO.getDescription());
             expenseOrderItemMapper.updateById(orderItemDO);
-
+            if(editVO.getExpenseSource().equals(BusinessConstants.DAILY_EXPENSES)){
+                DailyExpensesDO update = new DailyExpensesDO();
+                update.setRemarks(editVO.getDescription());
+                update.setCount(editVO.getCount());
+                update.setPrice(editVO.getPrice());
+                update.setAmount(editVO.getTotalAmount());
+                updateDailyExpenseList.add(update);
+            }
             totalAmount = totalAmount.add(orderItemDO.getTotalAmount());
         }
         expenseOrderDO.setActualAmount(totalAmount);
         expenseOrderMapper.updateById(expenseOrderDO);
+        dailyExpensesMapper.updateBatch(updateDailyExpenseList);
     }
 
     @Override

+ 15 - 117
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/MaterialIoService.java

@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.system.service.biz;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ImportResultVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialInBalanceListReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialInBalanceRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialIoOrderPageReqVO;
@@ -8,21 +9,24 @@ import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.Materia
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialIoOrderRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialIoOrderSaveReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inbound.*;
+import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inboundreturn.MaterialInboundReturnOrderPageReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inboundreturn.MaterialInboundReturnOrderPageRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inboundreturn.MaterialInboundReturnOrderRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inventory.MaterialInventoryFlowReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inventory.MaterialInventoryFlowRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inventory.MaterialInventoryReportPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inventory.MaterialInventoryReportRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.outbound.*;
-import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inboundreturn.MaterialInboundReturnOrderPageReqVO;
-import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.inboundreturn.MaterialInboundReturnOrderPageRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.outboundreturn.MaterialOutboundReturnOrderPageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.outboundreturn.MaterialOutboundReturnOrderPageRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.outboundreturn.MaterialOutboundReturnOrderRespVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.biz.MaterialInboundOrderDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.biz.MaterialOutboundOrderDO;
+import org.springframework.web.multipart.MultipartFile;
 
+import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
+import java.io.IOException;
 import java.util.List;
 
 /**
@@ -30,164 +34,58 @@ import java.util.List;
  */
 public interface MaterialIoService {
 
-    /**
-     * 物资入库
-     *
-     * @param reqVO 创建信息
-     * @return 编号
-     */
     Long inbound(@Valid MaterialIoOrderSaveReqVO reqVO);
 
-    /**
-     * 物资出库
-     *
-     * @param reqVO 创建信息
-     * @return 编号
-     */
     Long outbound(@Valid MaterialIoOrderSaveReqVO reqVO);
 
-    /**
-     * 出库退回
-     *
-     * @param reqVO 创建信息
-     * @return 编号
-     */
     Long outboundReturn(@Valid MaterialIoOrderSaveReqVO reqVO);
 
-    /**
-     * 入库退回
-     *
-     * @param reqVO 创建信息
-     * @return 编号
-     */
     Long inboundReturn(@Valid MaterialIoOrderSaveReqVO reqVO);
 
-    /**
-     * 更新出入库单
-     *
-     * @param updateReqVO 更新信息
-     */
     void update(@Valid MaterialIoOrderSaveReqVO updateReqVO);
 
-    /**
-     * 删除出入库单
-     *
-     * @param id 编号
-     */
     void delete(Long id);
 
-    /**
-     * 获得入库单详情
-     *
-     * @param id 编号
-     * @return 入库单详情
-     */
     MaterialInboundOrderRespVO getInboundOrder(Long id);
 
-    /**
-     * 获得出库单详情
-     *
-     * @param id 编号
-     * @return 出库单详情
-     */
     MaterialOutboundOrderRespVO getOutboundOrder(Long id);
 
-    /**
-     * 获得出入库单分页
-     *
-     * @param pageReqVO 分页查询
-     * @return 出入库单分页
-     */
     PageResult<MaterialIoOrderPageRespVO> page(MaterialIoOrderPageReqVO pageReqVO);
 
-    /**
-     * 获得入库批次列表(不分页,用于出库时选择可用批次)
-     *
-     * @param reqVO 查询条件
-     * @return 入库批次列表
-     */
     List<MaterialInBalanceRespVO> listInboundBatches(MaterialInBalanceListReqVO reqVO);
 
-    /**
-     * 入库单下拉列表
-     *
-     * @param tenantIds 查询条件
-     * @return 入库单下拉列表
-     */
     List<MaterialInboundOrderDO> inboundList(Long[] tenantIds);
-    /**
-     * 出库单下拉列表
-     *
-     * @param tenantIds 查询条件
-     * @return 出库单下拉列表
-     */
+
     List<MaterialOutboundOrderDO> outboundList(Long[] tenantIds);
 
-    /**
-     * 获取出库退回单详情
-     *
-     * @param id 退回单ID
-     * @return 出库退回单详情
-     */
     MaterialOutboundReturnOrderRespVO getOutboundReturn(Long id);
 
-    /**
-     * 获取入库退回单详情
-     *
-     * @param id 退回单ID
-     * @return 入库退回单详情
-     */
     MaterialInboundReturnOrderRespVO getInboundReturn(Long id);
 
-    /**
-     * 出库退回单分页
-     *
-     * @param reqVO 查询条件
-     * @return 分页结果
-     */
     PageResult<MaterialOutboundReturnOrderPageRespVO> pageOutboundReturn(MaterialOutboundReturnOrderPageReqVO reqVO);
 
-    /**
-     * 入库退回单分页
-     *
-     * @param reqVO 查询条件
-     * @return 分页结果
-     */
     PageResult<MaterialInboundReturnOrderPageRespVO> pageInboundReturn(MaterialInboundReturnOrderPageReqVO reqVO);
 
-    /**
-     * 入库统计汇总分页
-     */
     PageResult<MaterialInboundSummaryRespVO> pageInboundSummary(MaterialInboundSummaryPageReqVO reqVO);
 
-    /**
-     * 入库统计明细分页
-     */
     PageResult<MaterialInboundDetailRespVO> pageInboundDetail(MaterialInboundDetailPageReqVO reqVO);
 
-    /**
-     * 出库统计汇总分页
-     */
     PageResult<MaterialOutboundSummaryRespVO> pageOutboundSummary(MaterialOutboundSummaryPageReqVO reqVO);
 
-    /**
-     * 出库统计明细分页
-     */
     PageResult<MaterialOutboundDetailRespVO> pageOutboundDetail(MaterialOutboundDetailPageReqVO reqVO);
 
-    /**
-     * 库存查询报表分页
-     */
     PageResult<MaterialInventoryReportRespVO> pageInventoryReport(MaterialInventoryReportPageReqVO reqVO);
 
-    /**
-     * 库存查询报表导出列表(不分页)
-     */
     List<MaterialInventoryReportRespVO> listInventoryReport(MaterialInventoryReportPageReqVO reqVO);
 
-    /**
-     * 库存流水查询
-     */
     MaterialInventoryFlowRespVO getInventoryFlow(MaterialInventoryFlowReqVO reqVO);
 
+    void exportInboundImportTemplate(HttpServletResponse response) throws IOException;
+
+    void exportOutboundImportTemplate(HttpServletResponse response) throws IOException;
+
+    ImportResultVO importInbound(MultipartFile file);
+
+    ImportResultVO importOutbound(MultipartFile file);
+
 }

+ 103 - 4
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/MaterialIoServiceImpl.java

@@ -2,6 +2,11 @@ package cn.iocoder.yudao.module.system.service.biz;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.module.system.controller.admin.biz.vo.ImportResultVO;
+import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialIoImportExcelVO;
+import cn.iocoder.yudao.module.system.util.ImportUtil;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialInBalanceListReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialInBalanceRespVO;
 import cn.iocoder.yudao.module.system.controller.admin.biz.vo.materialio.MaterialIoOrderItemSaveReqVO;
@@ -36,6 +41,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.io.IOException;
 import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.time.YearMonth;
@@ -44,6 +50,9 @@ import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.web.multipart.MultipartFile;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exceptionCustomMsg;
@@ -1899,8 +1908,16 @@ public class MaterialIoServiceImpl implements MaterialIoService {
         LambdaQueryWrapper<MaterialInboundOrderItemDO> wrapper = new LambdaQueryWrapperX<MaterialInboundOrderItemDO>()
                 .inIfPresent(MaterialInboundOrderItemDO::getTenantId, (Object[]) reqVO.getTenantIds())
                 .eqIfPresent(MaterialInboundOrderItemDO::getStoreId, reqVO.getStoreId())
-                .inSql(MaterialInboundOrderItemDO::getInboundOrderId,
-                        "SELECT id FROM material_inbound_order WHERE order_date >= '" + monthStart + "' AND order_date < '" + nextMonthStart + "'");
+                // 口径:
+                // 1) 入库单为当月;
+                // 2) 或者该入库批次在当月发生过出库(即使入库单不是当月)
+                .and(w -> w.inSql(MaterialInboundOrderItemDO::getInboundOrderId,
+                                "SELECT id FROM material_inbound_order WHERE order_date >= '" + monthStart + "' AND order_date < '" + nextMonthStart + "'")
+                        .or()
+                        .inSql(MaterialInboundOrderItemDO::getId,
+                                "SELECT oi.ref_inbound_item_id FROM material_outbound_order_item oi " +
+                                        "INNER JOIN material_outbound_order o ON oi.outbound_order_id = o.id " +
+                                        "WHERE o.order_date >= '" + monthStart + "' AND o.order_date < '" + nextMonthStart + "'"));
 
         // 关键字匹配物资编码/名称
         if (StringUtils.isNotBlank(reqVO.getKeyword())) {
@@ -1953,8 +1970,16 @@ public class MaterialIoServiceImpl implements MaterialIoService {
         LambdaQueryWrapper<MaterialInboundOrderItemDO> wrapper = new LambdaQueryWrapperX<MaterialInboundOrderItemDO>()
                 .inIfPresent(MaterialInboundOrderItemDO::getTenantId, (Object[]) reqVO.getTenantIds())
                 .eqIfPresent(MaterialInboundOrderItemDO::getStoreId, reqVO.getStoreId())
-                .inSql(MaterialInboundOrderItemDO::getInboundOrderId,
-                        "SELECT id FROM material_inbound_order WHERE order_date >= '" + monthStart + "' AND order_date < '" + nextMonthStart + "'");
+                // 口径:
+                // 1) 入库单为当月;
+                // 2) 或者该入库批次在当月发生过出库(即使入库单不是当月)
+                .and(w -> w.inSql(MaterialInboundOrderItemDO::getInboundOrderId,
+                                "SELECT id FROM material_inbound_order WHERE order_date >= '" + monthStart + "' AND order_date < '" + nextMonthStart + "'")
+                        .or()
+                        .inSql(MaterialInboundOrderItemDO::getId,
+                                "SELECT oi.ref_inbound_item_id FROM material_outbound_order_item oi " +
+                                        "INNER JOIN material_outbound_order o ON oi.outbound_order_id = o.id " +
+                                        "WHERE o.order_date >= '" + monthStart + "' AND o.order_date < '" + nextMonthStart + "'"));
 
         if (StringUtils.isNotBlank(reqVO.getKeyword())) {
             List<Long> materialIds = materialInfoMapper.selectList(
@@ -2296,6 +2321,80 @@ public class MaterialIoServiceImpl implements MaterialIoService {
         inboundOrderItemMapper.updateById(inboundItem);
     }
 
+    @Override
+    public void exportImportTemplate(HttpServletResponse response) throws IOException {
+        ExcelUtils.write(response, "物资出入库导入模板.xlsx", "模板", MaterialIoImportExcelVO.class, Collections.emptyList());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ImportResultVO importMaterialIo(MultipartFile file) {
+        List<MaterialIoImportExcelVO> rows = ExcelUtils.read(file, MaterialIoImportExcelVO.class);
+        int success = 0;
+        int rowNum = 1;
+        List<ImportResultVO.RowError> failures = new ArrayList<>();
+
+        for (MaterialIoImportExcelVO row : rows) {
+            rowNum++;
+            try {
+                MaterialIoOrderSaveReqVO reqVO = buildImportReq(row);
+                if ("IN".equalsIgnoreCase(reqVO.getBizType())) {
+                    inbound(reqVO);
+                } else if ("OUT".equalsIgnoreCase(reqVO.getBizType())) {
+                    outbound(reqVO);
+                } else {
+                    throw new IllegalArgumentException("业务类型仅支持 IN 或 OUT");
+                }
+                success++;
+            } catch (Exception e) {
+                ImportUtil.addFailure(failures, rowNum, null,
+                        row.getMaterialId() == null ? null : String.valueOf(row.getMaterialId()),
+                        e.getMessage(), e);
+            }
+        }
+        return ImportUtil.buildResult(success, failures);
+    }
+
+    private MaterialIoOrderSaveReqVO buildImportReq(MaterialIoImportExcelVO row) {
+        if (row.getBizType() == null) {
+            throw new IllegalArgumentException("业务类型不能为空");
+        }
+        if (row.getOrderDate() == null) {
+            throw new IllegalArgumentException("单据日期不能为空");
+        }
+        if (row.getMaterialId() == null) {
+            throw new IllegalArgumentException("物资ID不能为空");
+        }
+        if (row.getQuantity() == null || row.getQuantity() <= 0) {
+            throw new IllegalArgumentException("数量必须大于0");
+        }
+
+        MaterialIoOrderItemSaveReqVO item = new MaterialIoOrderItemSaveReqVO();
+        item.setMaterialId(row.getMaterialId());
+        item.setInboundItemId(row.getInboundItemId());
+        item.setRefInStoreId(row.getRefInStoreId());
+        item.setQuantity(row.getQuantity());
+        item.setInUnitPrice(row.getInUnitPrice());
+        item.setSaleUnitPrice(row.getSaleUnitPrice());
+        item.setAmount(row.getAmount());
+        item.setProduceDate(row.getProduceDate());
+
+        MaterialIoOrderSaveReqVO reqVO = new MaterialIoOrderSaveReqVO();
+        reqVO.setBizType(row.getBizType().trim().toUpperCase());
+        reqVO.setOrderDate(row.getOrderDate());
+        reqVO.setRemark(row.getRemark());
+        reqVO.setOutDeptId(row.getOutDeptId());
+        reqVO.setOutUser(row.getOutUser());
+        reqVO.setOutReason(row.getOutReason());
+        reqVO.setTenantId(TenantContextHolder.getTenantId());
+        reqVO.setItems(Collections.singletonList(item));
+
+        if ("OUT".equals(reqVO.getBizType()) && row.getInboundItemId() == null) {
+            throw new IllegalArgumentException("出库导入时,入库批次明细ID不能为空");
+        }
+        return reqVO;
+    }
+
     /**
      * 批量加载物资信息
      */

+ 4 - 1
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/biz/SysOverheadChargeServiceImpl.java

@@ -165,6 +165,7 @@ public class SysOverheadChargeServiceImpl implements SysOverheadChargeService {
         List<Long> categoryIdList = new ArrayList<>();
         List<SysChargeCategoryDO> categoryList = sysChargeCategoryMapper.selectList(new LambdaQueryWrapperX<SysChargeCategoryDO>()
                 .eq(SysChargeCategoryDO::getIdentification, type)
+                .eq(SysChargeCategoryDO::getStatus,1)
                 .eqIfPresent(SysChargeCategoryDO::getTenantId, startTenantId));
         for (SysChargeCategoryDO sysChargeCategory : categoryList) {
             //查询节点下面的所有子节点
@@ -177,7 +178,9 @@ public class SysOverheadChargeServiceImpl implements SysOverheadChargeService {
         Long finalStartTenantId = startTenantId;
         List<List<OverheadChargeDO>> collect = categoryIdList.stream().map(item -> sysOverheadChargeMapper
                         .selectList(new LambdaQueryWrapperX<OverheadChargeDO>()
-                                .eq(OverheadChargeDO::getSuperiorsId, item).eqIfPresent(OverheadChargeDO::getTenantId, finalStartTenantId)))
+                                .eq(OverheadChargeDO::getSuperiorsId, item)
+                                .eq(OverheadChargeDO::getStatus,1)
+                                .eqIfPresent(OverheadChargeDO::getTenantId, finalStartTenantId)))
                 .collect(Collectors.toList());
         return collect.stream().flatMap(List::stream).collect(Collectors.toList());
     }

+ 35 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/util/pay/CertUtils.java

@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.system.util.pay;
+
+import java.io.ByteArrayInputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+
+public class CertUtils {
+
+    /**
+     * 从 PEM 格式的证书字符串中提取证书序列号(微信支付格式:十六进制大写,无冒号)
+     * @param certPem 证书 PEM 字符串,例如 "-----BEGIN CERTIFICATE-----\nMII...\n-----END CERTIFICATE-----"
+     * @return 序列号(大写十六进制字符串)
+     * @throws Exception 解析失败时抛出异常
+     */
+    public static String getMerchantSerialNumberFromPem(String certPem) throws Exception {
+        // 1. 移除 PEM 头尾标记和所有空白字符(换行、空格等)
+        String base64Cert = certPem
+                .replace("-----BEGIN CERTIFICATE-----", "")
+                .replace("-----END CERTIFICATE-----", "")
+                .replaceAll("\\s", "");
+
+        // 2. Base64 解码得到 DER 格式的字节数组
+        byte[] derCert = Base64.getDecoder().decode(base64Cert);
+
+        // 3. 使用 CertificateFactory 生成 X509Certificate 对象
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(derCert));
+
+        // 4. 获取序列号(BigInteger),转换为十六进制字符串并转为大写
+        String serialNumber = cert.getSerialNumber().toString(16).toUpperCase();
+
+        return serialNumber;
+    }
+}