zss
2026-04-25 a1a7bb5093d30397078ce42ef025cd07378d1e1c
Merge remote-tracking branch 'origin/dev_New_pro' into dev_New_pro
已添加2个文件
已修改40个文件
1112 ■■■■ 文件已修改
src/main/java/com/ruoyi/basic/controller/StorageAttachmentController.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/StorageAttachment.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/StorageBlobService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/utils/FileUtil.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/common/CommonController.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 83 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/vo/SalesLedgerVo.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockInRecordController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockInventoryController.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockInRecord.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockInRecordService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockInventoryService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockOutRecordService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockUninventoryService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev-pro.yml 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInventoryMapper.xml 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockUninventoryMapper.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/StorageAttachmentController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package com.ruoyi.basic.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.StorageAttachmentDTO;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.dto.SupplierManageDto;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.basic.service.StorageAttachmentService;
import com.ruoyi.common.constant.StorageAttachmentConstants;
import com.ruoyi.common.enums.StorageAttachmentRecordType;
import com.ruoyi.framework.web.domain.R;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@AllArgsConstructor
@RequestMapping("/basic/storage_attachment")
public class StorageAttachmentController {
    private StorageAttachmentService storageAttachmentService;
    /**
     * åˆ†é¡µæŸ¥è¯¢é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     * @param page åˆ†é¡µå‚æ•°
     * @param storageAttachmentDTO å…³è”记录信息
     * @return åˆ†é¡µç»“æžœ
     */
    @GetMapping("/listPage")
    public R listPage(Page page, StorageAttachmentDTO storageAttachmentDTO) {
        return R.ok(storageAttachmentService.listPage(page, storageAttachmentDTO));
    }
    /**
     * åˆ é™¤é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     * @param ids æ–‡ä»¶id列表
     * @return åˆ é™¤ç»“æžœ
     */
    @DeleteMapping("/delete")
    public R batchDelete(@RequestBody List<Long> ids) {
        return R.ok(storageAttachmentService.batchDeleteStorageAttachment(ids));
    }
    /**
     * ä¿å­˜é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     */
    @PostMapping("/add")
    public R add(@RequestBody StorageAttachmentDTO storageAttachmentDTO) {
        storageAttachmentService.saveStorageAttachment(storageAttachmentDTO);
        return R.ok();
    }
}
src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java
@@ -14,4 +14,9 @@
     * ä¸‹è½½åœ°å€
     */
    private String downloadURL;
    /**
     * æ–‡ä»¶ç±»åž‹
     */
    private String application;
}
src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java
@@ -11,4 +11,19 @@
    private final String type;
    ApplicationTypeEnum(String type) { this.type = type; }
    public String getType() { return type; }
    /**
     * æ ¹æ® type å€¼èŽ·å–å¯¹åº”çš„æžšä¸¾å®žä¾‹
     * @param type åº”用类型字符串
     * @return å¯¹åº”çš„ ApplicationTypeEnum æžšä¸¾å®žä¾‹
     * @throws RuntimeException å¦‚æžœ type æ— æ•ˆ
     */
    public static ApplicationTypeEnum getByType(String type) {
        for (ApplicationTypeEnum enumValue : ApplicationTypeEnum.values()) {
            if (enumValue.getType().equals(type)) {
                return enumValue;
            }
        }
        throw new RuntimeException("无效的应用类型: " + type);
    }
}
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -3,9 +3,28 @@
public enum RecordTypeEnum {
    SHIPPING_INFO("shipping_info"),
    INSPECTION_TASK("inspection_task"),
    PDA_VERSION("pda_version");
    PDA_VERSION("pda_version"),
    SALES_LEDGER("sales_ledger"),
    SUPPLIER_MANAGE("supplier_manage"),
    APPROVAL_PROCESS("approval_process"),
    QR_CODE_SCAN_RECORDS("qr_code_scan_records");
    private final String type;
    RecordTypeEnum(String type) { this.type = type; }
    public String getType() { return type; }
    /**
     * æ ¹æ® type å€¼èŽ·å–å¯¹åº”çš„æžšä¸¾å®žä¾‹
     * @param type è®°å½•类型字符串
     * @return å¯¹åº”çš„ RecordTypeEnum æžšä¸¾å®žä¾‹
     * @throws RuntimeException å¦‚æžœ type æ— æ•ˆ
     */
    public static RecordTypeEnum getByType(String type) {
        for (RecordTypeEnum enumValue : RecordTypeEnum.values()) {
            if (enumValue.getType().equals(type)) {
                return enumValue;
            }
        }
        throw new RuntimeException("无效的记录类型: " + type);
    }
}
src/main/java/com/ruoyi/basic/pojo/StorageAttachment.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import lombok.Data;
import java.io.Serializable;
src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
@@ -1,6 +1,13 @@
package com.ruoyi.basic.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.dto.StorageAttachmentDTO;
import com.ruoyi.basic.dto.StorageAttachmentVO;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.common.constant.StorageAttachmentConstants;
import com.ruoyi.common.enums.StorageAttachmentRecordType;
@@ -19,15 +26,16 @@
    /**
     * ä¿å­˜é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     * @param attachments æ–‡ä»¶ä¿¡æ¯åˆ—表
     * @param recordId ç®¡ç†è®°å½•id
     * @param recordType å…³è”记录类型
     * @param fileType æ–‡ä»¶ç±»åž‹
     */
    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType);
    public void saveStorageAttachment(StorageAttachmentDTO storageAttachmentDTO);
    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, String fileType);
    /**
     * åˆ†é¡µæŸ¥è¯¢é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     * @param page åˆ†é¡µå‚æ•°
     * @param storageAttachmentDTO å…³è”记录信息
     * @return åˆ†é¡µç»“æžœ
     */
    public IPage<StorageAttachmentVO> listPage(Page page, StorageAttachmentDTO storageAttachmentDTO);
    /**
     * åˆ é™¤é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
@@ -35,4 +43,11 @@
     * @return åˆ é™¤ç»“æžœ
     */
    public int deleteStorageAttachment(StorageAttachment storageAttachment);
    /**
     * æ‰¹é‡åˆ é™¤é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     * @param ids æ–‡ä»¶id列表
     * @return åˆ é™¤ç»“æžœ
     */
    public int batchDeleteStorageAttachment(List<Long> ids);
}
src/main/java/com/ruoyi/basic/service/StorageBlobService.java
@@ -23,9 +23,11 @@
     * @param files æ–‡ä»¶åˆ—表
     * @return ä¸Šä¼ ç»“æžœ
     */
    List<StorageBlobVO> upload(List<MultipartFile> files);
    List<StorageBlobVO> upload(List<MultipartFile> files, Boolean isPublic);
    File getFileByToken(String fileName, String token);
    public String getDownloadFileName(String fileName);
    File getPublicFile(String fileName, String publicKey);
    String getDownloadFileName(String fileName);
}
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -25,7 +25,10 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
@@ -1,15 +1,22 @@
package com.ruoyi.basic.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.StorageAttachmentDTO;
import com.ruoyi.basic.dto.StorageAttachmentVO;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.StorageAttachmentMapper;
import com.ruoyi.basic.mapper.StorageBlobMapper;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.basic.service.StorageAttachmentService;
import com.ruoyi.basic.service.StorageBlobService;
import com.ruoyi.common.constant.StorageAttachmentConstants;
import com.ruoyi.common.enums.StorageAttachmentRecordType;
import com.ruoyi.basic.utils.FileUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@@ -29,40 +36,17 @@
    private final StorageAttachmentMapper storageAttachmentMapper;
    private final StorageBlobService storageBlobService;
    private final FileUtil fileUtil;
    @Override
    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType) {
        // åˆ é™¤æ—§å›¾
        // todo fileChange
//        deleteStorageAttachment(new StorageAttachment(fileType.toString(), (long) recordType.ordinal(), recordId));
//        for (StorageAttachment attachment : attachments) {
//            // èŽ·å–å…³è”è®°å½•
//            StorageBlob storageBlob = attachment.getStorageBlobDTO();
//            attachment.setName(fileType.toString());
//            attachment.setRecordType((long) recordType.ordinal());
//            attachment.setRecordId(recordId);
//            attachment.setStorageBlobId(storageBlob.getId());
//            storageAttachmentMapper.insert(attachment);
//        }
    @Transactional(rollbackFor = Exception.class)
    public void saveStorageAttachment(StorageAttachmentDTO storageAttachmentDTO) {
        fileUtil.saveStorageAttachmentByRecordTypeAndRecordId(storageAttachmentDTO.getApplication(), RecordTypeEnum.getByType(storageAttachmentDTO.getRecordType()), storageAttachmentDTO.getRecordId(), storageAttachmentDTO.getStorageBlobDTOs());
    }
    @Override
    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, String fileType) {
        // åˆ é™¤æ—§å›¾
//        deleteStorageAttachment(new StorageAttachment(fileType, (long) recordType.ordinal(), recordId));
        // todo fileChange
//        for (StorageAttachment attachment : attachments) {
//            // èŽ·å–å…³è”è®°å½•
//            StorageBlob storageBlob = attachment.getStorageBlobDTO();
//            attachment.setName(fileType);
//            attachment.setRecordType((long) recordType.ordinal());
//            attachment.setRecordId(recordId);
//            attachment.setStorageBlobId(storageBlob.getId());
//            storageAttachmentMapper.insert(attachment);
//        }
    public IPage<StorageAttachmentVO> listPage(Page page, StorageAttachmentDTO storageAttachmentDTO) {
        return fileUtil.getStorageAttachmentVosPageListByApplicationAndRecordTypeAndRecordId(page, storageAttachmentDTO);
    }
    @Override
@@ -79,4 +63,10 @@
//    }
        return 0;
    }
    @Override
    public int batchDeleteStorageAttachment(List<Long> ids) {
        fileUtil.deleteStorageAttachmentsByStorageAttachmentIds(ids);
        return 1;
    }
}
src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java
@@ -20,6 +20,7 @@
import javax.crypto.SecretKey;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -39,7 +40,7 @@
    private final FileUtil fileUtil;
    @Override
    public List<StorageBlobVO> upload(List<MultipartFile> files) {
    public List<StorageBlobVO> upload(List<MultipartFile> files, Boolean isPublic) {
        if (CollectionUtils.isEmpty(files)) {
            throw new IllegalArgumentException("文件不能为空");
        }
@@ -65,7 +66,7 @@
            StorageBlobVO storageBlob;
            try {
                file.transferTo(dest);
                storageBlob = getStorageBlob(file, originalFileName, fileName, relativePath);
                storageBlob = getStorageBlob(file, originalFileName, fileName, relativePath, isPublic);
                if (storageBlob == null || storageBlob.getId() == null) {
                    throw new RuntimeException("文件元数据保存失败");
                }
@@ -109,13 +110,35 @@
        return new File(new File(properties.getPath(), path), fileName);
    }
    @Override
    public File getPublicFile(String fileName, String publicKey) {
        if (!StringUtils.hasText(fileName)) {
            throw new IllegalArgumentException("文件名不能为空");
        }
        if (!StringUtils.hasText(publicKey)) {
            throw new IllegalArgumentException("publicKey不能为空");
        }
        StorageBlob storageBlob = storageBlobMapper.selectOne(new LambdaQueryWrapper<StorageBlob>()
                .eq(StorageBlob::getUidFilename, fileName)
                .eq(StorageBlob::getResourceKey, publicKey)
                .last("limit 1"));
        if (storageBlob == null) {
            throw new IllegalArgumentException("公开文件不存在或publicKey不匹配");
        }
        String path = storageBlob.getPath();
        if (!StringUtils.hasText(path)) {
            return new File(properties.getPath(), fileName);
        }
        return new File(new File(properties.getPath(), path), fileName);
    }
    private StorageBlob findStorageBlob(String fileName) {
        return storageBlobMapper.selectOne(new LambdaQueryWrapper<StorageBlob>()
                .eq(StorageBlob::getUidFilename, fileName)
                .last("limit 1"));
    }
    private StorageBlobVO getStorageBlob(MultipartFile file, String originalFileName, String fileName, String relativePath) {
    private StorageBlobVO getStorageBlob(MultipartFile file, String originalFileName, String fileName, String relativePath, Boolean isPublic) {
        StorageBlobVO storageBlob = new StorageBlobVO();
        storageBlob.setResourceKey(UUID.randomUUID().toString().replace("-", ""));
        storageBlob.setContentType(file.getContentType());
@@ -123,8 +146,13 @@
        storageBlob.setUidFilename(fileName);
        storageBlob.setByteSize(file.getSize());
        storageBlob.setPath(relativePath);
        storageBlob.setPreviewURL(fileUtil.buildSignedPreviewUrl(storageBlob));
        storageBlob.setDownloadURL(fileUtil.buildSignedDownloadUrl(storageBlob));
        if (isPublic) {
            storageBlob.setPreviewURL(fileUtil.buildSignedUrl(storageBlob, "/preview/", BigDecimal.valueOf(-1)));
            storageBlob.setDownloadURL(fileUtil.buildSignedUrl(storageBlob, "/download/", BigDecimal.valueOf(-1)));
        } else {
            storageBlob.setPreviewURL(fileUtil.buildSignedPreviewUrl(storageBlob));
            storageBlob.setDownloadURL(fileUtil.buildSignedDownloadUrl(storageBlob));
        }
        int affectedRows = storageBlobMapper.insert(storageBlob);
        if (affectedRows <= 0) {
            throw new RuntimeException("文件元数据保存失败");
src/main/java/com/ruoyi/basic/utils/FileUtil.java
@@ -1,7 +1,10 @@
package com.ruoyi.basic.utils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.StorageAttachmentDTO;
import com.ruoyi.basic.dto.StorageAttachmentVO;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.dto.StorageBlobVO;
@@ -52,9 +55,6 @@
     * @param storageBlobDTOS æ–‡ä»¶ä¿¡æ¯
     */
    public void saveStorageAttachment(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, List<StorageBlobDTO> storageBlobDTOS) {
        if (CollectionUtils.isEmpty(storageBlobDTOS)) {
            throw new RuntimeException("文件信息不能为空");
        }
        if (application == null) {
            throw new RuntimeException("文件用途不能为空");
        }
@@ -66,10 +66,54 @@
        }
        // åˆ é™¤æ—§é™„件信息
        deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
        if (CollectionUtils.isEmpty(storageBlobDTOS)) {
            return;
        }
        List<StorageAttachment> storageAttachments = new ArrayList<>();
        for (StorageBlobDTO storageBlobDTO : storageBlobDTOS) {
            StorageAttachment storageAttachment = new StorageAttachment();
            storageAttachment.setApplication(application.getType());
            storageAttachment.setRecordType(recordType.getType());
            storageAttachment.setRecordId(recordId);
            storageAttachment.setStorageBlobId(storageBlobDTO.getId());
            storageAttachment.setDeleted(0L);
            storageAttachments.add(storageAttachment);
        }
        storageAttachmentMapper.insert(storageAttachments);
    }
    /**
     * ä¿å­˜é™„件信息
     *
     * @param recordType      å…³è”记录类型
     * @param recordId        å…³è”记录id
     * @param storageBlobDTOS æ–‡ä»¶ä¿¡æ¯
     */
    public void saveStorageAttachmentByRecordTypeAndRecordId(String application,RecordTypeEnum recordType, Long recordId, List<StorageBlobDTO> storageBlobDTOS) {
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        if (recordId == null) {
            throw new RuntimeException("关联记录id不能为空");
        }
        // åˆ é™¤æ—§é™„件信息
        if (application == null) {
            for (StorageBlobDTO storageBlobDTO : storageBlobDTOS) {
                deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.getByType(storageBlobDTO.getApplication()), recordType, recordId);
            }
        } else {
            deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.getByType(application), recordType, recordId);
        }
        if (CollectionUtils.isEmpty(storageBlobDTOS)) {
            deleteStorageAttachmentsByRecordTypeAndRecordId(recordType, recordId);
        }
        List<StorageAttachment> storageAttachments = new ArrayList<>();
        for (StorageBlobDTO storageBlobDTO : storageBlobDTOS) {
            StorageAttachment storageAttachment = new StorageAttachment();
            storageAttachment.setApplication(Objects.requireNonNullElseGet(application, () -> ApplicationTypeEnum.getByType(storageBlobDTO.getApplication()).getType()));
            storageAttachment.setRecordType(recordType.getType());
            storageAttachment.setRecordId(recordId);
            storageAttachment.setStorageBlobId(storageBlobDTO.getId());
@@ -128,6 +172,29 @@
    }
    /**
     * é€šè¿‡å…³è”记录类型、关联记录id删除文件信息
     *
     * @param recordType  å…³è”记录类型
     * @param recordId    å…³è”记录id
     */
    public void deleteStorageBlobsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId) {
        if (recordId == null || recordId <= 0) {
            throw new RuntimeException("关联记录id不能为空");
        }
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        List<StorageAttachment> storageAttachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordType, recordType.getType())
                .eq(StorageAttachment::getRecordId, recordId));
        if (CollectionUtils.isNotEmpty(storageAttachments)) {
            List<Long> storageAttachmentIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId)
                    .collect(Collectors.toList());
            deleteStorageBlobsByStorageAttachmentIds(storageAttachmentIds);
        }
    }
    /**
     * åˆ é™¤æ–‡ä»¶å…³è”信息
     *
     * @param storageAttachmentIds æ–‡ä»¶å…³è”id
@@ -159,6 +226,25 @@
                .eq(StorageAttachment::getRecordType, recordType.getType())
                .eq(StorageAttachment::getRecordId, recordId)
                .eq(StorageAttachment::getApplication, application.getType()));
    }
    /**
     * åˆ é™¤æ–‡ä»¶å…³è”信息
     *
     * @param recordType  å…³è”记录类型
     * @param recordId    å…³è”记录id
     */
    public void deleteStorageAttachmentsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId) {
        if (recordId == null || recordId <= 0) {
            throw new RuntimeException("关联记录id不能为空");
        }
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        deleteStorageBlobsByRecordTypeAndRecordId(recordType, recordId);
        storageAttachmentMapper.delete(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordType, recordType.getType())
                .eq(StorageAttachment::getRecordId, recordId));
    }
    /**
@@ -195,6 +281,46 @@
            throw new RuntimeException("文件id不能为空");
        }
        return storageAttachmentMapper.selectByIds(storageAttachmentIds);
    }
    /**
     * é€šè¿‡è®°å½•类型获取文件信息 attachment(分页)
     *
     * @param page       åˆ†é¡µå‚æ•°
     * @param storageAttachmentDTO å…³è”记录信息
     */
    public IPage<StorageAttachmentVO> getStorageAttachmentVosPageListByApplicationAndRecordTypeAndRecordId(Page page, StorageAttachmentDTO storageAttachmentDTO) {
        // åˆ†é¡µæŸ¥è¯¢ç¬¦åˆæ¡ä»¶çš„ StorageAttachment è®°å½•
        LambdaQueryWrapper<StorageAttachment> queryWrapper = new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordType, storageAttachmentDTO.getRecordType())
                .eq(StorageAttachment::getRecordId, storageAttachmentDTO.getRecordId());
        if (storageAttachmentDTO.getApplication() != null) {
            queryWrapper.eq(StorageAttachment::getApplication, storageAttachmentDTO.getApplication());
        }
        IPage<StorageAttachmentVO> storageAttachmentIPage = storageAttachmentMapper.selectPage(page, queryWrapper);
        // è½¬æ¢ä¸º StorageAttachmentVO å¹¶èŽ·å–å¯¹åº”çš„ StorageBlobVO
        List<StorageAttachmentVO> storageAttachmentVOS = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(storageAttachmentIPage.getRecords())) {
            for (StorageAttachment storageAttachment : storageAttachmentIPage.getRecords()) {
                StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO();
                BeanUtils.copyProperties(storageAttachment, storageAttachmentVO);
                List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(Collections.singletonList(storageAttachment.getId()));
                if (CollectionUtils.isEmpty(storageBlobVOS)) {
                    storageAttachmentVO.setStorageBlobVOS(new ArrayList<>());
                } else {
                    storageAttachmentVO.setStorageBlobVOS(storageBlobVOS);
                }
                storageAttachmentVOS.add(storageAttachmentVO);
            }
        }
        // æž„建分页结果
        IPage<StorageAttachmentVO> resultPage = new Page<>();
        BeanUtils.copyProperties(storageAttachmentIPage, resultPage);
        resultPage.setRecords(storageAttachmentVOS);
        return resultPage;
    }
    /**
@@ -565,8 +691,28 @@
        if (storageBlob == null || !StringUtils.hasText(storageBlob.getUidFilename())) {
            throw new IllegalArgumentException("文件信息不完整");
        }
        String domain = StringUtils.trimTrailingCharacter(properties.getDomain(), '/');
        String prefix = properties.getUrlPrefix().startsWith("/") ? properties.getUrlPrefix() : "/" + properties.getUrlPrefix();
        String normalizedActionPath = StringUtils.hasText(actionPath) ? actionPath : "/preview/";
        if (!normalizedActionPath.startsWith("/")) {
            normalizedActionPath = "/" + normalizedActionPath;
        }
        if (!normalizedActionPath.endsWith("/")) {
            normalizedActionPath = normalizedActionPath + "/";
        }
        String baseUrl = domain + prefix + normalizedActionPath + storageBlob.getUidFilename();
        // -1 è¡¨ç¤ºæ°¸ä¹…有效,不生成 token,改为 publicKey ç»„合校验
        if (expired != null && BigDecimal.valueOf(-1L).compareTo(expired) == 0) {
            if (!StringUtils.hasText(storageBlob.getResourceKey())) {
                throw new IllegalArgumentException("公开链接缺少publicKey");
            }
            return baseUrl + "?publicKey=" + storageBlob.getResourceKey();
        }
        long now = System.currentTimeMillis();
        long expiredMillis = expired.multiply(new BigDecimal("60000")).longValue();
        BigDecimal expiredValue = expired == null ? new BigDecimal("120") : expired;
        long expiredMillis = expiredValue.multiply(new BigDecimal("60000")).longValue();
        if (expiredMillis <= 0L) {
            expiredMillis = 2L * 60L * 60L * 1000L;
        }
@@ -583,16 +729,7 @@
                .signWith(key)            // é‡ç‚¹ï¼šä¼ å…¥ä¸Šé¢ç”Ÿæˆçš„ key å¯¹è±¡ï¼Œè€Œä¸æ˜¯ String
                .compact();
        cacheTokenUsage(token, expiredMillis);
        String domain = StringUtils.trimTrailingCharacter(properties.getDomain(), '/');
        String prefix = properties.getUrlPrefix().startsWith("/") ? properties.getUrlPrefix() : "/" + properties.getUrlPrefix();
        String normalizedActionPath = StringUtils.hasText(actionPath) ? actionPath : "/preview/";
        if (!normalizedActionPath.startsWith("/")) {
            normalizedActionPath = "/" + normalizedActionPath;
        }
        if (!normalizedActionPath.endsWith("/")) {
            normalizedActionPath = normalizedActionPath + "/";
        }
        return domain + prefix + normalizedActionPath + storageBlob.getUidFilename() + "?token=" + token;
        return baseUrl + "?token=" + token;
    }
    private void cacheTokenUsage(String token, long expiredMillis) {
src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java
@@ -5,11 +5,16 @@
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.StorageAttachmentMapper;
import com.ruoyi.basic.mapper.StorageBlobMapper;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.basic.pojo.StorageBlob;
import com.ruoyi.basic.service.StorageAttachmentService;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.inspectiontask.dto.QrCodeScanRecordDto;
import com.ruoyi.inspectiontask.mapper.QrCodeMapper;
@@ -51,6 +56,8 @@
    private final SysUserMapper sysUserMapper;
    private final FileUtil fileUtil;
    @Override
    public IPage<QrCodeScanRecordDto> selectQrCodeScanRecordList(Page<QrCodeScanRecord> page, QrCodeScanRecordDto qrCodeScanRecordDto) {
@@ -159,19 +166,15 @@
        }
        if (qrCodeScanRecordDto.getStorageBlobVO() != null && !qrCodeScanRecordDto.getStorageBlobVO().isEmpty()) {
            List<StorageAttachment> attachments = new ArrayList<>();
            // vo è½¬ dto
            List<StorageBlobDTO> storageBlobDTOs = new ArrayList<>();
            for (StorageBlobVO storageBlobVO : qrCodeScanRecordDto.getStorageBlobVO()) {
                StorageBlobDTO storageBlobDTO = new StorageBlobDTO();
                BeanUtils.copyProperties(storageBlobVO, storageBlobDTO);
                storageBlobDTOs.add(storageBlobDTO);
            }
            // todo fileChange
//            for (StorageBlobDTO storageBlobDTO : qrCodeScanRecordDto.getStorageBlobDTO()) {
//                StorageAttachment storageAttachment = new StorageAttachment(
//                        StorageAttachmentFile,
//                        (long) QrCodeScanRecords.ordinal(),
//                        qrCodeScanRecord.getId()
//                );
//                storageAttachment.setStorageBlobDTO(storageBlobDTO);
//                attachments.add(storageAttachment);
//            }
            storageAttachmentService.saveStorageAttachment(attachments, qrCodeScanRecord.getId(), QrCodeScanRecords, StorageAttachmentFile);
            fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.QR_CODE_SCAN_RECORDS, qrCodeScanRecord.getId(), storageBlobDTOs);
        }
        return i;
    }
@@ -181,4 +184,4 @@
        return qrCodeScanRecordMapper.deleteBatchIds(Arrays.asList(ids));
    }
}
}
src/main/java/com/ruoyi/project/common/CommonController.java
@@ -128,15 +128,32 @@
//    }
    @PostMapping({"/upload"})
    @Operation(summary = "文件上传")
    public R upload(@RequestParam("files") List<MultipartFile> files) throws Exception {
        return R.ok(storageBlobService.upload(files));
    public R upload(@RequestParam("files") List<MultipartFile> files) {
        return R.ok(storageBlobService.upload(files, false));
    }
    /**
     * å…¬å…±æ–‡ä»¶ä¸Šä¼ 
     * æ­¤æŽ¥å£ä¸Šä¼ çš„æ–‡ä»¶æ°¸ä¹…有效,慎用
     */
    @PostMapping({"/public/upload"})
    @Operation(summary = "文件上传")
    public R publicUpload(@RequestParam("files") List<MultipartFile> files) {
        return R.ok(storageBlobService.upload(files, true));
    }
    @GetMapping("/download/{fileName}")
    @Anonymous
    public void download(@PathVariable String fileName, @RequestParam("token") String token, HttpServletResponse response) throws Exception {
        File file = storageBlobService.getFileByToken(fileName, token);
    public void download(@PathVariable String fileName,
                         @RequestParam(value = "token", required = false) String token,
                         @RequestParam(value = "publicKey", required = false) String publicKey,
                         HttpServletResponse response) throws Exception {
        File file;
        if (publicKey != null) {
            file = fileUtil.compressFile(storageBlobService.getPublicFile(fileName, publicKey));
        } else {
            file = fileUtil.compressFile(storageBlobService.getFileByToken(fileName, token));
        }
        String originalFileName = storageBlobService.getDownloadFileName(fileName);
        String encodedFileName = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8.name()).replace("+", "%20");
        response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + encodedFileName);
