已修改21个文件
已删除1个文件
已添加9个文件
991 ■■■■ 文件已修改
src/main/java/com/ruoyi/RuoYiApplication.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/SupplierManageController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/excel/SupplierManageExcelDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/SupplierManage.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/ISupplierService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/SupplierServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/controller/TempFileController.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/mapper/TempFileMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/pojo/TempFile.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/service/TempFileService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/service/impl/TempFileServiceImpl.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/InvoiceRegistrationController.java 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/InvoiceRegistrationDto.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLedgerProductDTO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLedgerProductDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/excel/InvoiceRegisAndProductExcelDto.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/InvoiceRegistrationMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/InvoiceRegistrationProductMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerFileMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedgerFile.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/InvoiceRegistrationService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/InvoiceRegistrationServiceImpl.java 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 265 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-druid.yml 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/SupplierManageMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/InvoiceRegistrationMapper.xml 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/RuoYiApplication.java
@@ -3,12 +3,15 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
 * å¯åŠ¨ç¨‹åº
 * 
 * @author ruoyi
 */
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@EnableScheduling
public class RuoYiApplication
{
    public static void main(String[] args)
src/main/java/com/ruoyi/basic/controller/SupplierManageController.java
@@ -6,9 +6,11 @@
import com.ruoyi.basic.service.ISupplierService;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@RequestMapping("/system/supplier")
@@ -30,12 +32,15 @@
    /**
     * ä¾›åº”商删除
     * @param id
     * @param ids
     * @return
     */
    @DeleteMapping("/del")
    public AjaxResult delSupplier(Integer id) {
        supplierService.delSupplier(id);
    public AjaxResult delSupplier(@RequestBody List<Integer> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return AjaxResult.error("请选择至少一条数据");
        }
        supplierService.delSupplier(ids);
        return AjaxResult.success();
    }
@@ -44,8 +49,8 @@
     * @param id
     * @return
     */
    @GetMapping("/info")
    public AjaxResult supplierDetail(Integer id) {
    @GetMapping("/{id}")
    public AjaxResult supplierDetail(@PathVariable("id") Integer id) {
        return AjaxResult.success(supplierService.supplierDetail(id));
    }
@@ -56,6 +61,7 @@
     */
    @PostMapping("/update")
    public AjaxResult update(@RequestBody SupplierManage supplierManage) {
        supplierService.supplierUpdate(supplierManage);
        return AjaxResult.success();
    }
@@ -75,8 +81,8 @@
     * @param response
     * @param supplierManageDto
     */
    @GetMapping("/export")
    public void supplierExport(HttpServletResponse response, SupplierManageDto supplierManageDto) {
    @PostMapping("/export")
    public void supplierExport(HttpServletResponse response,SupplierManageDto supplierManageDto) {
        supplierService.supplierExport(response, supplierManageDto);
    }
}
src/main/java/com/ruoyi/basic/excel/SupplierManageExcelDto.java
@@ -35,8 +35,8 @@
    @Excel(name = "联系电话")
    private String contactUserPhone;
    @Excel(name = "维护人ID")
    private Integer maintainUserId;
    @Excel(name = "维护人")
    private String maintainUserName;
    @Excel(name = "维护时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
src/main/java/com/ruoyi/basic/pojo/SupplierManage.java
@@ -5,6 +5,7 @@
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@@ -42,8 +43,8 @@
    private Integer maintainUserId;
    @ApiModelProperty(value = "维护时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime maintainTime;
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate maintainTime;
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
src/main/java/com/ruoyi/basic/service/ISupplierService.java
@@ -6,6 +6,7 @@
import com.ruoyi.basic.pojo.SupplierManage;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
public interface ISupplierService {
@@ -18,10 +19,10 @@
    /**
     * ä¾›åº”商删除
     * @param id
     * @param ids
     * @return
     */
    int delSupplier(Integer id);
    int delSupplier(List<Integer> ids);
    /**
     * ä¾›åº”商详情
@@ -35,7 +36,7 @@
     * @param supplierManage
     * @return
     */
    int supplierManage(SupplierManage supplierManage);
    int supplierUpdate(SupplierManage supplierManage);
    /**
     * ä¾›åº”商分页查询
src/main/java/com/ruoyi/basic/service/impl/SupplierServiceImpl.java
@@ -1,5 +1,6 @@
package com.ruoyi.basic.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -34,12 +35,14 @@
    /**
     * ä¾›åº”商删除
     * @param id
     * @param ids
     * @return
     */
    @Override
    public int delSupplier(Integer id) {
        return supplierMapper.deleteById(id);
    public int delSupplier(List<Integer> ids) {
        LambdaQueryWrapper<SupplierManage> delWrapper = new LambdaQueryWrapper<>();
        delWrapper.in(SupplierManage::getId, ids);
        return supplierMapper.delete(delWrapper);
    }
    /**
@@ -58,7 +61,7 @@
     * @return
     */
    @Override
    public int supplierManage(SupplierManage supplierManage) {
    public int supplierUpdate(SupplierManage supplierManage) {
        return supplierMapper.updateById(supplierManage);
    }
src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java
@@ -23,6 +23,11 @@
        IGNORE_TABLES.add("sys_menu");
        IGNORE_TABLES.add("sys_role_dept");
        IGNORE_TABLES.add("sys_logininfor");
        IGNORE_TABLES.add("sys_post");
        IGNORE_TABLES.add("sys_user_post");
        IGNORE_TABLES.add("sales_ledger_product");
        IGNORE_TABLES.add("sales_ledger_file");
        IGNORE_TABLES.add("temp_file");
    }
}
src/main/java/com/ruoyi/other/controller/TempFileController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.other.controller;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.other.service.TempFileService;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@RestController
@RequestMapping("/file")
@AllArgsConstructor
public class TempFileController {
    private TempFileService tempFileService;
    @PostMapping("/upload")
    public AjaxResult uploadFile(MultipartFile file) {
        try {
            return AjaxResult.success(tempFileService.uploadFile(file));
        }catch (Exception e) {
            return AjaxResult.error(e.getMessage());
        }
    }
}
src/main/java/com/ruoyi/other/mapper/TempFileMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
package com.ruoyi.other.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.other.pojo.TempFile;
public interface TempFileMapper extends BaseMapper<TempFile> {
}
src/main/java/com/ruoyi/other/pojo/TempFile.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.other.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("temp_file")
public class TempFile {
    private String tempId;         // ä¸´æ—¶æ–‡ä»¶ID(UUID)
    private String originalName;   // åŽŸå§‹æ–‡ä»¶å
    private String tempPath;       // ä¸´æ—¶å­˜å‚¨è·¯å¾„
    private LocalDateTime expireTime; // è¿‡æœŸæ—¶é—´
}
src/main/java/com/ruoyi/other/service/TempFileService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
package com.ruoyi.other.service;
import com.ruoyi.other.pojo.TempFile;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
public interface TempFileService {
    TempFile uploadFile(MultipartFile file) throws IOException;
}
src/main/java/com/ruoyi/other/service/impl/TempFileServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
package com.ruoyi.other.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.other.mapper.TempFileMapper;
import com.ruoyi.other.pojo.TempFile;
import com.ruoyi.other.service.TempFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
@Service
@Slf4j
public class TempFileServiceImpl extends ServiceImpl<TempFileMapper, TempFile> implements TempFileService {
    @Autowired
    private TempFileMapper tempFileMapper;
    @Value("${file.temp-dir}")
    private String tempDir;
    // ä¸Šä¼ åˆ°ä¸´æ—¶ç›®å½•
    @Override
    public TempFile uploadFile(MultipartFile file) throws IOException {
        // 1. ç”Ÿæˆä¸´æ—¶æ–‡ä»¶ID和路径
        String tempId = UUID.randomUUID().toString();
        Path tempFilePath = Paths.get(tempDir, tempId + "_" + file.getOriginalFilename());
        // 2. ç¡®ä¿ç›®å½•存在
        Path parentDir = tempFilePath.getParent();
        if (parentDir != null) {
            Files.createDirectories(parentDir); // é€’归创建目录
        }
        // 3. ä¿å­˜æ–‡ä»¶åˆ°ä¸´æ—¶ç›®å½•
        file.transferTo(tempFilePath.toFile());
        // 4. ä¿å­˜ä¸´æ—¶æ–‡ä»¶è®°å½•
        TempFile tempFileRecord = new TempFile();
        tempFileRecord.setTempId(tempId);
        tempFileRecord.setOriginalName(file.getOriginalFilename());
        tempFileRecord.setTempPath(tempFilePath.toString());
        tempFileRecord.setExpireTime(LocalDateTime.now().plusHours(2)); // 2小时后过期
        tempFileMapper.insert(tempFileRecord);
        return tempFileRecord;
    }
    @Scheduled(cron = "0 0 3 * * ?") // æ¯å¤©å‡Œæ™¨3点执行
    public void cleanupExpiredTempFiles() {
        LambdaQueryWrapper<TempFile> wrapper = new LambdaQueryWrapper<>();
        wrapper.lt(TempFile::getExpireTime, LocalDateTime.now()); // expireTime < å½“前时间
        List<TempFile> expiredFiles = tempFileMapper.selectList(wrapper);
        for (TempFile file : expiredFiles) {
            try {
                // åˆ é™¤ç‰©ç†æ–‡ä»¶
                Files.deleteIfExists(Paths.get(file.getTempPath()));
                // åˆ é™¤æ•°æ®åº“记录
                tempFileMapper.deleteById(file);
                log.info("已清理过期临时文件: {}", file.getTempPath());
            } catch (IOException e) {
                log.error("删除文件失败: {}", file.getTempPath(), e);
                // å¯é€‰æ‹©è®°å½•失败日志或重试
            }
        }
        log.info("过期临时文件清理完成,共清理 {} ä¸ªæ–‡ä»¶", expiredFiles.size());
    }
}
src/main/java/com/ruoyi/sales/controller/InvoiceRegistrationController.java
@@ -8,6 +8,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@@ -22,9 +23,9 @@
     * @param invoiceRegistrationDto
     * @return
     */
    @PostMapping("/add")
    public AjaxResult invoiceRegistrationAdd(@RequestBody InvoiceRegistrationDto invoiceRegistrationDto) {
        invoiceRegistrationService.invoiceRegistrationAdd(invoiceRegistrationDto);
    @PostMapping("/saveOrUpdate")
    public AjaxResult invoiceRegistrationSaveOrUpdate(@RequestBody InvoiceRegistrationDto invoiceRegistrationDto) {
        invoiceRegistrationService.invoiceRegistrationSaveOrUpdate(invoiceRegistrationDto);
        return AjaxResult.success();
    }
@@ -34,7 +35,7 @@
     * @return
     */
    @DeleteMapping("/del")
    public AjaxResult invoiceRegistrationDel(@RequestParam List<Integer> ids) {
    public AjaxResult invoiceRegistrationDel(@RequestBody  List<Integer> ids) {
        invoiceRegistrationService.invoiceRegistrationDel(ids);
        return AjaxResult.success();
    }
@@ -70,4 +71,24 @@
    public AjaxResult invoiceRegistrationProductList(InvoiceRegistrationProductDto invoiceRegistrationProductDto) {
        return AjaxResult.success(invoiceRegistrationService.invoiceRegistrationProductList(invoiceRegistrationProductDto));
    }
    /**
     * å¼€ç¥¨ç™»è®°äº§å“è¯¦æƒ…
     * @param id
     * @return
     */
    @GetMapping("/detail")
    public AjaxResult invoiceRegistrationDetail(Integer id) {
        return AjaxResult.success(invoiceRegistrationService.invoiceRegistrationDetail(id));
    }
    /**
     * å¼€ç¥¨ç™»è®°å¯¼å‡º
     * @param response
     * @param invoiceRegistrationDto
     */
    @PostMapping("/export")
    public void invoiceRegistrationExport(HttpServletResponse response, InvoiceRegistrationDto invoiceRegistrationDto) {
        invoiceRegistrationService.invoiceRegistrationExport(response, invoiceRegistrationDto);
    }
}
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -1,6 +1,6 @@
package com.ruoyi.sales.controller;
import java.util.List;
import java.util.*;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -8,13 +8,7 @@
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.service.ISalesLedgerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
@@ -23,14 +17,13 @@
/**
 * é”€å”®å°è´¦Controller
 *
 *
 * @author ruoyi
 * @date 2025-05-08
 */
@RestController
@RequestMapping("/sales/ledger")
public class SalesLedgerController extends BaseController
{
public class SalesLedgerController extends BaseController {
    @Autowired
    private ISalesLedgerService salesLedgerService;
@@ -38,10 +31,9 @@
     * æŸ¥è¯¢é”€å”®å°è´¦åˆ—表
     */
    @GetMapping("/list")
    public TableDataInfo list(SalesLedger salesLedger)
    {
    public TableDataInfo list(SalesLedgerDto salesLedgerDto) {
        startPage();
        List<SalesLedger> list = salesLedgerService.selectSalesLedgerList(salesLedger);
        List<SalesLedger> list = salesLedgerService.selectSalesLedgerList(salesLedgerDto);
        return getDataTable(list);
    }
@@ -49,11 +41,8 @@
     * æŸ¥è¯¢é”€å”®å°è´¦å’Œäº§å“çˆ¶å­åˆ—表
     */
    @GetMapping("/getSalesLedgerWithProducts")
    public TableDataInfo getSalesLedgerWithProducts()
    {
        startPage();
        List<SalesLedgerDto> list = salesLedgerService.getSalesLedgerWithProducts();
        return getDataTable(list);
    public SalesLedgerDto getSalesLedgerWithProducts(SalesLedgerDto salesLedgerDto) {
        return salesLedgerService.getSalesLedgerWithProducts(salesLedgerDto);
    }
    /**
@@ -61,29 +50,18 @@
     */
    @Log(title = "销售台账", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, SalesLedger salesLedger)
    {
        List<SalesLedger> list = salesLedgerService.selectSalesLedgerList(salesLedger);
    public void export(HttpServletResponse response, SalesLedgerDto salesLedgerDto) {
        List<SalesLedger> list = salesLedgerService.selectSalesLedgerList(salesLedgerDto);
        ExcelUtil<SalesLedger> util = new ExcelUtil<SalesLedger>(SalesLedger.class);
        util.exportExcel(response, list, "销售台账数据");
    }
    /**
     * èŽ·å–é”€å”®å°è´¦è¯¦ç»†ä¿¡æ¯
     */
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Long id)
    {
        return success(salesLedgerService.selectSalesLedgerById(id));
    }
    /**
     * æ–°å¢žä¿®æ”¹é”€å”®å°è´¦
     */
    @Log(title = "销售台账", businessType = BusinessType.INSERT)
    @PostMapping ("/addOrUpdateSalesLedger")
    public AjaxResult add(@RequestBody SalesLedgerDto salesLedgerDto)
    {
    @PostMapping("/addOrUpdateSalesLedger")
    public AjaxResult add(@RequestBody SalesLedgerDto salesLedgerDto) {
        return toAjax(salesLedgerService.addOrUpdateSalesLedger(salesLedgerDto));
    }
@@ -91,12 +69,22 @@
     * åˆ é™¤é”€å”®å°è´¦
     */
    @Log(title = "销售台账", businessType = BusinessType.DELETE)
    @DeleteMapping("/delLedger")
    public AjaxResult remove(@RequestBody Long[] ids)
    {
    @DeleteMapping("/delLedger")
    public AjaxResult remove(@RequestBody Long[] ids) {
        if (ids == null || ids.length == 0) {
            return AjaxResult.error("请传入要删除的ID");
        }
        return toAjax(salesLedgerService.deleteSalesLedgerByIds(ids));
    }
    /**
     * æŸ¥è¯¢é”€å”®å°è´¦ä¸åˆ†é¡µ
     * @param salesLedgerDto
     * @return
     */
    @GetMapping("/listNoPage")
    public AjaxResult listNoPage(SalesLedgerDto salesLedgerDto){
        List<SalesLedger> list = salesLedgerService.selectSalesLedgerList(salesLedgerDto);
        return AjaxResult.success(list);
    }
}
src/main/java/com/ruoyi/sales/dto/InvoiceRegistrationDto.java
@@ -5,6 +5,7 @@
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
@@ -13,4 +14,13 @@
    @ApiModelProperty(name = "开票登记产品集合")
    private List<InvoiceRegistrationProductDto> productDtoList;
    @ApiModelProperty(name = "客户合同号")
    private String customerContractNo;
    @ApiModelProperty(name = "客户名称")
    private String customerName;
    @ApiModelProperty(name = "合同金额")
    private BigDecimal contractAmount;
}
src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
@@ -23,4 +23,6 @@
    private String attachmentMaterials;
    private Boolean hasChildren = false;
    private List<SalesLedgerProduct> productData;
    private List<String> tempFileIds;
}
src/main/java/com/ruoyi/sales/dto/SalesLedgerProductDTO.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/sales/dto/SalesLedgerProductDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,4 @@
package com.ruoyi.sales.dto;
public class SalesLedgerProductDto {
}
src/main/java/com/ruoyi/sales/excel/InvoiceRegisAndProductExcelDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,73 @@
package com.ruoyi.sales.excel;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class InvoiceRegisAndProductExcelDto {
    @Excel(name = "销售合同号")
    private String salesContractNo;
    @Excel(name = "客户合同号")
    private String customerContractNo;
    @Excel(name = "客户名称")
    private String customerName;
    @Excel(name =  "业务员")
    private String salesman;
    @Excel(name = "项目名称")
    private String projectName;
    @Excel(name =  "合同金额")
    private BigDecimal contractAmount;
    @Excel(name =  "产品大类")
    private String productCategory;
    @Excel(name =  "规格型号")
    private String specificationModel;
    @Excel(name =  "单位")
    private String unit;
    @Excel(name =  "数量")
    private BigDecimal quantity;
    @Excel(name =  "税率%")
    private BigDecimal taxRate;
    @Excel(name = "含税单价")
    private BigDecimal taxInclusiveUnitPrice;
    @Excel(name = "含税总价")
    private BigDecimal taxInclusiveTotalPrice;
    @Excel(name ="不含税总价")
    private BigDecimal taxExclusiveTotalPrice;
    @Excel(name ="发票类型")
    private String invoiceType;
    @Excel(name ="开票数")
    private Integer invoiceNum;
    @Excel(name ="未开票数")
    private Integer noInvoiceNum;
    @Excel(name ="开票金额")
    private BigDecimal invoiceAmount;
    @Excel(name ="未开票金额")
    private BigDecimal noInvoiceAmount;
}
src/main/java/com/ruoyi/sales/mapper/InvoiceRegistrationMapper.java
@@ -4,8 +4,11 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.sales.dto.InvoiceRegistrationDto;
import com.ruoyi.sales.excel.InvoiceRegisAndProductExcelDto;
import com.ruoyi.sales.pojo.InvoiceRegistration;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface InvoiceRegistrationMapper extends BaseMapper<InvoiceRegistration> {
@@ -16,4 +19,10 @@
     * @return
     */
    IPage<InvoiceRegistrationDto> invoiceRegistrationListPage(Page page, @Param("invoiceRegistrationDto") InvoiceRegistrationDto invoiceRegistrationDto);
    /**
     * å¼€ç¥¨ç™»è®°å¯¼å‡ºæ•°æ®æŸ¥è¯¢
     * @return
     */
    List<InvoiceRegisAndProductExcelDto> invoiceRegisAndProductExcelDtoList();
}
src/main/java/com/ruoyi/sales/mapper/InvoiceRegistrationProductMapper.java
@@ -1,6 +1,7 @@
package com.ruoyi.sales.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.sales.dto.InvoiceRegistrationDto;
import com.ruoyi.sales.dto.InvoiceRegistrationProductDto;
import com.ruoyi.sales.pojo.InvoiceRegistrationProduct;
import org.apache.ibatis.annotations.Param;
@@ -15,5 +16,4 @@
     * @return
     */
    List<InvoiceRegistrationProductDto> invoiceRegistrationProductList(@Param("invoiceRegistrationProductDto") InvoiceRegistrationProductDto invoiceRegistrationProductDto);
}
src/main/java/com/ruoyi/sales/mapper/SalesLedgerFileMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
package com.ruoyi.sales.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.sales.pojo.SalesLedgerFile;
public interface SalesLedgerFileMapper extends BaseMapper<SalesLedgerFile> {
}
src/main/java/com/ruoyi/sales/pojo/SalesLedgerFile.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package com.ruoyi.sales.pojo;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("sales_ledger_file")
public class SalesLedgerFile {
    @TableId(type = IdType.AUTO)
    private Long id;
    /** é”€å”®å°è´¦ID */
    private Long ledgerId;
    /** æ–‡ä»¶åç§° */
    private String fileName;
    /** æ–‡ä»¶è·¯å¾„ */
    private String filePath;
    /** åˆ›å»ºæ—¶é—´ */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /** æ›´æ–°æ—¶é—´ */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
@@ -14,13 +14,11 @@
 */
public interface ISalesLedgerService extends IService<SalesLedger> {
    List<SalesLedger> selectSalesLedgerList(SalesLedger salesLedger);
    SalesLedger selectSalesLedgerById(Long id);
    List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto);
    int deleteSalesLedgerByIds(Long[] ids);
    int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto);
    List<SalesLedgerDto> getSalesLedgerWithProducts();
    SalesLedgerDto getSalesLedgerWithProducts(SalesLedgerDto salesLedgerDto);
}
src/main/java/com/ruoyi/sales/service/InvoiceRegistrationService.java
@@ -6,6 +6,7 @@
import com.ruoyi.sales.dto.InvoiceRegistrationProductDto;
import org.springframework.web.bind.annotation.RequestBody;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
public interface InvoiceRegistrationService {
@@ -15,7 +16,7 @@
     * @param invoiceRegistrationDto
     * @return
     */
    void invoiceRegistrationAdd(InvoiceRegistrationDto invoiceRegistrationDto);
    void invoiceRegistrationSaveOrUpdate(InvoiceRegistrationDto invoiceRegistrationDto);
    /**
     * å¼€ç¥¨ç™»è®°åˆ é™¤
@@ -46,4 +47,18 @@
     * @return
     */
    List<InvoiceRegistrationProductDto> invoiceRegistrationProductList(InvoiceRegistrationProductDto invoiceRegistrationProductDto);
    /**
     * å¼€ç¥¨ç™»è®°è¯¦æƒ…
     * @param id
     * @return
     */
    InvoiceRegistrationDto invoiceRegistrationDetail(Integer id);
    /**
     * å¼€ç¥¨ç™»è®°å¯¼å‡º
     * @param response
     * @param invoiceRegistrationDto
     */
    void invoiceRegistrationExport(HttpServletResponse response, InvoiceRegistrationDto invoiceRegistrationDto);
}
src/main/java/com/ruoyi/sales/service/impl/InvoiceRegistrationServiceImpl.java
@@ -1,11 +1,15 @@
package com.ruoyi.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.excel.SupplierManageExcelDto;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.sales.dto.InvoiceRegistrationDto;
import com.ruoyi.sales.dto.InvoiceRegistrationProductDto;
import com.ruoyi.sales.excel.InvoiceRegisAndProductExcelDto;
import com.ruoyi.sales.mapper.InvoiceRegistrationMapper;
import com.ruoyi.sales.mapper.InvoiceRegistrationProductMapper;
import com.ruoyi.sales.pojo.InvoiceRegistration;
@@ -16,7 +20,11 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class InvoiceRegistrationServiceImpl extends ServiceImpl<InvoiceRegistrationMapper, InvoiceRegistration> implements InvoiceRegistrationService {
@@ -35,19 +43,30 @@
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void invoiceRegistrationAdd(InvoiceRegistrationDto invoiceRegistrationDto) {
    public void invoiceRegistrationSaveOrUpdate(InvoiceRegistrationDto invoiceRegistrationDto) {
        InvoiceRegistration invoiceRegistration = new InvoiceRegistration();
        BeanUtils.copyProperties(invoiceRegistrationDto, invoiceRegistration);
        // æ–°å¢žå¼€ç¥¨ç™»è®°
        invoiceRegistrationMapper.insert(invoiceRegistration);
        List<InvoiceRegistrationProductDto> productDtoList = invoiceRegistrationDto.getProductDtoList();
        // æ–°å¢žå¼€ç¥¨äº§å“ç™»è®°
        if(CollectionUtils.isNotEmpty(productDtoList)){
            for (InvoiceRegistrationProductDto invoiceRegistrationProductDto : productDtoList) {
                InvoiceRegistrationProduct invoiceRegistrationProduct = new InvoiceRegistrationProduct();
                BeanUtils.copyProperties(invoiceRegistrationProductDto, invoiceRegistrationProduct);
                invoiceRegistrationProduct.setInvoiceRegistrationId(invoiceRegistration.getId());
                invoiceRegistrationProductMapper.insert(invoiceRegistrationProduct);
        // æ–°å¢žå¼€ç¥¨ç™»è®°
        if(invoiceRegistrationDto.getId() == null){
            invoiceRegistrationMapper.insert(invoiceRegistration);
            // æ–°å¢žå¼€ç¥¨äº§å“ç™»è®°
            if(CollectionUtils.isNotEmpty(productDtoList)){
                for (InvoiceRegistrationProductDto invoiceRegistrationProductDto : productDtoList) {
                    InvoiceRegistrationProduct invoiceRegistrationProduct = new InvoiceRegistrationProduct();
                    BeanUtils.copyProperties(invoiceRegistrationProductDto, invoiceRegistrationProduct);
                    invoiceRegistrationProduct.setInvoiceRegistrationId(invoiceRegistration.getId());
                    invoiceRegistrationProductMapper.insert(invoiceRegistrationProduct);
                }
            }
        // å¼€ç¥¨ç™»è®°ä¿®æ”¹
        }else {
            if(CollectionUtils.isNotEmpty(productDtoList)){
                for (InvoiceRegistrationProductDto invoiceRegistrationProductDto : productDtoList) {
                    InvoiceRegistrationProduct invoiceRegistrationProduct = new InvoiceRegistrationProduct();
                    BeanUtils.copyProperties(invoiceRegistrationProductDto, invoiceRegistrationProduct);
                    invoiceRegistrationProductMapper.updateById(invoiceRegistrationProduct);
                }
            }
        }
    }
@@ -109,4 +128,41 @@
    public List<InvoiceRegistrationProductDto> invoiceRegistrationProductList(InvoiceRegistrationProductDto invoiceRegistrationProductDto) {
        return invoiceRegistrationProductMapper.invoiceRegistrationProductList(invoiceRegistrationProductDto);
    }
    /**
     * å¼€ç¥¨ç™»è®°è¯¦æƒ…
     * @param id
     * @return
     */
    @Override
    public InvoiceRegistrationDto invoiceRegistrationDetail(Integer id) {
        InvoiceRegistration invoiceRegistration = invoiceRegistrationMapper.selectById(id);
        if(ObjectUtils.isEmpty(invoiceRegistration)){
            throw new RuntimeException("开票登记信息查找失败");
        }
        InvoiceRegistrationDto invoiceRegistrationDto = new InvoiceRegistrationDto();
        BeanUtils.copyProperties(invoiceRegistration, invoiceRegistrationDto);
        QueryWrapper<InvoiceRegistrationProduct> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("invoice_registration_id", id);
        List<InvoiceRegistrationProduct> invoiceRegistrationProductList = invoiceRegistrationProductMapper.selectList(queryWrapper);
        List<InvoiceRegistrationProductDto> invoiceRegistrationProductDtoList = invoiceRegistrationProductList.stream().map(item -> {
            InvoiceRegistrationProductDto invoiceRegistrationProductDto = new InvoiceRegistrationProductDto();
            BeanUtils.copyProperties(item, invoiceRegistrationProductDto);
            return invoiceRegistrationProductDto;
        }).collect(Collectors.toList());
        invoiceRegistrationDto.setProductDtoList(invoiceRegistrationProductDtoList);
        return invoiceRegistrationDto;
    }
    /**
     * å¼€ç¥¨ç™»è®°å¯¼å‡º
     * @param response
     * @param invoiceRegistrationDto
     */
    @Override
    public void invoiceRegistrationExport(HttpServletResponse response, InvoiceRegistrationDto invoiceRegistrationDto) {
        List<InvoiceRegisAndProductExcelDto> invoiceRegisAndProductExcelDtoList = invoiceRegistrationMapper.invoiceRegisAndProductExcelDtoList();
        ExcelUtil<InvoiceRegisAndProductExcelDto> util = new ExcelUtil<InvoiceRegisAndProductExcelDto>(InvoiceRegisAndProductExcelDto.class);
        util.exportExcel(response, invoiceRegisAndProductExcelDtoList, "开票登记信息");
    }
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -1,15 +1,25 @@
package com.ruoyi.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerProductService;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
 * äº§å“ä¿¡æ¯Service业务层处理
@@ -18,9 +28,12 @@
 * @date 2025-05-08
 */
@Service
@AllArgsConstructor
public class SalesLedgerProductServiceImpl extends ServiceImpl<SalesLedgerProductMapper, SalesLedgerProduct> implements ISalesLedgerProductService {
    @Autowired
    private SalesLedgerProductMapper salesLedgerProductMapper;
    private SalesLedgerMapper salesLedgerMapper;
    @Override
    public SalesLedgerProduct selectSalesLedgerProductById(Long id) {
@@ -35,16 +48,109 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteSalesLedgerProductByIds(Long[] ids) {
        return salesLedgerProductMapper.deleteBatchIds(Arrays.asList(ids));
        if (ids == null || ids.length == 0) {
            return 0;
        }
        // 1. å…ˆæŸ¥è¯¢è¦åˆ é™¤çš„子表记录,获取对应的 salesLedgerId
        List<SalesLedgerProduct> deletedProducts = salesLedgerProductMapper.selectBatchIds(Arrays.asList(ids));
        if (deletedProducts.isEmpty()) {
            return 0; // æ²¡æœ‰å¯åˆ é™¤çš„æ•°æ®
        }
        // å¯èƒ½å±žäºŽå¤šä¸ªä¸»è¡¨ï¼ˆä½†é€šå¸¸ä¸€ä¸ªæŽ¥å£åªå¤„理一个主表)
        Set<Long> mainIds = deletedProducts.stream()
                .map(SalesLedgerProduct::getSalesLedgerId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        // 2. æ‰§è¡Œåˆ é™¤æ“ä½œ
        int result = salesLedgerProductMapper.deleteBatchIds(Arrays.asList(ids));
        // 3. å¯¹æ¯ä¸ªä¸»è¡¨ID进行金额更新
        for (Long salesLedgerId : mainIds) {
            LambdaQueryWrapper<SalesLedgerProduct> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerId);
            List<SalesLedgerProduct> remainingProducts = salesLedgerProductMapper.selectList(wrapper);
            // è°ƒç”¨é€šç”¨æ–¹æ³•更新主表金额
            updateMainContractAmount(
                    salesLedgerId,
                    remainingProducts,
                    SalesLedgerProduct::getTaxInclusiveTotalPrice,
                    salesLedgerMapper,
                    SalesLedger.class
            );
        }
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addOrUpdateSalesLedgerProduct(SalesLedgerProduct salesLedgerProduct) {
        if (salesLedgerProduct.getId() == null){
            return salesLedgerProductMapper.insert(salesLedgerProduct);
        }else {
            return  salesLedgerProductMapper.updateById(salesLedgerProduct);
        int result;
        Long salesLedgerId = salesLedgerProduct.getSalesLedgerId();
        if (salesLedgerProduct.getId() == null) {
            result = salesLedgerProductMapper.insert(salesLedgerProduct);
        } else {
            result = salesLedgerProductMapper.updateById(salesLedgerProduct);
        }
        // å¦‚果插入或更新成功,并且有 salesLedgerId,才继续更新主表金额
        if (result > 0 && salesLedgerId != null) {
            // æŸ¥è¯¢è¯¥ä¸»è¡¨ä¸‹çš„æ‰€æœ‰å­è¡¨æ•°æ®
            LambdaQueryWrapper<SalesLedgerProduct> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerId);
            List<SalesLedgerProduct> productList = salesLedgerProductMapper.selectList(wrapper);
            // è°ƒç”¨é€šç”¨æ–¹æ³•更新主表金额
            updateMainContractAmount(
                    salesLedgerId,
                    productList,
                    SalesLedgerProduct::getTaxInclusiveTotalPrice,
                    salesLedgerMapper,
                    SalesLedger.class
            );
        }
        return result;
    }
    /**
     * é€šç”¨æ–¹æ³•:根据主表ID和子表列表,更新主表的合同金额
     */
    public <T, S> void updateMainContractAmount(
            Long mainId,
            List<T> subList,
            Function<T, BigDecimal> amountGetter,
            BaseMapper<S> mainMapper,
            Class<S> mainEntityClass) {
        if (mainId == null || subList == null || subList.isEmpty()) {
            return;
        }
        BigDecimal totalAmount = subList.stream()
                .map(amountGetter)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        try {
            S entity = mainEntityClass.getDeclaredConstructor().newInstance();
            Field idField = mainEntityClass.getDeclaredField("id");
            idField.setAccessible(true);
            idField.set(entity, mainId);
            Field amountField = mainEntityClass.getDeclaredField("contractAmount");
            amountField.setAccessible(true);
            amountField.set(entity, totalAmount);
            mainMapper.updateById(entity);
        } catch (Exception e) {
            throw new RuntimeException("动态更新主表金额失败", e);
        }
    }
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -1,28 +1,44 @@
package com.ruoyi.sales.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.other.mapper.TempFileMapper;
import com.ruoyi.other.pojo.TempFile;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.mapper.SalesLedgerFileMapper;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerFile;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerService;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.nio.file.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
@@ -32,14 +48,22 @@
 * @date 2025-05-08
 */
@Service
@AllArgsConstructor
@RequiredArgsConstructor
@Slf4j
public class SalesLedgerServiceImpl extends ServiceImpl<SalesLedgerMapper, SalesLedger> implements ISalesLedgerService {
    private SalesLedgerMapper salesLedgerMapper;
    private final  SalesLedgerMapper salesLedgerMapper;
    private CustomerMapper customerMapper;
    private final  CustomerMapper customerMapper;
    private SalesLedgerProductMapper salesLedgerProductMapper;
    private final  SalesLedgerProductMapper salesLedgerProductMapper;
    private final SalesLedgerFileMapper salesLedgerFileMapper;
    private final TempFileMapper tempFileMapper;
    @Value("${file.upload-dir}")
    private String uploadDir;
    private static final String LOCK_PREFIX = "contract_no_lock:";
    private static final long LOCK_WAIT_TIMEOUT = 10; // é”ç­‰å¾…超时时间(秒)
@@ -48,33 +72,34 @@
    private final RedisTemplate<String, String> redisTemplate;
    @Override
    public List<SalesLedger> selectSalesLedgerList(SalesLedger salesLedger) {
        return salesLedgerMapper.selectList(new LambdaQueryWrapper<>());
    public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) {
        LambdaQueryWrapper<SalesLedger> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(salesLedgerDto.getCustomerName())) {
            queryWrapper.eq(SalesLedger::getCustomerName, salesLedgerDto.getCustomerName());
        }
        return salesLedgerMapper.selectList(queryWrapper);
    }
    public List<SalesLedgerDto> getSalesLedgerWithProducts() {
        List<SalesLedger> ledgers = salesLedgerMapper.selectList(new LambdaQueryWrapper<>());
        List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(new LambdaQueryWrapper<>());
    public SalesLedgerDto getSalesLedgerWithProducts(SalesLedgerDto salesLedgerDto) {
        // 1. æŸ¥è¯¢ä¸»è¡¨
        SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerDto.getId());
        if (salesLedger == null) {
            throw new BaseException("台账不存在");
        }
        Map<Long, List<SalesLedgerProduct>> productMap = products.stream()
                .collect(Collectors.groupingBy(SalesLedgerProduct::getSalesLedgerId));
        // 2. æŸ¥è¯¢å­è¡¨
        LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
        productWrapper.eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId());
        List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(productWrapper);
        return ledgers.stream().map(ledger -> {
            SalesLedgerDto dto = new SalesLedgerDto();
            org.springframework.beans.BeanUtils.copyProperties(ledger, dto);
            List<SalesLedgerProduct> ledgerProducts = productMap.getOrDefault(ledger.getId(), Collections.emptyList());
            if (!ledgerProducts.isEmpty()) {
                dto.setHasChildren(true);
                dto.setProductData(ledgerProducts);
            }
            return dto;
        }).collect(Collectors.toList());
    }
    @Override
    public SalesLedger selectSalesLedgerById(Long id) {
        return salesLedgerMapper.selectById(id);
        // 3. è½¬æ¢ DTO
        SalesLedgerDto resultDto = new SalesLedgerDto();
        BeanUtils.copyProperties(salesLedger, resultDto);
        if (!products.isEmpty()) {
            resultDto.setHasChildren(true);
            resultDto.setProductData(products);
        }
        return resultDto;
    }
    @Override
@@ -96,36 +121,125 @@
        return salesLedgerMapper.deleteBatchIds(idList);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto) {
        // 1. æ ¡éªŒå®¢æˆ·ä¿¡æ¯
        Customer customer = customerMapper.selectById(salesLedgerDto.getCustomerId());
        if (customer == null) {
            throw new BaseException("客户不存在");
        try {
            // 1. æ ¡éªŒå®¢æˆ·ä¿¡æ¯
            Customer customer = customerMapper.selectById(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);
                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());
        }
        // 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. å¤„理子表数据
        if (salesLedgerDto.getProductData() != null && !salesLedgerDto.getProductData().isEmpty()) {
            handleSalesLedgerProducts(salesLedger.getId(), salesLedgerDto.getProductData());
        }
        return 1; // æ“ä½œæˆåŠŸè¿”å›ž1
    }
    // æ–‡ä»¶è¿ç§»æ–¹æ³•
    /**
     * å°†ä¸´æ—¶æ–‡ä»¶è¿ç§»åˆ°æ­£å¼ç›®å½•
     *
     * @param businessId  ä¸šåŠ¡ID(销售台账ID)
     * @param tempFileIds ä¸´æ—¶æ–‡ä»¶ID列表
     * @throws IOException æ–‡ä»¶æ“ä½œå¼‚常
     */
    private void migrateTempFilesToFormal(Long businessId, List<String> tempFileIds) throws IOException {
        if (CollectionUtils.isEmpty(tempFileIds)) {
            return;
        }
        // æž„建正式目录路径(按业务类型和日期分组)
        String formalDir = uploadDir + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
        Path formalDirPath = Paths.get(formalDir);
        // ç¡®ä¿æ­£å¼ç›®å½•存在(递归创建)
        if (!Files.exists(formalDirPath)) {
            Files.createDirectories(formalDirPath);
        }
        for (String tempFileId : tempFileIds) {
            // æŸ¥è¯¢ä¸´æ—¶æ–‡ä»¶è®°å½•
            TempFile tempFile = tempFileMapper.selectById(tempFileId);
            if (tempFile == null) {
                throw new FileNotFoundException("临时文件不存在: " + tempFileId);
            }
            // æž„建正式文件名(包含业务ID和时间戳,避免冲突)
            String originalFilename = tempFile.getOriginalName();
            String fileExtension = FilenameUtils.getExtension(originalFilename);
            String formalFilename = businessId + "_" +
                    System.currentTimeMillis() + "_" +
                    UUID.randomUUID().toString().substring(0, 8) +
                    (StringUtils.hasText(fileExtension) ? "." + fileExtension : "");
            Path formalFilePath = formalDirPath.resolve(formalFilename);
            try {
                // æ‰§è¡Œæ–‡ä»¶è¿ç§»ï¼ˆä½¿ç”¨åŽŸå­æ“ä½œç¡®ä¿å®‰å…¨æ€§ï¼‰
                Files.move(
                        Paths.get(tempFile.getTempPath()),
                        formalFilePath,
                        StandardCopyOption.REPLACE_EXISTING,
                        StandardCopyOption.ATOMIC_MOVE
                );
                log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath);
                // æ›´æ–°æ–‡ä»¶è®°å½•(关联到业务ID)
                SalesLedgerFile fileRecord = new SalesLedgerFile();
                fileRecord.setLedgerId(businessId);
                fileRecord.setFileName(originalFilename);
                fileRecord.setFilePath(formalFilePath.toString());
                fileRecord.setCreateTime(LocalDateTime.now());
                salesLedgerFileMapper.insert(fileRecord);
                // åˆ é™¤ä¸´æ—¶æ–‡ä»¶è®°å½•
                tempFileMapper.deleteById(tempFile);
                log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath);
            } catch (IOException e) {
                log.error("文件迁移失败: {}", tempFile.getTempPath(), e);
                // å¯é€‰æ‹©å›žæ»šäº‹åŠ¡æˆ–è®°å½•å¤±è´¥æ–‡ä»¶
                throw new IOException("文件迁移异常", e);
            }
        }
    }
    private void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products) {
        // æŒ‰ID分组,区分新增和更新的记录
@@ -138,11 +252,15 @@
        // æ‰§è¡Œæ›´æ–°æ“ä½œ
        if (!updateList.isEmpty()) {
            salesLedgerProductMapper.updateBatchSomeColumn(updateList);
            for (SalesLedgerProduct product : updateList) {
                salesLedgerProductMapper.updateById(product);
            }
        }
        // æ‰§è¡Œæ’入操作
        if (!insertList.isEmpty()) {
            salesLedgerProductMapper.insertBatchSomeColumn(insertList);
            for (SalesLedgerProduct salesLedgerProduct : insertList) {
                salesLedgerProductMapper.insert(salesLedgerProduct);
            }
        }
    }
@@ -213,4 +331,39 @@
        }
        return next;
    }
    public <T, S> void updateMainContractAmount(
            Long mainId,
            List<T> subList,
            Function<T, BigDecimal> amountGetter,
            BaseMapper<S> mainMapper,
            Class<S> mainEntityClass) {
        if (mainId == null || subList == null || subList.isEmpty()) {
            return;
        }
        // è®¡ç®—子表金额总和
        BigDecimal totalAmount = subList.stream()
                .map(amountGetter)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // æž„造主表更新对象(支持任意主表类型)
        try {
            S entity = mainEntityClass.getDeclaredConstructor().newInstance();
            Field idField = mainEntityClass.getDeclaredField("id");
            idField.setAccessible(true);
            idField.set(entity, mainId);
            // è®¾ç½® contractAmount å­—段,注意这里假设字段名为 "contractAmount"
            Field amountField = mainEntityClass.getDeclaredField("contractAmount");
            amountField.setAccessible(true);
            amountField.set(entity, totalAmount);
            mainMapper.updateById(entity);
        } catch (Exception e) {
            throw new RuntimeException("动态更新主表金额失败", e);
        }
    }
}
src/main/resources/application-druid.yml
@@ -6,7 +6,8 @@
        druid:
            # ä¸»åº“数据源
            master:
                url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
#                url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                url: jdbc:mysql://114.132.189.42:9004/product-inventory-management?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                username: root
                password: 123456
            # ä»Žåº“数据源
@@ -59,3 +60,7 @@
                wall:
                    config:
                        multi-statement-allow: true
file:
    temp-dir: D:/ruoyi/temp/uploads   # ä¸´æ—¶ç›®å½•
    upload-dir: D:/ruoyi/prod/uploads # æ­£å¼ç›®å½•
src/main/resources/mapper/basic/SupplierManageMapper.xml
@@ -22,7 +22,7 @@
        T1.update_time,
        T1.update_user,
        T1.tenant_id,
        T2.user_name AS maintainUserName
        T2.nick_name AS maintainUserName
        FROM supplier_manage T1
        LEFT JOIN sys_user T2 ON T1.maintain_user_id = T2.user_id
        <where>
@@ -50,7 +50,7 @@
        T1.update_time,
        T1.update_user,
        T1.tenant_id,
        T2.user_name AS maintainUserName
        T2.nick_name AS maintainUserName
        FROM supplier_manage T1
        LEFT JOIN sys_user T2 ON T1.maintain_user_id = T2.user_id
        <where>
src/main/resources/mapper/sales/InvoiceRegistrationMapper.xml
@@ -5,17 +5,58 @@
<mapper namespace="com.ruoyi.sales.mapper.InvoiceRegistrationMapper">
    <select id="invoiceRegistrationListPage" resultType="com.ruoyi.sales.dto.InvoiceRegistrationDto">
        SELECT
            id              ,
            sales_ledger_id   ,
            sales_contract_no ,
            customer_id       ,
            salesman          ,
            project_name      ,
            create_time       ,
            create_user       ,
            update_time       ,
            update_user       ,
            tenant_id
        FROM invoice_registration
            T1.id              ,
            T1.sales_ledger_id   ,
            T1.sales_contract_no ,
            T1.customer_id       ,
            T1.salesman          ,
            T1.project_name      ,
            T1.create_time       ,
            T1.create_user       ,
            T1.update_time       ,
            T1.update_user       ,
            T1.tenant_id,
            T2.customer_contract_no,
            T3.customer_name,
            T2.contract_amount
        FROM invoice_registration T1
        LEFT JOIN sales_ledger T2 ON T1.sales_ledger_id = T2.id
        LEFT JOIN customer T3 ON T1.customer_id = T3.id
    </select>
    <select id="invoiceRegisAndProductExcelDtoList" resultType="com.ruoyi.sales.excel.InvoiceRegisAndProductExcelDto">
        SELECT
            T1.id              ,
            T1.sales_ledger_id   ,
            T1.sales_contract_no ,
            T1.customer_id       ,
            T1.salesman          ,
            T1.project_name      ,
            T1.create_time       ,
            T1.create_user       ,
            T1.update_time       ,
            T1.update_user       ,
            T1.tenant_id,
            T2.customer_contract_no,
            T3.customer_name,
            T2.contract_amount,
            T4.product_category,
            T4.specification_model,
            T4.unit,
            T4.quantity,
            T4.tax_rate,
            T4.tax_inclusive_unit_price,
            T4.tax_inclusive_total_price,
            T4.tax_exclusive_total_price,
            T4.invoice_type,
            T4.invoice_num,
            T4.invoice_amount,
            T4.no_invoice_num,
            T4.no_invoice_amount
        FROM invoice_registration T1
                 JOIN sales_ledger T2 ON T1.sales_ledger_id = T2.id
                 JOIN customer T3 ON T1.customer_id = T3.id
                JOIN invoice_registration_product T4 ON T1.id = T4.invoice_registration_id
        ORDER BY T1.id ASC
    </select>
</mapper>