@@ -147,8 +164,14 @@
    @GetMapping("/preview/{fileName}")
    @Anonymous
    public ResponseEntity<FileSystemResource> preview(@PathVariable String fileName,
                                                      @RequestParam("token") String token) throws Exception {
        File file = fileUtil.compressFile(storageBlobService.getFileByToken(fileName, token));
                                                      @RequestParam(value = "token", required = false) String token,
                                                      @RequestParam(value = "publicKey", required = false) String publicKey) throws Exception {
        File file;
        if (publicKey != null) {
            file = fileUtil.compressFile(storageBlobService.getPublicFile(fileName, publicKey));
        } else {
            file = fileUtil.compressFile(storageBlobService.getFileByToken(fileName, token));
        }
        String contentType = Files.probeContentType(file.toPath());
        ContentDisposition contentDisposition = ContentDisposition.inline()
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -4,6 +4,8 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
@@ -19,6 +21,7 @@
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.service.ICommonFileService;
import com.ruoyi.sales.service.ISalesLedgerService;
import com.ruoyi.sales.vo.SalesLedgerVo;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.annotations.ApiParam;
@@ -28,6 +31,7 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.basic.utils.FileUtil;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -55,6 +59,7 @@
    private ICommonFileService commonFileService;
    private InvoiceLedgerMapper invoiceLedgerMapper;
    private ReceiptPaymentMapper receiptPaymentMapper;
    private final FileUtil fileUtil;
    /**
     * å¯¼å…¥é”€å”®å°è´¦
@@ -149,13 +154,13 @@
    @PostMapping("/export")
    public void export(HttpServletResponse response, SalesLedgerDto salesLedgerDto) {
        Page page = new Page(-1,-1);
        IPage<SalesLedger> salesLedgerIPage = listPage(page, salesLedgerDto);
        ExcelUtil<SalesLedger> util = new ExcelUtil<SalesLedger>(SalesLedger.class);
        IPage<SalesLedgerVo> salesLedgerIPage = listPage(page, salesLedgerDto);
        ExcelUtil<SalesLedgerVo> util = new ExcelUtil<SalesLedgerVo>(SalesLedgerVo.class);
        if(salesLedgerIPage == null){
            util.exportExcel(response, new ArrayList<>(), "销售台账数据");
            return;
        }
        List<SalesLedger> list = salesLedgerIPage.getRecords();
        List<SalesLedgerVo> list = salesLedgerIPage.getRecords();
        util.exportExcel(response, list, "销售台账数据");
    }
@@ -168,8 +173,8 @@
        Page page = new Page();
        page.setCurrent(-1);
        page.setSize(-1);
        IPage<SalesLedger> salesLedgerIPage = listPage(page, salesLedgerDto);
        ExcelUtil<SalesLedger> util = new ExcelUtil<SalesLedger>(SalesLedger.class);
        IPage<SalesLedgerVo> salesLedgerIPage = listPage(page, salesLedgerDto);
        ExcelUtil<SalesLedgerVo> util = new ExcelUtil<SalesLedgerVo>(SalesLedgerVo.class);
        util.exportExcel(response, salesLedgerIPage == null ? new ArrayList<>() : salesLedgerIPage.getRecords(), "导出开票登记列表");
    }
@@ -251,8 +256,8 @@
     * æŸ¥è¯¢é”€å”®å°è´¦åˆ—表
     */
    @GetMapping("/listPage")
    public IPage<SalesLedger> listPage(Page page, SalesLedgerDto salesLedgerDto) {
        IPage<SalesLedger> iPage = salesLedgerService.selectSalesLedgerListPage(page, salesLedgerDto);
    public IPage<SalesLedgerVo> listPage(Page page, SalesLedgerDto salesLedgerDto) {
        IPage<SalesLedgerVo> iPage = salesLedgerService.selectSalesLedgerListPage(page, salesLedgerDto);
        //  æŸ¥è¯¢ç»“果为空,直接返回
        if (CollectionUtils.isEmpty(iPage.getRecords())) {
@@ -295,10 +300,10 @@
            }
        }
        for (SalesLedger salesLedger : iPage.getRecords()) {
            Long ledgerId = salesLedger.getId();
        for (SalesLedgerVo salesLedgerVo : iPage.getRecords()) {
            Long ledgerId = salesLedgerVo.getId();
            // åˆåŒæ€»é‡‘额
            BigDecimal contractAmount = salesLedger.getContractAmount() == null ? BigDecimal.ZERO : salesLedger.getContractAmount();
            BigDecimal contractAmount = salesLedgerVo.getContractAmount() == null ? BigDecimal.ZERO : salesLedgerVo.getContractAmount();
            // å¼€ç¥¨æ€»é¢å’Œå›žæ¬¾æ€»é¢
            BigDecimal invoiceTotal = invoiceTotals.getOrDefault(ledgerId, BigDecimal.ZERO);
            BigDecimal receiptPaymentAmountTotal = receiptTotals.getOrDefault(ledgerId, BigDecimal.ZERO);
@@ -315,15 +320,17 @@
                noReceiptPaymentAmountTotal = BigDecimal.ZERO;
            }
            salesLedger.setNoInvoiceAmountTotal(noInvoiceAmountTotal);
            salesLedger.setInvoiceTotal(invoiceTotal);
            salesLedger.setReceiptPaymentAmountTotal(receiptPaymentAmountTotal);
            salesLedger.setNoReceiptAmount(noReceiptPaymentAmountTotal);
            salesLedgerVo.setNoInvoiceAmountTotal(noInvoiceAmountTotal);
            salesLedgerVo.setInvoiceTotal(invoiceTotal);
            salesLedgerVo.setReceiptPaymentAmountTotal(receiptPaymentAmountTotal);
            salesLedgerVo.setNoReceiptAmount(noReceiptPaymentAmountTotal);
            //  å¦‚果已经有过开票或回款操作,则不允许编辑
            boolean hasInvoiceOperation = invoiceTotal.compareTo(BigDecimal.ZERO) > 0;
            boolean hasReceiptOperation = receiptPaymentAmountTotal.compareTo(BigDecimal.ZERO) > 0;
            salesLedger.setIsEdit(!(hasInvoiceOperation || hasReceiptOperation));
            salesLedgerVo.setIsEdit(!(hasInvoiceOperation || hasReceiptOperation));
            salesLedgerVo.setStorageBlobVOs(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.FILE, RecordTypeEnum.SALES_LEDGER, ledgerId));
        }
        if (ObjectUtils.isNotEmpty(salesLedgerDto.getStatus())) {
src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -58,4 +59,6 @@
    @Schema(description = "交货日期")
    private LocalDate deliveryDate;
    private List<StorageBlobDTO> storageBlobDTOs;
}
src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
@@ -10,6 +10,7 @@
import com.ruoyi.sales.dto.SalesTrendDto;
import com.ruoyi.sales.dto.StatisticsTableDto;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.vo.SalesLedgerVo;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@@ -52,7 +53,7 @@
     * @param salesLedgerDto
     * @return
     */
    IPage<SalesLedger> selectSalesLedgerListPage(Page page, @Param("salesLedgerDto") SalesLedgerDto salesLedgerDto);
    IPage<SalesLedgerVo> selectSalesLedgerListPage(Page page, @Param("salesLedgerDto") SalesLedgerDto salesLedgerDto);
    /**
     * æŒ‰æœˆä»½ç»Ÿè®¡è®¢å•数、销售额(支持产品大类、客户名称筛选)
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
@@ -11,6 +11,7 @@
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.vo.SalesLedgerVo;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Nullable;
@@ -47,7 +48,7 @@
    List<MonthlyAmountDto> getAmountHalfYear(Integer type);
    IPage<SalesLedger> selectSalesLedgerListPage(Page page, SalesLedgerDto salesLedgerDto);
    IPage<SalesLedgerVo> selectSalesLedgerListPage(Page page, SalesLedgerDto salesLedgerDto);
    AjaxResult importData(MultipartFile file);
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -10,10 +10,13 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.service.AccountIncomeService;
import com.ruoyi.basic.dto.CustomerPrivatePoolDto;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.CustomerPrivatePoolMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.SaleEnum;
import com.ruoyi.common.exception.base.BaseException;
@@ -41,6 +44,7 @@
import com.ruoyi.sales.mapper.*;
import com.ruoyi.sales.pojo.*;
import com.ruoyi.sales.service.ISalesLedgerService;
import com.ruoyi.sales.vo.SalesLedgerVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
@@ -111,6 +115,7 @@
    private final PurchaseReturnOrderProductsMapper purchaseReturnOrderProductsMapper;
    private final SysUserMapper sysUserMapper;
    private final CustomerPrivatePoolMapper customerPrivatePoolMapper;
    private final FileUtil fileUtil;
    @Override
    public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) {
@@ -317,7 +322,7 @@
    }
    @Override
    public IPage<SalesLedger> selectSalesLedgerListPage(Page page, SalesLedgerDto salesLedgerDto) {
    public IPage<SalesLedgerVo> selectSalesLedgerListPage(Page page, SalesLedgerDto salesLedgerDto) {
        return salesLedgerMapper.selectSalesLedgerListPage(page, salesLedgerDto);
    }
@@ -575,47 +580,43 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto) {
        try {
            // 1. æ ¡éªŒå®¢æˆ·ä¿¡æ¯
            CustomerPrivatePoolDto customer = customerPrivatePoolMapper.selectInfo(salesLedgerDto.getCustomerId());
            if (customer == null) {
                throw new BaseException("客户不存在");
            }
            // 2. DTO转Entity
            SalesLedger salesLedger = convertToEntity(salesLedgerDto);
            salesLedger.setCustomerName(customer.getCustomerName());
            salesLedger.setTenantId(customer.getTenantId());
            // 3. æ–°å¢žæˆ–更新主表
            if (salesLedger.getId() == null) {
                String contractNo = generateSalesContractNo();
                salesLedger.setSalesContractNo(contractNo);
                salesLedgerMapper.insert(salesLedger);
            } else {
                salesLedgerMapper.updateById(salesLedger);
            }
            // 4. å¤„理子表数据
            List<SalesLedgerProduct> productList = salesLedgerDto.getProductData();
            if (productList != null && !productList.isEmpty()) {
                handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType()));
                updateMainContractAmount(
                        salesLedger.getId(),
                        productList,
                        SalesLedgerProduct::getTaxInclusiveTotalPrice,
                        salesLedgerMapper,
                        SalesLedger.class
                );
            }
            // 5. è¿ç§»ä¸´æ—¶æ–‡ä»¶åˆ°æ­£å¼ç›®å½•
            if (salesLedgerDto.getTempFileIds() != null && !salesLedgerDto.getTempFileIds().isEmpty()) {
                migrateTempFilesToFormal(salesLedger.getId(), salesLedgerDto.getTempFileIds());
            }
            return 1;
        } catch (IOException e) {
            throw new BaseException("文件迁移失败: " + e.getMessage());
        // 1. æ ¡éªŒå®¢æˆ·ä¿¡æ¯
        CustomerPrivatePoolDto customer = customerPrivatePoolMapper.selectInfo(salesLedgerDto.getCustomerId());
        if (customer == null) {
            throw new BaseException("客户不存在");
        }
        // 2. DTO转Entity
        SalesLedger salesLedger = convertToEntity(salesLedgerDto);
        salesLedger.setCustomerName(customer.getCustomerName());
        salesLedger.setTenantId(customer.getTenantId());
        // 3. æ–°å¢žæˆ–更新主表
        if (salesLedger.getId() == null) {
            String contractNo = generateSalesContractNo();
            salesLedger.setSalesContractNo(contractNo);
            salesLedgerMapper.insert(salesLedger);
        } else {
            salesLedgerMapper.updateById(salesLedger);
        }
        // 4. å¤„理子表数据
        List<SalesLedgerProduct> productList = salesLedgerDto.getProductData();
        if (productList != null && !productList.isEmpty()) {
            handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType()));
            updateMainContractAmount(
                    salesLedger.getId(),
                    productList,
                    SalesLedgerProduct::getTaxInclusiveTotalPrice,
                    salesLedgerMapper,
                    SalesLedger.class
            );
        }
        // 5. ä¿å­˜æ–‡ä»¶
        if (salesLedgerDto.getStorageBlobDTOs() != null && !salesLedgerDto.getStorageBlobDTOs().isEmpty()) {
            fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.SALES_LEDGER, salesLedger.getId(), salesLedgerDto.getStorageBlobDTOs());
        }
        return 1;
    }
    /**
src/main/java/com/ruoyi/sales/vo/SalesLedgerVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.sales.vo;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.sales.pojo.SalesLedger;
import lombok.Data;
import java.util.List;
@Data
public class SalesLedgerVo extends SalesLedger {
    private List<StorageBlobVO> storageBlobVOs;
}
src/main/java/com/ruoyi/stock/controller/StockInRecordController.java
@@ -43,10 +43,31 @@
        return AjaxResult.success(stockInRecordService.batchDelete(ids));
    }
    @DeleteMapping("/pending")
    @Log(title = "入库管理-删除待审批入库", businessType = BusinessType.DELETE)
    @Operation(summary = "删除待审批的入库记录")
    public AjaxResult deletePending(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return AjaxResult.error("请选择至少一条数据");
        }
        return AjaxResult.success(stockInRecordService.batchDeletePending(ids));
    }
    @PostMapping("/exportStockInRecord")
    @Operation(summary = "导出入库记录")
    public void exportStockInRecord(HttpServletResponse response, StockInRecordDto stockInRecordDto) {
        stockInRecordService.exportStockInRecord(response,stockInRecordDto);
    }
    @PostMapping("/approve")
    @Log(title = "入库管理-审批入库", businessType = BusinessType.UPDATE)
    @Operation(summary = "批量审批入库记录")
    public AjaxResult approve(@RequestBody StockInRecordDto approveDto) {
        if(CollectionUtils.isEmpty(approveDto.getIds())){
            return AjaxResult.error("请选择至少一条数据");
        }
        stockInRecordService.batchApprove(approveDto.getIds(), approveDto.getApprovalStatus());
        return AjaxResult.success();
    }
}
src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
@@ -68,6 +68,22 @@
        return R.ok(stockInventoryService.subtractStockInventory(stockInventoryDto));
    }
    @PostMapping("/addStockInRecordOnly")
    @Operation(summary = "新增入库记录(仅创建记录,不调整库存)")
    public R addStockInRecordOnly(@RequestBody StockInventoryDto stockInventoryDto) {
        stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode()));
        stockInventoryDto.setRecordId(0L);
        return R.ok(stockInventoryService.addStockInRecordOnly(stockInventoryDto));
    }
    @PostMapping("/addStockOutRecordOnly")
    @Operation(summary = "新增出库记录(仅创建记录,不调整库存)")
    public R addStockOutRecordOnly(@RequestBody StockInventoryDto stockInventoryDto) {
        stockInventoryDto.setRecordType(String.valueOf(StockOutQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_OUT.getCode()));
        stockInventoryDto.setRecordId(0L);
        return R.ok(stockInventoryService.addStockOutRecordOnly(stockInventoryDto));
    }
    @PostMapping("importStockInventory")
    @Operation(summary = "导入库存")
src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java
@@ -10,7 +10,6 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
@@ -61,10 +60,31 @@
        return AjaxResult.success(stockOutRecordService.batchDelete(ids));
    }
    @DeleteMapping("/pending")
    @Log(title = "出库管理-删除待审批出库", businessType = BusinessType.DELETE)
    @Operation(summary = "删除待审批的出库记录")
    public AjaxResult deletePending(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return AjaxResult.error("请选择至少一条数据");
        }
        return AjaxResult.success(stockOutRecordService.batchDeletePending(ids));
    }
    @PostMapping("/exportStockOutRecord")
    @Operation(summary = "导出出库记录")
    public void exportStockOutRecord(HttpServletResponse response, StockOutRecordDto stockOutRecordDto) {
        stockOutRecordService.exportStockOutRecord(response,stockOutRecordDto);
    }
    @PostMapping("/approve")
    @Log(title = "出库管理-审批出库", businessType = BusinessType.UPDATE)
    @Operation(summary = "批量审批出库记录")
    public AjaxResult approve(@RequestBody StockOutRecordDto approveDto) {
        if(CollectionUtils.isEmpty(approveDto.getIds())){
            return AjaxResult.error("请选择至少一条数据");
        }
        stockOutRecordService.batchApprove(approveDto.getIds(), approveDto.getApprovalStatus());
        return AjaxResult.success();
    }
}
src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java
@@ -51,6 +51,22 @@
        return R.ok(stockUninventoryService.subtractStockUninventory(stockUninventoryDto));
    }
    @PostMapping("/addStockInRecordOnly")
    @Operation(summary = "新增入库记录(仅创建记录,不调整库存)")
    public R addStockInRecordOnly(@RequestBody StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setRecordType(String.valueOf(StockInUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode()));
        stockUninventoryDto.setRecordId(0L);
        return R.ok(stockUninventoryService.addStockInRecordOnly(stockUninventoryDto));
    }
    @PostMapping("/addStockOutRecordOnly")
    @Operation(summary = "新增出库记录(仅创建记录,不调整库存)")
    public R addStockOutRecordOnly(@RequestBody StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setRecordType(String.valueOf(StockOutUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()));
        stockUninventoryDto.setRecordId(0L);
        return R.ok(stockUninventoryService.addStockOutRecordOnly(stockUninventoryDto));
    }
    @PostMapping("/exportStockUninventory")
    @Operation(summary = "导出库存")
    public void exportStockUninventory(HttpServletResponse response, StockUninventoryDto stockUninventoryDto) {
src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java
@@ -4,6 +4,8 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
public class StockInRecordDto extends StockInRecord {
@@ -30,4 +32,7 @@
    @Schema(description = "顶部父产品id")
    private Long topParentProductId;
    @Schema(description = "记录ID列表")
    private List<Long> ids;
}
src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
@@ -43,31 +43,37 @@
    @Schema(description = "顶部父产品id")
    private Long topParentProductId;
    @Schema(description = "库存类型:qualified(合格)、unqualified(不合格)")
    private String stockType;
    @Schema(description = "合格库存数量")
    private BigDecimal qualifiedQuantity;
    @Schema(description = "不合格库存数量")
    private BigDecimal unQualifiedQuantity;
    @Schema(description = "合格库存冻结数量")
    private BigDecimal qualifiedLockedQuantity;
    @Schema(description = "不合格库存冻结数量")
    private BigDecimal unQualifiedLockedQuantity;
    @Schema(description = "合格库存未冻结数量")
    private BigDecimal qualifiedUnLockedQuantity;
    @Schema(description = "不合格库存未冻结数量")
    private BigDecimal unQualifiedUnLockedQuantity;
    @Schema(description = "合格库存待审核出库数量(不占用库存但已申请出库)")
    private BigDecimal qualifiedPendingOutQuantity;
    @Schema(description = "不合格库存待审核出库数量")
    private BigDecimal unQualifiedPendingOutQuantity;
    @Schema(description = "合格库存ID")
    private Long qualifiedId;
    @Schema(description = "不合格库存ID")
    private Long unQualifiedId;
}
}
src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java
@@ -6,6 +6,8 @@
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@@ -29,4 +31,7 @@
    @Schema(description = "顶部父产品id")
    private Long topParentProductId;
    @Schema(description = "记录ID列表")
    private List<Long> ids;
}
src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
@@ -50,4 +50,6 @@
    List<Map<String, Object>> selectDailyStockOutCounts(@Param("rootCategoryId") Long rootCategoryId, @Param("startDate") String startDate, @Param("endDate") String endDate);
    BigDecimal selectTotalByDate(@Param("now") LocalDate now);
    BigDecimal selectPendingOutQuantity(@Param("productModelId") Long productModelId, @Param("batchNo") String batchNo, @Param("type") String type);
}
src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java
@@ -9,6 +9,7 @@
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -26,6 +27,8 @@
    int updateSubtractStockUnInventory(@Param("ew") StockUninventoryDto stockUninventoryDto);
    BigDecimal selectPendingOutQuantity(@Param("productModelId") Long productModelId, @Param("batchNo") String batchNo, @Param("type") String type);
    int updateAddStockUnInventory(@Param("ew") StockUninventoryDto stockUninventoryDto);
    List<StockUnInventoryExportData> listStockInventoryExportData(@Param("ew") StockUninventoryDto stockUninventoryDto);
src/main/java/com/ruoyi/stock/pojo/StockInRecord.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.enums.ReviewStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -65,6 +66,9 @@
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @Schema(description = "审批状态  0-待审批 1-通过 2-驳回", implementation = ReviewStatusEnum.class)
    private Integer approvalStatus;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.enums.ReviewStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@@ -74,6 +75,9 @@
    @Schema(description = "类型  0合格入库 1不合格入库")
    private String type;
    @Schema(description = "审批状态  0-待审批 1-通过 2-驳回", implementation = ReviewStatusEnum.class)
    private Integer approvalStatus;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/stock/service/StockInRecordService.java
@@ -18,5 +18,9 @@
    int batchDelete(List<Long> ids);
    int batchDeletePending(List<Long> ids);
    void exportStockInRecord(HttpServletResponse response, StockInRecordDto stockInRecordDto);
    int batchApprove(List<Long> ids, Integer approvalStatus);
}
src/main/java/com/ruoyi/stock/service/StockInventoryService.java
@@ -29,6 +29,10 @@
    Boolean subtractStockInventory(StockInventoryDto stockInventoryDto);
    Boolean addStockInRecordOnly(StockInventoryDto stockInventoryDto);
    Boolean addStockOutRecordOnly(StockInventoryDto stockInventoryDto);
    R importStockInventory(MultipartFile file);
    void exportStockInventory(HttpServletResponse response, StockInventoryDto stockInventoryDto);
src/main/java/com/ruoyi/stock/service/StockOutRecordService.java
@@ -26,5 +26,9 @@
    int batchDelete(List<Long> ids);
    int batchDeletePending(List<Long> ids);
    void exportStockOutRecord(HttpServletResponse response, StockOutRecordDto stockOutRecordDto);
    int batchApprove(List<Long> ids, Integer approvalStatus);
}
src/main/java/com/ruoyi/stock/service/StockUninventoryService.java
@@ -25,6 +25,10 @@
    Integer subtractStockUninventory(StockUninventoryDto stockUninventoryDto);
    Integer addStockInRecordOnly(StockUninventoryDto stockUninventoryDto);
    Integer addStockOutRecordOnly(StockUninventoryDto stockUninventoryDto);
    void exportStockUninventory(HttpServletResponse response, StockUninventoryDto stockUninventoryDto);
    Boolean frozenStock(StockInventoryDto stockInventoryDto);
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.ReviewStatusEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.base.BaseException;
@@ -26,6 +27,7 @@
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
@@ -127,4 +129,106 @@
        ExcelUtil<StockInRecordExportData> util = new ExcelUtil<>(StockInRecordExportData.class);
        util.exportExcel(response,list, "入库记录信息");
    }
    private StockInventory getStockInventory(Long productModelId, String batchNo) {
        LambdaQueryWrapper<StockInventory> eq = new LambdaQueryWrapper<>();
        eq.eq(StockInventory::getProductModelId, productModelId);
        if (StringUtils.isEmpty(batchNo)) {
            eq.isNull(StockInventory::getBatchNo);
        } else {
            eq.eq(StockInventory::getBatchNo, batchNo);
        }
        return stockInventoryMapper.selectOne(eq);
    }
    private StockUninventory getStockUninventory(Long productModelId, String batchNo) {
        LambdaQueryWrapper<StockUninventory> eq = new LambdaQueryWrapper<>();
        eq.eq(StockUninventory::getProductModelId, productModelId);
        if (StringUtils.isEmpty(batchNo)) {
            eq.isNull(StockUninventory::getBatchNo);
        } else {
            eq.eq(StockUninventory::getBatchNo, batchNo);
        }
        return stockUninventoryMapper.selectOne(eq);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchDeletePending(List<Long> ids) {
        for (Long id : ids) {
            StockInRecord stockInRecord = stockInRecordMapper.selectById(id);
            if (stockInRecord == null) {
                throw new BaseException("入库记录不存在,无法删除!!!");
            }
            if (stockInRecord.getApprovalStatus() != null && !ReviewStatusEnum.PENDING_REVIEW.getCode().equals(stockInRecord.getApprovalStatus())) {
                throw new BaseException("只有待审批状态的记录才能删除,入库批次:" + stockInRecord.getInboundBatches());
            }
        }
        return stockInRecordMapper.deleteBatchIds(ids);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchApprove(List<Long> ids, Integer approvalStatus) {
        if (CollectionUtils.isEmpty(ids)) {
            throw new BaseException("请选择至少一条数据");
        }
        if (approvalStatus == null || (!ReviewStatusEnum.APPROVED.getCode().equals(approvalStatus) && !ReviewStatusEnum.REJECTED.getCode().equals(approvalStatus))) {
            throw new BaseException("审批状态值无效");
        }
        for (Long id : ids) {
            StockInRecord stockInRecord = stockInRecordMapper.selectById(id);
            if (stockInRecord == null) {
                throw new BaseException("入库记录不存在,无法审批!!!");
            }
            if (stockInRecord.getApprovalStatus() != null && !ReviewStatusEnum.PENDING_REVIEW.getCode().equals(stockInRecord.getApprovalStatus())) {
                throw new BaseException("只有待审批状态的记录才能审批,入库批次:" + stockInRecord.getInboundBatches());
            }
            stockInRecord.setApprovalStatus(approvalStatus);
            stockInRecordMapper.updateById(stockInRecord);
            // å®¡æ‰¹é€šè¿‡æ—¶ï¼Œåº“存增加
            if (ReviewStatusEnum.APPROVED.getCode().equals(approvalStatus)) {
                if ("0".equals(stockInRecord.getType())) {
                    // åˆæ ¼å…¥åº“ -> å…ˆæŸ¥åº“存,存在则更新,不存在则新增
                    StockInventory stockInventory = getStockInventory(stockInRecord.getProductModelId(), stockInRecord.getBatchNo());
                    StockInventoryDto stockInventoryDto = new StockInventoryDto();
                    stockInventoryDto.setProductModelId(stockInRecord.getProductModelId());
                    stockInventoryDto.setBatchNo(stockInRecord.getBatchNo());
                    stockInventoryDto.setQualitity(stockInRecord.getStockInNum());
                    stockInventoryDto.setRemark(stockInRecord.getRemark());
                    if (stockInventory == null) {
                        stockInventoryMapper.insert(new StockInventory() {{
                            setProductModelId(stockInRecord.getProductModelId());
                            setQualitity(stockInRecord.getStockInNum());
                            setBatchNo(stockInRecord.getBatchNo());
                            setRemark(stockInRecord.getRemark());
                            setVersion(1);
                        }});
                    } else {
                        stockInventoryMapper.updateAddStockInventory(stockInventoryDto);
                    }
                } else if ("1".equals(stockInRecord.getType())) {
                    // ä¸åˆæ ¼å…¥åº“ -> å…ˆæŸ¥åº“存,存在则更新,不存在则新增
                    StockUninventory stockUninventory = getStockUninventory(stockInRecord.getProductModelId(), stockInRecord.getBatchNo());
                    StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
                    stockUninventoryDto.setProductModelId(stockInRecord.getProductModelId());
                    stockUninventoryDto.setBatchNo(stockInRecord.getBatchNo());
                    stockUninventoryDto.setQualitity(stockInRecord.getStockInNum());
                    stockUninventoryDto.setRemark(stockInRecord.getRemark());
                    if (stockUninventory == null) {
                        stockUninventoryMapper.insert(new StockUninventory() {{
                            setProductModelId(stockInRecord.getProductModelId());
                            setQualitity(stockInRecord.getStockInNum());
                            setBatchNo(stockInRecord.getBatchNo());
                            setRemark(stockInRecord.getRemark());
                            setVersion(1);
                        }});
                    } else {
                        stockUninventoryMapper.updateAddStockUnInventory(stockUninventoryDto);
                    }
                }
            }
        }
        return ids.size();
    }
}
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -8,7 +8,8 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.R;
@@ -145,6 +146,63 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addStockInRecordOnly(StockInventoryDto stockInventoryDto) {
        StockInRecordDto stockInRecordDto = new StockInRecordDto();
        stockInRecordDto.setRecordId(stockInventoryDto.getRecordId());
        stockInRecordDto.setRecordType(stockInventoryDto.getRecordType());
        stockInRecordDto.setStockInNum(stockInventoryDto.getQualitity());
        stockInRecordDto.setBatchNo(stockInventoryDto.getBatchNo());
        stockInRecordDto.setProductModelId(stockInventoryDto.getProductModelId());
        stockInRecordDto.setType("0");
        stockInRecordDto.setRemark(stockInventoryDto.getRemark());
        stockInRecordService.add(stockInRecordDto);
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addStockOutRecordOnly(StockInventoryDto stockInventoryDto) {
        LambdaQueryWrapper<StockInventory> eq = new LambdaQueryWrapper<>();
        eq.eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId());
        if (StringUtils.isEmpty(stockInventoryDto.getBatchNo())) {
            eq.isNull(StockInventory::getBatchNo);
        } else {
            eq.eq(StockInventory::getBatchNo, stockInventoryDto.getBatchNo());
        }
        StockInventory stockInventory = stockInventoryMapper.selectOne(eq);
        if (stockInventory == null) {
            throw new ServiceException("库存记录不存在");
        }
        BigDecimal lockedQty = stockInventory.getLockedQuantity();
        if (lockedQty == null) {
            lockedQty = BigDecimal.ZERO;
        }
        BigDecimal pendingOut = stockInventoryMapper.selectPendingOutQuantity(
                stockInventoryDto.getProductModelId(),
                stockInventoryDto.getBatchNo(),
                "0"
        );
        if (pendingOut == null) {
            pendingOut = BigDecimal.ZERO;
        }
        BigDecimal availableQty = stockInventory.getQualitity().subtract(lockedQty).subtract(pendingOut);
        if (stockInventoryDto.getQualitity().compareTo(availableQty) > 0) {
            throw new ServiceException("申请数量超过可用库存,当前可用库存为:" + availableQty);
        }
        StockOutRecordDto stockOutRecordDto = new StockOutRecordDto();
        stockOutRecordDto.setRecordId(stockInventoryDto.getRecordId());
        stockOutRecordDto.setRecordType(stockInventoryDto.getRecordType());
        stockOutRecordDto.setStockOutNum(stockInventoryDto.getQualitity());
        stockOutRecordDto.setBatchNo(stockInventoryDto.getBatchNo());
        stockOutRecordDto.setProductModelId(stockInventoryDto.getProductModelId());
        stockOutRecordDto.setType("0");
        stockOutRecordDto.setRemark(stockInventoryDto.getRemark());
        stockOutRecordService.add(stockOutRecordDto);
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R importStockInventory(MultipartFile file) {
        try {
            // æŸ¥è¯¢æ‰€æœ‰çš„产品并构建映射,提高查找效率
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.ReviewStatusEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.exception.base.BaseException;
@@ -26,6 +27,8 @@
import com.ruoyi.stock.service.StockOutRecordService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
@@ -130,4 +133,90 @@
        ExcelUtil<StockOutRecordExportData> util = new ExcelUtil<>(StockOutRecordExportData.class);
        util.exportExcel(response,list, "出库记录信息");
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchDeletePending(List<Long> ids) {
        for (Long id : ids) {
            StockOutRecord stockOutRecord = stockOutRecordMapper.selectById(id);
            if (stockOutRecord == null) {
                throw new BaseException("出库记录不存在,无法删除!!!");
            }
            if (stockOutRecord.getApprovalStatus() != null && !ReviewStatusEnum.PENDING_REVIEW.getCode().equals(stockOutRecord.getApprovalStatus())) {
                throw new BaseException("只有待审批状态的记录才能删除,出库批次:" + stockOutRecord.getOutboundBatches());
            }
        }
        return stockOutRecordMapper.deleteBatchIds(ids);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchApprove(List<Long> ids, Integer approvalStatus) {
        if (CollectionUtils.isEmpty(ids)) {
            throw new BaseException("请选择至少一条数据");
        }
        if (approvalStatus == null || (!ReviewStatusEnum.APPROVED.getCode().equals(approvalStatus) && !ReviewStatusEnum.REJECTED.getCode().equals(approvalStatus))) {
            throw new BaseException("审批状态值无效");
        }
        for (Long id : ids) {
            StockOutRecord stockOutRecord = stockOutRecordMapper.selectById(id);
            if (stockOutRecord == null) {
                throw new BaseException("出库记录不存在,无法审批!!!");
            }
            if (stockOutRecord.getApprovalStatus() != null && !ReviewStatusEnum.PENDING_REVIEW.getCode().equals(stockOutRecord.getApprovalStatus())) {
                throw new BaseException("只有待审批状态的记录才能审批,出库批次:" + stockOutRecord.getOutboundBatches());
            }
            stockOutRecord.setApprovalStatus(approvalStatus);
            stockOutRecordMapper.updateById(stockOutRecord);
            // å®¡æ‰¹é€šè¿‡æ—¶ï¼Œæ‰£å‡åº“å­˜
            if (ReviewStatusEnum.APPROVED.getCode().equals(approvalStatus)) {
                if ("0".equals(stockOutRecord.getType())) {
                    // åˆæ ¼å‡ºåº“ -> å…ˆæŸ¥åº“存是否存在,存在才扣减
                    StockInventory stockInventory = getStockInventory(stockOutRecord.getProductModelId(), stockOutRecord.getBatchNo());
                    if (stockInventory == null) {
                        throw new BaseException("合格库存记录不存在,出库批次:" + stockOutRecord.getOutboundBatches());
                    }
                    StockInventoryDto stockInventoryDto = new StockInventoryDto();
                    stockInventoryDto.setProductModelId(stockOutRecord.getProductModelId());
                    stockInventoryDto.setBatchNo(stockOutRecord.getBatchNo());
                    stockInventoryDto.setQualitity(stockOutRecord.getStockOutNum());
                    stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto);
                } else if ("1".equals(stockOutRecord.getType())) {
                    // ä¸åˆæ ¼å‡ºåº“ -> å…ˆæŸ¥åº“存是否存在,存在才扣减
                    StockUninventory stockUninventory = getStockUninventory(stockOutRecord.getProductModelId(), stockOutRecord.getBatchNo());
                    if (stockUninventory == null) {
                        throw new BaseException("不合格库存记录不存在,出库批次:" + stockOutRecord.getOutboundBatches());
                    }
                    StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
                    stockUninventoryDto.setProductModelId(stockOutRecord.getProductModelId());
                    stockUninventoryDto.setBatchNo(stockOutRecord.getBatchNo());
                    stockUninventoryDto.setQualitity(stockOutRecord.getStockOutNum());
                    stockUninventoryMapper.updateSubtractStockUnInventory(stockUninventoryDto);
                }
            }
        }
        return ids.size();
    }
    private StockInventory getStockInventory(Long productModelId, String batchNo) {
        LambdaQueryWrapper<StockInventory> eq = new LambdaQueryWrapper<>();
        eq.eq(StockInventory::getProductModelId, productModelId);
        if (StringUtils.isEmpty(batchNo)) {
            eq.isNull(StockInventory::getBatchNo);
        } else {
            eq.eq(StockInventory::getBatchNo, batchNo);
        }
        return stockInventoryMapper.selectOne(eq);
    }
    private StockUninventory getStockUninventory(Long productModelId, String batchNo) {
        LambdaQueryWrapper<StockUninventory> eq = new LambdaQueryWrapper<>();
        eq.eq(StockUninventory::getProductModelId, productModelId);
        if (StringUtils.isEmpty(batchNo)) {
            eq.isNull(StockUninventory::getBatchNo);
        } else {
            eq.eq(StockUninventory::getBatchNo, batchNo);
        }
        return stockUninventoryMapper.selectOne(eq);
    }
}
src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
@@ -6,6 +6,7 @@
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.stock.dto.StockInRecordDto;
@@ -24,6 +25,8 @@
import org.springframework.transaction.annotation.Transactional;
import jakarta.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -95,6 +98,7 @@
        stockOutRecordDto.setRecordType(stockUninventoryDto.getRecordType());
        stockOutRecordDto.setStockOutNum(stockUninventoryDto.getQualitity());
        stockOutRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
        stockOutRecordDto.setBatchNo(stockUninventoryDto.getBatchNo());
        stockOutRecordDto.setType("1");
        stockOutRecordService.add(stockOutRecordDto);
        StockUninventory oldStockInventory = stockUninventoryMapper.selectOne(new QueryWrapper<StockUninventory>().lambda().eq(StockUninventory::getProductModelId, stockUninventoryDto.getProductModelId()));
@@ -107,6 +111,63 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addStockInRecordOnly(StockUninventoryDto stockUninventoryDto) {
        StockInRecordDto stockInRecordDto = new StockInRecordDto();
        stockInRecordDto.setRecordId(stockUninventoryDto.getRecordId());
        stockInRecordDto.setRecordType(stockUninventoryDto.getRecordType());
        stockInRecordDto.setStockInNum(stockUninventoryDto.getQualitity());
        stockInRecordDto.setBatchNo(stockUninventoryDto.getBatchNo());
        stockInRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
        stockInRecordDto.setType("1");
        stockInRecordDto.setRemark(stockUninventoryDto.getRemark());
        stockInRecordService.add(stockInRecordDto);
        return 1;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addStockOutRecordOnly(StockUninventoryDto stockUninventoryDto) {
        LambdaQueryWrapper<StockUninventory> eq = new LambdaQueryWrapper<>();
        eq.eq(StockUninventory::getProductModelId, stockUninventoryDto.getProductModelId());
        if (StringUtils.isEmpty(stockUninventoryDto.getBatchNo())) {
            eq.isNull(StockUninventory::getBatchNo);
        } else {
            eq.eq(StockUninventory::getBatchNo, stockUninventoryDto.getBatchNo());
        }
        StockUninventory stockUninventory = stockUninventoryMapper.selectOne(eq);
        if (stockUninventory == null) {
            throw new BaseException("库存记录不存在");
        }
        BigDecimal lockedQty = stockUninventory.getLockedQuantity();
        if (lockedQty == null) {
            lockedQty = BigDecimal.ZERO;
        }
        BigDecimal pendingOut = stockUninventoryMapper.selectPendingOutQuantity(
                stockUninventoryDto.getProductModelId(),
                stockUninventoryDto.getBatchNo(),
                "1"
        );
        if (pendingOut == null) {
            pendingOut = BigDecimal.ZERO;
        }
        BigDecimal availableQty = stockUninventory.getQualitity().subtract(lockedQty).subtract(pendingOut);
        if (stockUninventoryDto.getQualitity().compareTo(availableQty) > 0) {
            throw new BaseException("申请数量超过可用库存,当前可用库存为:" + availableQty);
        }
        StockOutRecordDto stockOutRecordDto = new StockOutRecordDto();
        stockOutRecordDto.setRecordId(stockUninventoryDto.getRecordId());
        stockOutRecordDto.setRecordType(stockUninventoryDto.getRecordType());
        stockOutRecordDto.setStockOutNum(stockUninventoryDto.getQualitity());
        stockOutRecordDto.setBatchNo(stockUninventoryDto.getBatchNo());
        stockOutRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
        stockOutRecordDto.setType("1");
        stockOutRecordDto.setRemark(stockUninventoryDto.getRemark());
        stockOutRecordService.add(stockOutRecordDto);
        return 1;
    }
    @Override
    public void exportStockUninventory(HttpServletResponse response, StockUninventoryDto stockUninventoryDto) {
        List<StockUnInventoryExportData> list = stockUninventoryMapper.listStockInventoryExportData(stockUninventoryDto);
        ExcelUtil<StockUnInventoryExportData> util = new ExcelUtil<>(StockUnInventoryExportData.class);
src/main/resources/application-dev-pro.yml
@@ -53,14 +53,6 @@
    com.ruoyi: warn
    org.springframework: warn
minio:
  endpoint: http://114.132.189.42/
  port: 7019
  secure: false
  accessKey: admin
  secretKey: 12345678
  preview-expiry: 24 # é¢„览地址默认24小时
  default-bucket: uploadPath
# ç”¨æˆ·é…ç½®
user:
  password:
@@ -79,7 +71,7 @@
    druid:
      # ä¸»åº“数据源
      master:
        url: jdbc:mysql://localhost:3307/product-inventory-management-new-pro?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        url: jdbc:mysql://localhost:3306/product-inventory-management-new-pro?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: 123456
      # ä»Žåº“数据源
@@ -259,6 +251,7 @@
  # æ˜¯å¦å…è®¸ç”Ÿæˆæ–‡ä»¶è¦†ç›–到本地(自定义路径),默认不允许
  allowOverwrite: false
# æ–‡ä»¶ä¸Šä¼ é…ç½®
file:
  temp-dir: D:/ruoyi/temp/uploads   # ä¸´æ—¶ç›®å½•
  upload-dir: D:/ruoyi/prod/uploads # æ­£å¼ç›®å½•
@@ -269,4 +262,4 @@
  useLimit: 10 # ä½¿ç”¨æ¬¡æ•°
  compress: true # æ˜¯å¦åŽ‹ç¼©
  needCompressSize: 10MB # åŽ‹ç¼©é˜ˆå€¼
  compressQuality: 0.1 # åŽ‹ç¼©è´¨é‡(0.0-1.0)
  compressQuality: 0.5 # åŽ‹ç¼©è´¨é‡(0.0-1.0)
src/main/resources/mapper/sales/SalesLedgerMapper.xml
@@ -42,7 +42,7 @@
        </where>
    </select>
    <select id="selectSalesLedgerListPage" resultType="com.ruoyi.sales.pojo.SalesLedger">
    <select id="selectSalesLedgerListPage" resultType="com.ruoyi.sales.vo.SalesLedgerVo">
        SELECT T1.id,
        T1.sales_contract_no,
        T1.customer_contract_no,
src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -106,8 +106,10 @@
            SUM(unQualifiedQuantity) as unQualifiedQuantity,
            SUM(qualifiedLockedQuantity) as qualifiedLockedQuantity,
            SUM(unQualifiedLockedQuantity) as unQualifiedLockedQuantity,
            SUM(qualifiedQuantity - qualifiedLockedQuantity) as qualifiedUnLockedQuantity,
            SUM(unQualifiedQuantity - unQualifiedLockedQuantity) as unQualifiedUnLockedQuantity,
            SUM(qualifiedQuantity - qualifiedLockedQuantity - IFNULL(qualifiedPendingOut, 0)) as qualifiedUnLockedQuantity,
            SUM(unQualifiedQuantity - unQualifiedLockedQuantity - IFNULL(unQualifiedPendingOut, 0)) as unQualifiedUnLockedQuantity,
            SUM(IFNULL(qualifiedPendingOut, 0)) as qualifiedPendingOutQuantity,
            SUM(IFNULL(unQualifiedPendingOut, 0)) as unQualifiedPendingOutQuantity,
            product_model_id,
            MAX(create_time) as create_time,
            MAX(update_time) as update_time,
@@ -139,7 +141,16 @@
            si.remark,
            pm.unit,
            p.product_name,
            p.id as product_id
            p.id as product_id,
            (
                select IFNULL(SUM(sor.stock_out_num), 0)
                from stock_out_record sor
                where sor.product_model_id = si.product_model_id
                  and (si.batch_no is null and sor.batch_no is null or si.batch_no = sor.batch_no)
                  and sor.type = '0'
                  and sor.approval_status = 0
            ) as qualifiedPendingOut,
            0 as unqualifiedPendingOut
            from stock_inventory si
            left join product_model pm on si.product_model_id = pm.id
            left join product p on pm.product_id = p.id
@@ -165,7 +176,16 @@
            su.remark,
            pm.unit,
            p.product_name,
            p.id as product_id
            p.id as product_id,
            0 as qualifiedPendingOut,
            (
                select IFNULL(SUM(sor.stock_out_num), 0)
                from stock_out_record sor
                where sor.product_model_id = su.product_model_id
                  and (su.batch_no is null and sor.batch_no is null or su.batch_no = sor.batch_no)
                  and sor.type = '1'
                  and sor.approval_status = 0
            ) as unqualifiedPendingOut
            from stock_uninventory su
            left join product_model pm on su.product_model_id = pm.id
            left join product p on pm.product_id = p.id
@@ -410,4 +430,13 @@
        ORDER BY DATE(sor.create_time) ASC
    </select>
    <select id="selectPendingOutQuantity" resultType="java.math.BigDecimal">
        SELECT IFNULL(SUM(sor.stock_out_num), 0)
        FROM stock_out_record sor
        WHERE sor.product_model_id = #{productModelId}
          AND (sor.batch_no = #{batchNo} OR (#{batchNo} IS NULL AND sor.batch_no IS NULL))
          AND sor.type = #{type}
          AND sor.approval_status = 0
    </select>
</mapper>
src/main/resources/mapper/stock/StockUninventoryMapper.xml
@@ -95,4 +95,13 @@
        </if>
    </select>
    <select id="selectPendingOutQuantity" resultType="java.math.BigDecimal">
        SELECT IFNULL(SUM(sor.stock_out_num), 0)
        FROM stock_out_record sor
        WHERE sor.product_model_id = #{productModelId}
          AND (sor.batch_no = #{batchNo} OR (#{batchNo} IS NULL AND sor.batch_no IS NULL))
          AND sor.type = #{type}
          AND sor.approval_status = 0
    </select>
</mapper>