zhangwencui
2026-10-29 b2f8320707dc72b2429bfb4fe0b317c11b1e5b7f
change
已添加15个文件
已修改10个文件
1284 ■■■■■ 文件已修改
src/main/java/com/ruoyi/basic/controller/CustomerController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerFinancialReportController.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerFinancialReportMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/Customer.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerFinancialReport.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/CustomerFinancialReportService.java 264 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customerPortal/controller/CustomerSubmissionController.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customerPortal/dto/ReviewSubmissionDto.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customerPortal/mapper/CustomerSubmissionMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customerPortal/pojo/CustomerSubmission.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customerPortal/service/CustomerSubmissionService.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/dto/SaveInfoDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/pojo/Info.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerSimpleController.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerSimpleMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PurchaseLedgerSimple.java 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/BusinessApprovalController.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/BusinessReviewDto.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/BusinessApprovalService.java 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/projectManagement/InfoMapper.xml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerMapper.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerController.java
@@ -13,6 +13,7 @@
import com.ruoyi.framework.web.domain.R;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -43,6 +44,7 @@
     * å¯¼å‡ºå®¢æˆ·æ¡£æ¡ˆåˆ—表
     */
    @Log(title = "客户档案", businessType = BusinessType.EXPORT)
    @PreAuthorize("@ss.hasPermi('basic:customer:export')")
    @PostMapping("/export")
    public void export(HttpServletResponse response, CustomerDto customer) {
        ExcelUtil<CustomerVo> util = new ExcelUtil<CustomerVo>(CustomerVo.class);
@@ -50,6 +52,7 @@
    }
    @PostMapping("/downloadTemplate")
    @PreAuthorize("@ss.hasPermi('basic:customer:import')")
    @Log(title = "客户档案-下载模板", businessType = BusinessType.EXPORT)
    public void downloadTemplate(HttpServletResponse response) {
        ExcelUtil<Customer> util = new ExcelUtil<Customer>(Customer.class);
@@ -61,6 +64,7 @@
     * å¯¼å…¥å®¢æˆ·æ¡£æ¡ˆ
     */
    @Log(title = "客户档案", businessType = BusinessType.IMPORT)
    @PreAuthorize("@ss.hasPermi('basic:customer:import')")
    @PostMapping("/importData")
    public R importData(MultipartFile file, Integer type) throws Exception {
@@ -71,6 +75,7 @@
     * èŽ·å–å®¢æˆ·æ¡£æ¡ˆè¯¦ç»†ä¿¡æ¯
     */
    @GetMapping(value = "/{id}")
    @PreAuthorize("@ss.hasPermi('basic:customer:query')")
    public R getInfo(@PathVariable("id") Long id) {
        return R.ok(customerService.selectCustomerDetailById(id));
    }
@@ -79,6 +84,7 @@
     * æ–°å¢žå®¢æˆ·æ¡£æ¡ˆ
     */
    @Log(title = "客户档案", businessType = BusinessType.INSERT)
    @PreAuthorize("@ss.hasPermi('basic:customer:add')")
    @PostMapping("/addCustomer")
    public R add(@RequestBody Customer customer) {
        return R.ok(customerService.insertCustomer(customer));
@@ -88,6 +94,7 @@
     * ä¿®æ”¹å®¢æˆ·æ¡£æ¡ˆ
     */
    @Log(title = "客户档案", businessType = BusinessType.UPDATE)
    @PreAuthorize("@ss.hasPermi('basic:customer:edit')")
    @PostMapping("/updateCustomer")
    public R edit(@RequestBody Customer customer) {
        return R.ok(customerService.updateCustomer(customer));
@@ -97,6 +104,7 @@
     * åˆ é™¤å®¢æˆ·æ¡£æ¡ˆ
     */
    @Log(title = "客户档案", businessType = BusinessType.DELETE)
    @PreAuthorize("@ss.hasPermi('basic:customer:remove')")
    @DeleteMapping("/delCustomer")
    public R remove(@RequestBody Long[] ids) {
        if (ids == null || ids.length == 0) {
src/main/java/com/ruoyi/basic/controller/CustomerFinancialReportController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
package com.ruoyi.basic.controller;
import com.ruoyi.basic.pojo.CustomerFinancialReport;
import com.ruoyi.basic.service.CustomerFinancialReportService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
/**
 * å®¢æˆ·è¿‘3年财务报表
 */
@RestController
@RequestMapping("/basic/customerFinancialReport")
@AllArgsConstructor
public class CustomerFinancialReportController {
    private final CustomerFinancialReportService customerFinancialReportService;
    /**
     * åŽå°æŒ‰å®¢æˆ·æŸ¥è¯¢è¿‘3年财务报表
     */
    @Operation(summary = "查询客户近3年财务报表")
    @PreAuthorize("@ss.hasPermi('basic:customer:report')")
    @GetMapping("/list")
    public AjaxResult list(Long customerId) {
        return AjaxResult.success(customerFinancialReportService.listByCustomer(customerId));
    }
    /**
     * å®¢æˆ·è´¦å·æŸ¥è¯¢è‡ªå·±çš„è¿‘3年财务报表
     */
    @Operation(summary = "查询我的财务报表")
    @PreAuthorize("@ss.hasPermi('customer:report:mine')")
    @GetMapping("/mine")
    public AjaxResult mine() {
        return AjaxResult.success(customerFinancialReportService.listMine());
    }
    /**
     * ä¸Šä¼ /修改财务报表
     */
    @Operation(summary = "上传财务报表")
    @Log(title = "客户财务报表", businessType = BusinessType.UPDATE)
    @PreAuthorize("@ss.hasPermi('basic:customer:report') or @ss.hasPermi('customer:report:mine')")
    @PostMapping("/save")
    public AjaxResult save(@RequestBody CustomerFinancialReport report) {
        return AjaxResult.success(customerFinancialReportService.saveReport(report));
    }
    /**
     * ç”³æŠ¥å®¡æ‰¹åˆ—表(内部人员查看所有客户的提交情况)
     */
    @Operation(summary = "申报审批列表")
    @PreAuthorize("@ss.hasPermi('sales:report:audit')")
    @GetMapping("/auditList")
    public AjaxResult auditList(String customerName, Integer reportYear, Integer auditStatus) {
        return AjaxResult.success(customerFinancialReportService.auditList(customerName, reportYear, auditStatus));
    }
    /**
     * å®¡æ‰¹è´¢åŠ¡æŠ¥è¡¨ï¼ˆ1通过 2退回)
     */
    @Operation(summary = "审批财务报表")
    @Log(title = "客户财务报表", businessType = BusinessType.UPDATE)
    @PreAuthorize("@ss.hasPermi('sales:report:audit')")
    @PostMapping("/audit")
    public AjaxResult audit(@RequestBody CustomerFinancialReport report) {
        return AjaxResult.success(customerFinancialReportService.auditReport(
                report.getId(), report.getAuditStatus(), report.getAuditRemark()));
    }
    /**
     * åˆ é™¤è´¢åŠ¡æŠ¥è¡¨ï¼ˆå®¢æˆ·è´¦å·ä¸å¯åˆ é™¤ï¼‰
     */
    @Operation(summary = "删除财务报表")
    @Log(title = "客户财务报表", businessType = BusinessType.DELETE)
    @PreAuthorize("@ss.hasPermi('basic:customer:report')")
    @DeleteMapping("/{id}")
    public AjaxResult remove(@PathVariable Long id) {
        customerFinancialReportService.deleteReport(id);
        return AjaxResult.success();
    }
}
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -87,6 +87,7 @@
    PURCHASE_RETURN_ORDERS("purchase_return_orders"),
    SALES_LEDGER_PRODUCT_TEMPLATE("sales_ledger_product_template"),
    PURCHASE_LEDGER("purchase_ledger"),
    PURCHASE_LEDGER_SIMPLE("purchase_ledger_simple"),
    PURCHASE_LEDGER_TEMPLATE("purchase_ledger_template"),
    // Project Management
    SHIPPING_ADDRESS("shipping_address"),
src/main/java/com/ruoyi/basic/mapper/CustomerFinancialReportMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
package com.ruoyi.basic.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.CustomerFinancialReport;
public interface CustomerFinancialReportMapper extends BaseMapper<CustomerFinancialReport> {
}
src/main/java/com/ruoyi/basic/pojo/Customer.java
@@ -134,4 +134,10 @@
    @Schema(description = "是否被分配:0-未分配,1-已分配")
    private Integer isAssigned;
    /**
     * ç»‘定的登录账号id(customer_user ä¸€å¯¹ä¸€ç»‘定,非客户表字段)
     */
    @TableField(exist = false)
    private Long bindUserId;
}
src/main/java/com/ruoyi/basic/pojo/CustomerFinancialReport.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * å®¢æˆ·è¿‘3年财务报表
 *
 * @author ruoyi
 * @date 2026-10-29
 */
@TableName("customer_financial_report")
@Data
public class CustomerFinancialReport implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * å®¢æˆ·id
     */
    private Long customerId;
    /**
     * æŠ¥è¡¨å¹´åº¦
     */
    private Integer reportYear;
    /**
     * æŠ¥è¡¨åç§°
     */
    private String reportName;
    /**
     * æŠ¥è¡¨é™„件(JSON数组字符串)
     */
    private String reportFiles;
    /**
     * å®¡æ‰¹çŠ¶æ€ï¼š0待审批 1已通过 2已退回
     */
    private Integer auditStatus;
    /**
     * å®¡æ‰¹äººid
     */
    private Long auditUserId;
    /**
     * å®¡æ‰¹äºº
     */
    private String auditUserName;
    /**
     * å®¡æ‰¹æ—¶é—´
     */
    private LocalDateTime auditTime;
    /**
     * å®¡æ‰¹æ„è§/退回原因
     */
    private String auditRemark;
    /**
     * ä¸Šä¼ äººid
     */
    private Long uploadUserId;
    /**
     * ä¸Šä¼ äºº
     */
    private String uploadUserName;
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @Schema(description = "客户名称")
    @TableField(exist = false)
    private String customerName;
}
src/main/java/com/ruoyi/basic/service/CustomerFinancialReportService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,264 @@
package com.ruoyi.basic.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.mapper.CustomerFinancialReportMapper;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.CustomerUserMapper;
import com.ruoyi.basic.mapper.StorageBlobMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.CustomerFinancialReport;
import com.ruoyi.basic.pojo.CustomerUser;
import com.ruoyi.basic.pojo.StorageBlob;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.LoginUser;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.Year;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * å®¢æˆ·è¿‘3年财务报表Service
 */
@Service
@RequiredArgsConstructor
public class CustomerFinancialReportService extends ServiceImpl<CustomerFinancialReportMapper, CustomerFinancialReport> {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private final CustomerMapper customerMapper;
    private final CustomerUserMapper customerUserMapper;
    private final StorageBlobMapper storageBlobMapper;
    private final FileUtil fileUtil;
    /**
     * åŽå°æŒ‰å®¢æˆ·æŸ¥è¯¢è¿‘3年财务报表
     */
    public List<CustomerFinancialReport> listByCustomer(Long customerId) {
        if (customerId == null) {
            throw new ServiceException("请选择客户");
        }
        List<CustomerFinancialReport> reports = list(new LambdaQueryWrapper<CustomerFinancialReport>()
                .eq(CustomerFinancialReport::getCustomerId, customerId)
                .orderByDesc(CustomerFinancialReport::getReportYear));
        refreshFileUrls(reports);
        fillCustomerName(reports);
        return reports;
    }
    /**
     * å®¢æˆ·è´¦å·æŸ¥è¯¢è‡ªå·±çš„è¿‘3年财务报表
     */
    public List<CustomerFinancialReport> listMine() {
        Long customerId = currentCustomerId();
        List<CustomerFinancialReport> reports = list(new LambdaQueryWrapper<CustomerFinancialReport>()
                .eq(CustomerFinancialReport::getCustomerId, customerId)
                .orderByDesc(CustomerFinancialReport::getReportYear));
        refreshFileUrls(reports);
        fillCustomerName(reports);
        return reports;
    }
    /**
     * ä¿å­˜è´¢åŠ¡æŠ¥è¡¨ï¼šåŒä¸€å®¢æˆ·åŒä¸€å¹´åº¦ä»…ä¿ç•™ä¸€æ¡ï¼Œå¯åå¤è¦†ç›–ä¿®æ”¹
     */
    @Transactional(rollbackFor = Exception.class)
    public CustomerFinancialReport saveReport(CustomerFinancialReport input) {
        if (input.getReportYear() == null) {
            throw new ServiceException("请选择报表年度");
        }
        int currentYear = Year.now().getValue();
        if (input.getReportYear() < currentYear - 2 || input.getReportYear() > currentYear) {
            throw new ServiceException("只能上传最近三个年度的财务报表");
        }
        if (StringUtils.isBlank(input.getReportFiles())) {
            throw new ServiceException("请上传财务报表附件");
        }
        Long customerId;
        if (isCustomerAccount()) {
            customerId = currentCustomerId();
        } else {
            customerId = input.getCustomerId();
            if (customerId == null || customerMapper.selectById(customerId) == null) {
                throw new ServiceException("客户不存在");
            }
        }
        CustomerFinancialReport entity = getOne(new LambdaQueryWrapper<CustomerFinancialReport>()
                .eq(CustomerFinancialReport::getCustomerId, customerId)
                .eq(CustomerFinancialReport::getReportYear, input.getReportYear()), false);
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (entity == null) {
            entity = new CustomerFinancialReport();
            entity.setCustomerId(customerId);
            entity.setReportYear(input.getReportYear());
        }
        entity.setReportName(input.getReportName());
        entity.setReportFiles(input.getReportFiles());
        // é‡æ–°æäº¤åŽå›žåˆ°å¾…审批状态,等待内部人员重新审批
        entity.setAuditStatus(0);
        entity.setAuditUserId(null);
        entity.setAuditUserName(null);
        entity.setAuditTime(null);
        entity.setAuditRemark(null);
        entity.setUploadUserId(loginUser.getUserId());
        entity.setUploadUserName(loginUser.getNickName());
        saveOrUpdate(entity);
        return entity;
    }
    /**
     * ç”³æŠ¥å®¡æ‰¹åˆ—表:内部人员查看所有客户的提交情况
     */
    public List<CustomerFinancialReport> auditList(String customerName, Integer reportYear, Integer auditStatus) {
        LambdaQueryWrapper<CustomerFinancialReport> wrapper = new LambdaQueryWrapper<CustomerFinancialReport>()
                .eq(reportYear != null, CustomerFinancialReport::getReportYear, reportYear)
                .eq(auditStatus != null, CustomerFinancialReport::getAuditStatus, auditStatus)
                .orderByDesc(CustomerFinancialReport::getUpdateTime);
        if (StringUtils.isNotBlank(customerName)) {
            List<Long> customerIds = customerMapper.selectList(new LambdaQueryWrapper<Customer>()
                            .like(Customer::getCustomerName, customerName))
                    .stream().map(Customer::getId).filter(Objects::nonNull).collect(Collectors.toList());
            if (customerIds.isEmpty()) {
                return new java.util.ArrayList<>();
            }
            wrapper.in(CustomerFinancialReport::getCustomerId, customerIds);
        }
        List<CustomerFinancialReport> reports = list(wrapper);
        refreshFileUrls(reports);
        fillCustomerName(reports);
        return reports;
    }
    /**
     * å®¡æ‰¹è´¢åŠ¡æŠ¥è¡¨ï¼š1通过 2退回
     */
    @Transactional(rollbackFor = Exception.class)
    public CustomerFinancialReport auditReport(Long id, Integer auditStatus, String auditRemark) {
        if (isCustomerAccount()) {
            throw new ServiceException("客户账号无权审批财务报表");
        }
        if (id == null) {
            throw new ServiceException("请选择申报记录");
        }
        if (auditStatus == null || auditStatus != 1 && auditStatus != 2) {
            throw new ServiceException("审批结果不正确");
        }
        CustomerFinancialReport entity = getById(id);
        if (entity == null) {
            throw new ServiceException("申报记录不存在");
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        entity.setAuditStatus(auditStatus);
        entity.setAuditUserId(loginUser.getUserId());
        entity.setAuditUserName(loginUser.getNickName());
        entity.setAuditTime(LocalDateTime.now());
        entity.setAuditRemark(auditRemark);
        updateById(entity);
        return entity;
    }
    /**
     * åˆ é™¤è´¢åŠ¡æŠ¥è¡¨ï¼šä»…åŽå°äººå‘˜å¯åˆ é™¤ï¼Œå®¢æˆ·è´¦å·ä¸å…è®¸åˆ é™¤
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteReport(Long id) {
        if (isCustomerAccount()) {
            throw new ServiceException("客户账号无权删除财务报表");
        }
        CustomerFinancialReport entity = getById(id);
        if (entity == null) {
            throw new ServiceException("财务报表不存在");
        }
        removeById(id);
    }
    /**
     * é‡æ–°ç”Ÿæˆé™„件预览/下载地址:数据库中保存的是上传时的带签名地址,会过期,读取时按文件id刷新
     */
    private void refreshFileUrls(List<CustomerFinancialReport> reports) {
        if (reports == null || reports.isEmpty()) {
            return;
        }
        for (CustomerFinancialReport report : reports) {
            String files = report.getReportFiles();
            if (StringUtils.isBlank(files)) {
                continue;
            }
            try {
                JsonNode root = OBJECT_MAPPER.readTree(files);
                if (root == null || !root.isArray()) {
                    continue;
                }
                for (JsonNode node : root) {
                    Long blobId = node.hasNonNull("id") ? node.get("id").asLong() : null;
                    if (blobId == null) {
                        continue;
                    }
                    StorageBlob blob = storageBlobMapper.selectById(blobId);
                    if (blob == null) {
                        continue;
                    }
                    StorageBlobVO vo = new StorageBlobVO();
                    vo.setUidFilename(blob.getUidFilename());
                    vo.setPath(blob.getPath());
                    vo.setResourceKey(blob.getResourceKey());
                    ObjectNode objectNode = (ObjectNode) node;
                    objectNode.put("url", fileUtil.buildSignedPreviewUrl(vo));
                    objectNode.put("downloadURL", fileUtil.buildSignedDownloadUrl(vo));
                    if (!node.hasNonNull("name")) {
                        objectNode.put("name", blob.getOriginalFilename());
                    }
                }
                report.setReportFiles(OBJECT_MAPPER.writeValueAsString(root));
            } catch (Exception e) {
                // æ—§æ•°æ®æˆ–非 JSON æ ¼å¼ï¼Œä¿æŒåŽŸå€¼
            }
        }
    }
    private boolean isCustomerAccount() {
        Long userId = SecurityUtils.getUserId();
        return !SecurityUtils.isAdmin(userId) && SecurityUtils.hasRole("customer");
    }
    /**
     * èŽ·å–å½“å‰ç™»å½•è´¦å·ç»‘å®šçš„å®¢æˆ·id
     */
    private Long currentCustomerId() {
        CustomerUser binding = customerUserMapper.selectOne(new LambdaQueryWrapper<CustomerUser>()
                .eq(CustomerUser::getUserId, SecurityUtils.getUserId()).last("limit 1"));
        if (binding == null || binding.getCustomerId() == null) {
            throw new ServiceException("当前账号尚未绑定客户,请联系管理员");
        }
        return binding.getCustomerId();
    }
    private void fillCustomerName(List<CustomerFinancialReport> reports) {
        if (reports == null || reports.isEmpty()) {
            return;
        }
        Set<Long> ids = reports.stream().map(CustomerFinancialReport::getCustomerId)
                .filter(Objects::nonNull).collect(Collectors.toSet());
        if (ids.isEmpty()) {
            return;
        }
        List<Customer> customers = customerMapper.selectBatchIds(ids);
        java.util.Map<Long, String> nameMap = customers.stream()
                .collect(Collectors.toMap(Customer::getId, Customer::getCustomerName, (a, b) -> a));
        reports.forEach(item -> item.setCustomerName(nameMap.getOrDefault(item.getCustomerId(), "")));
    }
}
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -33,6 +33,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@@ -70,6 +71,8 @@
    private CustomerReturnVisitService customerReturnVisitService;
    @Autowired
    private CustomerUserService customerUserService;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    /**
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆ
@@ -92,6 +95,13 @@
    public CustomerVo selectCustomerDetailById(Long id) {
        CustomerVo customerVo = new CustomerVo();
        BeanUtils.copyProperties(this.getById(id), customerVo);
        // å›žæ˜¾ç»‘定的登录账号
        CustomerUser binding = customerUserService.getOne(new LambdaQueryWrapper<CustomerUser>()
                .eq(CustomerUser::getCustomerId, id).last("limit 1"), false);
        if (binding != null) {
            customerVo.setBindUserId(binding.getUserId());
        }
        // æŸ¥è¯¢è·Ÿè¿›è®°å½•
        List<CustomerFollowUp> followUpList = customerFollowUpService.list(
@@ -196,11 +206,14 @@
     * @return ç»“æžœ
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertCustomer(Customer customer) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Long tenantId = loginUser.getTenantId();
        customer.setTenantId(tenantId);
        return customerMapper.insert(customer);
        int rows = customerMapper.insert(customer);
        saveCustomerBinding(customer.getId(), customer.getBindUserId());
        return rows;
    }
    /**
@@ -210,12 +223,38 @@
     * @return ç»“æžœ
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateCustomer(Customer customer) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Long tenantId = loginUser.getTenantId();
        validateCustomerEditPermission(customer.getId());
        customer.setTenantId(tenantId);
        return customerMapper.updateById(customer);
        int rows = customerMapper.updateById(customer);
        saveCustomerBinding(customer.getId(), customer.getBindUserId());
        return rows;
    }
    /**
     * ä¿å­˜å®¢æˆ·ä¸Žç™»å½•账号的一对一绑定关系
     */
    private void saveCustomerBinding(Long customerId, Long bindUserId) {
        if (customerId == null) {
            return;
        }
        customerUserService.remove(new LambdaQueryWrapper<CustomerUser>()
                .eq(CustomerUser::getCustomerId, customerId));
        if (bindUserId == null) {
            return;
        }
        // ä¸€ä¸ªç™»å½•账号只能绑定一个客户
        customerUserService.remove(new LambdaQueryWrapper<CustomerUser>()
                .eq(CustomerUser::getUserId, bindUserId));
        CustomerUser binding = new CustomerUser();
        binding.setCustomerId(customerId);
        binding.setUserId(bindUserId);
        customerUserService.save(binding);
        // ç»‘定账号自动授予“客户”角色,使其登录后只能访问客户门户
        jdbcTemplate.update("INSERT IGNORE INTO sys_user_role (user_id, role_id) "
                + "SELECT ?, role_id FROM sys_role WHERE role_key = 'customer' AND del_flag = '0'", bindUserId);
    }
    /**
@@ -418,22 +457,6 @@
        customer.setType(1);
        customer.setIsAssigned(0);
        return this.updateById(customer);
    }
    private void validateCustomerEditPermission(Long customerId) {
        Customer customer = customerMapper.selectById(customerId);
        if (customer == null) {
            throw new RuntimeException("客户档案不存在");
        }
        if (!hasSalesModuleReference(customerId)) {
            return;
        }
        if (Objects.equals(customer.getType(), 0)) {
            throw new RuntimeException("已被销售模块引用的私海客户不能编辑");
        }
        if (Objects.equals(customer.getType(), 1) && Objects.equals(customer.getIsAssigned(), 1)) {
            throw new RuntimeException("已领用并且被销售模块引用的公海客户不能编辑");
        }
    }
    private void validatePublicSeaRecyclePermission(Long customerId) {
src/main/java/com/ruoyi/customerPortal/controller/CustomerSubmissionController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package com.ruoyi.customerPortal.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.customerPortal.dto.ReviewSubmissionDto;
import com.ruoyi.customerPortal.pojo.CustomerSubmission;
import com.ruoyi.customerPortal.service.CustomerSubmissionService;
import com.ruoyi.framework.web.domain.AjaxResult;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/customer/submission")
@RequiredArgsConstructor
public class CustomerSubmissionController {
    private final CustomerSubmissionService customerSubmissionService;
    @GetMapping("/mine")
    @PreAuthorize("@ss.hasPermi('customer:submission:mine')")
    public AjaxResult mine(Page<CustomerSubmission> page, Integer reportYear, Integer status) {
        return AjaxResult.success(customerSubmissionService.listMine(page, reportYear, status));
    }
    @PostMapping
    @PreAuthorize("@ss.hasPermi('customer:submission:mine')")
    public AjaxResult submit(@RequestBody CustomerSubmission submission) {
        return AjaxResult.success(customerSubmissionService.submit(submission));
    }
    @GetMapping("/manage/list")
    @PreAuthorize("@ss.hasPermi('customer:submission:manage')")
    public AjaxResult manageList(Page<CustomerSubmission> page, Long customerId,
                                 Integer reportYear, Integer status) {
        return AjaxResult.success(customerSubmissionService.listManage(page, customerId, reportYear, status));
    }
    @PutMapping("/review")
    @PreAuthorize("@ss.hasPermi('customer:submission:manage')")
    public AjaxResult review(@RequestBody ReviewSubmissionDto dto) {
        customerSubmissionService.review(dto);
        return AjaxResult.success();
    }
}
src/main/java/com/ruoyi/customerPortal/dto/ReviewSubmissionDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.customerPortal.dto;
import lombok.Data;
@Data
public class ReviewSubmissionDto {
    private Long id;
    /** 1处理中 2已发布 3已退回 */
    private Integer status;
    private String resultText;
    private String resultFiles;
    private String reviewRemark;
}
src/main/java/com/ruoyi/customerPortal/mapper/CustomerSubmissionMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
package com.ruoyi.customerPortal.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.customerPortal.pojo.CustomerSubmission;
public interface CustomerSubmissionMapper extends BaseMapper<CustomerSubmission> {
}
src/main/java/com/ruoyi/customerPortal/pojo/CustomerSubmission.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package com.ruoyi.customerPortal.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/** å®¢æˆ·å¹´åº¦ç”³æŠ¥ææ–™åŠå‘布结果。 */
@Data
@TableName("customer_submission")
public class CustomerSubmission {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long customerId;
    private Integer reportYear;
    private String reportName;
    private String materialFiles;
    /** 0待审核 1处理中 2已发布 3已退回 */
    private Integer status;
    private String resultText;
    private String resultFiles;
    private String reviewRemark;
    private Long submitUserId;
    private String submitUserName;
    private Long publishUserId;
    private String publishUserName;
    private LocalDateTime publishTime;
    private Integer isDeleted;
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @TableField(exist = false)
    private String customerName;
}
src/main/java/com/ruoyi/customerPortal/service/CustomerSubmissionService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
package com.ruoyi.customerPortal.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.CustomerUserMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.CustomerUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.customerPortal.dto.ReviewSubmissionDto;
import com.ruoyi.customerPortal.mapper.CustomerSubmissionMapper;
import com.ruoyi.customerPortal.pojo.CustomerSubmission;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.Year;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class CustomerSubmissionService extends ServiceImpl<CustomerSubmissionMapper, CustomerSubmission> {
    private final CustomerUserMapper customerUserMapper;
    private final CustomerMapper customerMapper;
    public Page<CustomerSubmission> listMine(Page<CustomerSubmission> page, Integer reportYear, Integer status) {
        CustomerUser binding = currentBinding();
        LambdaQueryWrapper<CustomerSubmission> query = new LambdaQueryWrapper<CustomerSubmission>()
                .eq(CustomerSubmission::getCustomerId, binding.getCustomerId())
                .eq(CustomerSubmission::getIsDeleted, 0)
                .eq(reportYear != null, CustomerSubmission::getReportYear, reportYear)
                .eq(status != null, CustomerSubmission::getStatus, status)
                .orderByDesc(CustomerSubmission::getReportYear)
                .orderByDesc(CustomerSubmission::getCreateTime);
        return fillCustomerName(page(page, query));
    }
    public Page<CustomerSubmission> listManage(Page<CustomerSubmission> page, Long customerId,
                                                Integer reportYear, Integer status) {
        ensureStaff();
        LambdaQueryWrapper<CustomerSubmission> query = new LambdaQueryWrapper<CustomerSubmission>()
                .eq(CustomerSubmission::getIsDeleted, 0)
                .eq(customerId != null, CustomerSubmission::getCustomerId, customerId)
                .eq(reportYear != null, CustomerSubmission::getReportYear, reportYear)
                .eq(status != null, CustomerSubmission::getStatus, status)
                .orderByAsc(CustomerSubmission::getStatus)
                .orderByDesc(CustomerSubmission::getUpdateTime);
        return fillCustomerName(page(page, query));
    }
    @Transactional(rollbackFor = Exception.class)
    public CustomerSubmission submit(CustomerSubmission input) {
        CustomerUser binding = currentBinding();
        int currentYear = Year.now().getValue();
        if (input.getReportYear() == null || input.getReportYear() < currentYear - 2
                || input.getReportYear() > currentYear) {
            throw new ServiceException("只能提交最近三个年度的财务材料");
        }
        if (input.getMaterialFiles() == null || input.getMaterialFiles().isBlank()) {
            throw new ServiceException("请至少上传一份材料");
        }
        CustomerSubmission entity = getOne(new LambdaQueryWrapper<CustomerSubmission>()
                .eq(CustomerSubmission::getCustomerId, binding.getCustomerId())
                .eq(CustomerSubmission::getReportYear, input.getReportYear())
                .eq(CustomerSubmission::getIsDeleted, 0), false);
        if (entity != null && entity.getStatus() != null
                && entity.getStatus() != 0 && entity.getStatus() != 3) {
            throw new ServiceException("材料已受理或结果已发布,不能再次修改");
        }
        if (entity == null) {
            entity = new CustomerSubmission();
            entity.setCustomerId(binding.getCustomerId());
            entity.setReportYear(input.getReportYear());
            entity.setSubmitUserId(SecurityUtils.getUserId());
            entity.setSubmitUserName(SecurityUtils.getLoginUser().getNickName());
            entity.setIsDeleted(0);
        }
        entity.setReportName(input.getReportName());
        entity.setMaterialFiles(input.getMaterialFiles());
        entity.setStatus(0);
        entity.setReviewRemark(null);
        entity.setResultText(null);
        entity.setResultFiles(null);
        entity.setPublishUserId(null);
        entity.setPublishUserName(null);
        entity.setPublishTime(null);
        saveOrUpdate(entity);
        return entity;
    }
    @Transactional(rollbackFor = Exception.class)
    public void review(ReviewSubmissionDto dto) {
        ensureStaff();
        if (dto.getId() == null || dto.getStatus() == null
                || (dto.getStatus() != 1 && dto.getStatus() != 2 && dto.getStatus() != 3)) {
            throw new ServiceException("审核参数不正确");
        }
        CustomerSubmission entity = getById(dto.getId());
        if (entity == null || Objects.equals(entity.getIsDeleted(), 1)) {
            throw new ServiceException("申报记录不存在");
        }
        Integer oldStatus = entity.getStatus();
        if (dto.getStatus() == 1 && !Objects.equals(oldStatus, 0)) {
            throw new ServiceException("只有待审核材料可以受理");
        }
        if (dto.getStatus() == 2 && !Objects.equals(oldStatus, 1)) {
            throw new ServiceException("只有处理中材料可以发布结果");
        }
        if (dto.getStatus() == 3 && !Objects.equals(oldStatus, 0) && !Objects.equals(oldStatus, 1)) {
            throw new ServiceException("当前状态不能退回");
        }
        if (dto.getStatus() == 3 && (dto.getReviewRemark() == null || dto.getReviewRemark().isBlank())) {
            throw new ServiceException("退回时必须填写原因");
        }
        if (dto.getStatus() == 2 && (dto.getResultText() == null || dto.getResultText().isBlank())
                && (dto.getResultFiles() == null || dto.getResultFiles().isBlank())) {
            throw new ServiceException("发布时请填写结果说明或上传结果文件");
        }
        entity.setStatus(dto.getStatus());
        entity.setResultText(dto.getResultText());
        entity.setResultFiles(dto.getResultFiles());
        entity.setReviewRemark(dto.getReviewRemark());
        if (dto.getStatus() == 2) {
            entity.setPublishUserId(SecurityUtils.getUserId());
            entity.setPublishUserName(SecurityUtils.getLoginUser().getNickName());
            entity.setPublishTime(LocalDateTime.now());
        }
        updateById(entity);
    }
    private CustomerUser currentBinding() {
        CustomerUser binding = customerUserMapper.selectOne(new LambdaQueryWrapper<CustomerUser>()
                .eq(CustomerUser::getUserId, SecurityUtils.getUserId()).last("limit 1"));
        if (binding == null) {
            throw new ServiceException("当前账号尚未绑定客户,请联系管理员");
        }
        return binding;
    }
    private void ensureStaff() {
        if (!SecurityUtils.isAdmin(SecurityUtils.getUserId()) && SecurityUtils.hasRole("customer")) {
            throw new ServiceException("客户账号无权执行此操作");
        }
    }
    private Page<CustomerSubmission> fillCustomerName(Page<CustomerSubmission> result) {
        Set<Long> ids = result.getRecords().stream().map(CustomerSubmission::getCustomerId)
                .filter(Objects::nonNull).collect(Collectors.toSet());
        Map<Long, Customer> customerMap = ids.isEmpty() ? Collections.emptyMap() : customerMapper.selectBatchIds(ids)
                .stream().collect(Collectors.toMap(Customer::getId, Function.identity(), (a, b) -> a));
        result.getRecords().forEach(item -> {
            Customer customer = customerMap.get(item.getCustomerId());
            item.setCustomerName(customer == null ? "" : customer.getCustomerName());
        });
        return result;
    }
}
src/main/java/com/ruoyi/projectManagement/dto/SaveInfoDto.java
@@ -42,6 +42,9 @@
    private String source;
    // é¡¹ç›®åˆ†ç±»
    private String projectClassification;
    private Long managerId;
    private String managerName;
    private Long salesmanId;
src/main/java/com/ruoyi/projectManagement/pojo/Info.java
@@ -74,6 +74,12 @@
    private String source;
    /**
     * é¡¹ç›®åˆ†ç±»
     */
    @TableField(value = "project_classification")
    private String projectClassification;
    /**
     * é¡¹ç›®ç»ç†id
     */
    @TableField(value = "manager_id")
src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerSimpleController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,134 @@
package com.ruoyi.purchase.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.purchase.mapper.PurchaseLedgerSimpleMapper;
import com.ruoyi.purchase.pojo.PurchaseLedgerSimple;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
/**
 * é‡‡è´­å°è´¦ï¼ˆç®€ï¼‰Controller
 * åªåšåŸºç¡€å°è´¦çš„增删改查与导出,不与合同、供应商、产品、入库等模块关联。
 */
@RestController
@RequestMapping("/purchase/simpleLedger")
@Tag(name = "采购台账(简)")
@AllArgsConstructor
public class PurchaseLedgerSimpleController {
    private final PurchaseLedgerSimpleMapper purchaseLedgerSimpleMapper;
    /**
     * åˆ†é¡µåˆ—表
     */
    @GetMapping("/list")
    @Operation(summary = "采购台账(简)分页列表")
    public R list(@RequestParam(defaultValue = "1") long current,
                  @RequestParam(defaultValue = "10") long size,
                  @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate paymentDateStart,
                  @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate paymentDateEnd,
                  @RequestParam(required = false) String paymentMethod) {
        LambdaQueryWrapper<PurchaseLedgerSimple> wrapper = new LambdaQueryWrapper<>();
        if (paymentDateStart != null) {
            wrapper.ge(PurchaseLedgerSimple::getPaymentDate, toDate(paymentDateStart));
        }
        if (paymentDateEnd != null) {
            wrapper.le(PurchaseLedgerSimple::getPaymentDate, toDate(paymentDateEnd));
        }
        if (paymentMethod != null && !paymentMethod.isBlank()) {
            wrapper.eq(PurchaseLedgerSimple::getPaymentMethod, paymentMethod);
        }
        wrapper.orderByDesc(PurchaseLedgerSimple::getPurchaseDate)
                .orderByDesc(PurchaseLedgerSimple::getId);
        return R.ok(purchaseLedgerSimpleMapper.selectPage(new Page<>(current, size), wrapper));
    }
    /**
     * è¯¦æƒ…
     */
    @GetMapping("/{id}")
    @Operation(summary = "采购台账(简)详情")
    public R getById(@PathVariable Long id) {
        return R.ok(purchaseLedgerSimpleMapper.selectById(id));
    }
    /**
     * æ–°å¢žã€ç¼–辑
     */
    @PostMapping("/save")
    @Log(title = "采购台账(简)", businessType = BusinessType.INSERT)
    @Operation(summary = "采购台账(简)新增、编辑")
    public AjaxResult save(@RequestBody PurchaseLedgerSimple entity) {
        Date now = new Date();
        if (entity.getId() == null) {
            entity.setCreateTime(now);
            entity.setUpdateTime(now);
            purchaseLedgerSimpleMapper.insert(entity);
        } else {
            entity.setUpdateTime(now);
            purchaseLedgerSimpleMapper.updateById(entity);
        }
        return AjaxResult.success(entity.getId());
    }
    /**
     * åˆ é™¤
     */
    @DeleteMapping("/delete")
    @Log(title = "采购台账(简)", businessType = BusinessType.DELETE)
    @Operation(summary = "采购台账(简)删除")
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return AjaxResult.error("请选择要删除的数据");
        }
        purchaseLedgerSimpleMapper.deleteBatchIds(ids);
        return AjaxResult.success();
    }
    /**
     * å¯¼å‡º
     */
    @PostMapping("/export")
    @Log(title = "采购台账(简)", businessType = BusinessType.EXPORT)
    @Operation(summary = "采购台账(简)导出")
    public void export(HttpServletResponse response,
                       @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate paymentDateStart,
                       @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate paymentDateEnd,
                       @RequestParam(required = false) String paymentMethod) {
        LambdaQueryWrapper<PurchaseLedgerSimple> wrapper = new LambdaQueryWrapper<>();
        if (paymentDateStart != null) {
            wrapper.ge(PurchaseLedgerSimple::getPaymentDate, toDate(paymentDateStart));
        }
        if (paymentDateEnd != null) {
            wrapper.le(PurchaseLedgerSimple::getPaymentDate, toDate(paymentDateEnd));
        }
        if (paymentMethod != null && !paymentMethod.isBlank()) {
            wrapper.eq(PurchaseLedgerSimple::getPaymentMethod, paymentMethod);
        }
        wrapper.orderByDesc(PurchaseLedgerSimple::getPurchaseDate)
                .orderByDesc(PurchaseLedgerSimple::getId);
        List<PurchaseLedgerSimple> list = purchaseLedgerSimpleMapper.selectList(wrapper);
        ExcelUtil<PurchaseLedgerSimple> util = new ExcelUtil<>(PurchaseLedgerSimple.class);
        util.exportExcel(response, list, "采购台账");
    }
    private Date toDate(LocalDate date) {
        return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }
}
src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerSimpleMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.purchase.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.purchase.pojo.PurchaseLedgerSimple;
import org.apache.ibatis.annotations.Mapper;
/**
 * é‡‡è´­å°è´¦ï¼ˆç®€ï¼‰Mapper
 */
@Mapper
public interface PurchaseLedgerSimpleMapper extends BaseMapper<PurchaseLedgerSimple> {
}
src/main/java/com/ruoyi/purchase/pojo/PurchaseLedgerSimple.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,122 @@
package com.ruoyi.purchase.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
 * é‡‡è´­å°è´¦ï¼ˆç®€ï¼‰å¯¹è±¡ purchase_ledger_simple
 *
 * ä»…保留采购日期、支出日期、金额、采购物品、发票号码、付款方式、备注等基础信息,
 * ä¸ä¸ŽåˆåŒã€ä¾›åº”商、产品、入库等模块关联。
 */
@Getter
@Setter
@TableName("purchase_ledger_simple")
@Schema(name = "PurchaseLedgerSimple对象", description = "采购台账(简)")
public class PurchaseLedgerSimple implements Serializable {
    private static final long serialVersionUID = 1L;
    @Schema(description = "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * æ”¯å‡ºæ—¥æœŸ
     */
    @Schema(description = "支出日期")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @Excel(name = "支出日期", width = 20, dateFormat = "yyyy-MM-dd")
    private Date paymentDate;
    /**
     * é‡‡è´­æ—¥æœŸ
     */
    @Schema(description = "采购日期")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @Excel(name = "采购日期", width = 20, dateFormat = "yyyy-MM-dd")
    private Date purchaseDate;
    /**
     * é‡‘额
     */
    @Schema(description = "金额")
    @Excel(name = "金额")
    private BigDecimal amount;
    /**
     * é‡‡è´­ç‰©å“
     */
    @Schema(description = "采购物品")
    @Excel(name = "采购物品")
    private String purchaseGoods;
    /**
     * å‘票号码
     */
    @Schema(description = "发票号码")
    @Excel(name = "发票号码")
    private String invoiceNumber;
    /**
     * ä»˜æ¬¾æ–¹å¼
     */
    @Schema(description = "付款方式")
    @Excel(name = "付款方式")
    private String paymentMethod;
    /**
     * å¤‡æ³¨
     */
    @Schema(description = "备注")
    @Excel(name = "备注")
    private String remark;
    /**
     * å½•入人id
     */
    @Schema(description = "录入人id")
    private Long recorderId;
    /**
     * å½•入人
     */
    @Schema(description = "录入人")
    @Excel(name = "录入人")
    private String recorderName;
    /**
     * å½•入日期
     */
    @Schema(description = "录入日期")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    @Excel(name = "录入日期", width = 20, dateFormat = "yyyy-MM-dd")
    private Date entryDate;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @Schema(description = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
    @Schema(description = "更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;
}
src/main/java/com/ruoyi/sales/controller/BusinessApprovalController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package com.ruoyi.sales.controller;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.dto.BusinessReviewDto;
import com.ruoyi.sales.service.BusinessApprovalService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/business/approval")
@RequiredArgsConstructor
public class BusinessApprovalController {
    private final BusinessApprovalService businessApprovalService;
    @PostMapping("/submit/{id}")
    @PreAuthorize("@ss.hasPermi('business:approval:submit')")
    public AjaxResult submit(@PathVariable Long id) {
        businessApprovalService.submit(id);
        return AjaxResult.success();
    }
    @PutMapping("/review")
    @PreAuthorize("@ss.hasPermi('business:approval:review')")
    public AjaxResult review(@RequestBody BusinessReviewDto dto) {
        return AjaxResult.success(businessApprovalService.review(dto));
    }
}
src/main/java/com/ruoyi/sales/dto/BusinessReviewDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
package com.ruoyi.sales.dto;
import lombok.Data;
@Data
public class BusinessReviewDto {
    private Long salesLedgerId;
    private Boolean approved;
    private String remark;
    private Long planId;
}
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
@@ -9,6 +9,7 @@
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
/**
@@ -139,6 +140,15 @@
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /** ä¸šåŠ¡å®¡æ‰¹ï¼š0草稿 1待审批 2已通过 3已退回 */
    private Integer businessApprovalStatus;
    private String businessApprovalRemark;
    private Long businessApprovedBy;
    private String businessApprovedName;
    private LocalDateTime businessApprovedTime;
    private LocalDateTime businessSubmittedTime;
    private Long projectManagementInfoId;
    @Schema(description = "是否有生产记录")
    @TableField(exist = false)
    private Boolean hasProductionRecord;
src/main/java/com/ruoyi/sales/service/BusinessApprovalService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,124 @@
package com.ruoyi.sales.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.enums.PlanStageEnum;
import com.ruoyi.common.enums.ReviewStatusEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.projectManagement.dto.PlanStageDto;
import com.ruoyi.projectManagement.mapper.InfoMapper;
import com.ruoyi.projectManagement.mapper.PlanMapper;
import com.ruoyi.projectManagement.mapper.PlanNodeMapper;
import com.ruoyi.projectManagement.pojo.Info;
import com.ruoyi.projectManagement.pojo.Plan;
import com.ruoyi.projectManagement.pojo.PlanNode;
import com.ruoyi.sales.dto.BusinessReviewDto;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Service
@RequiredArgsConstructor
public class BusinessApprovalService {
    private final SalesLedgerMapper salesLedgerMapper;
    private final InfoMapper infoMapper;
    private final PlanMapper planMapper;
    private final PlanNodeMapper planNodeMapper;
    @Transactional(rollbackFor = Exception.class)
    public void submit(Long id) {
        SalesLedger ledger = requireLedger(id);
        Integer status = ledger.getBusinessApprovalStatus();
        if (status != null && status == 2) {
            throw new ServiceException("该业务已经审批通过");
        }
        ledger.setBusinessApprovalStatus(1);
        ledger.setBusinessApprovalRemark(null);
        ledger.setBusinessSubmittedTime(LocalDateTime.now());
        salesLedgerMapper.updateById(ledger);
    }
    @Transactional(rollbackFor = Exception.class)
    public Long review(BusinessReviewDto dto) {
        if (dto.getSalesLedgerId() == null || dto.getApproved() == null) {
            throw new ServiceException("审批参数不完整");
        }
        SalesLedger ledger = requireLedger(dto.getSalesLedgerId());
        if (ledger.getBusinessApprovalStatus() == null || ledger.getBusinessApprovalStatus() != 1) {
            throw new ServiceException("只有待审批业务可以审核");
        }
        ledger.setBusinessApprovedBy(SecurityUtils.getUserId());
        ledger.setBusinessApprovedName(SecurityUtils.getLoginUser().getNickName());
        ledger.setBusinessApprovedTime(LocalDateTime.now());
        ledger.setBusinessApprovalRemark(dto.getRemark());
        if (!dto.getApproved()) {
            ledger.setBusinessApprovalStatus(3);
            salesLedgerMapper.updateById(ledger);
            return null;
        }
        Long projectId = ledger.getProjectManagementInfoId();
        if (projectId == null) {
            projectId = createProject(ledger, dto.getPlanId());
            ledger.setProjectManagementInfoId(projectId);
        }
        ledger.setBusinessApprovalStatus(2);
        salesLedgerMapper.updateById(ledger);
        return projectId;
    }
    private Long createProject(SalesLedger ledger, Long selectedPlanId) {
        Plan plan = selectedPlanId == null ? planMapper.selectOne(new LambdaQueryWrapper<Plan>()
                .eq(Plan::getName, "项目申报六阶段")
                .eq(Plan::getIsDelete, 0).last("limit 1")) : planMapper.selectById(selectedPlanId);
        if (plan == null) {
            throw new ServiceException("未找到“项目申报六阶段”计划,请先执行升级SQL");
        }
        List<PlanNode> nodes = planNodeMapper.selectList(new LambdaQueryWrapper<PlanNode>()
                .eq(PlanNode::getProjectManagementPlanId, plan.getId())
                .eq(PlanNode::getIsDelete, 0).orderByAsc(PlanNode::getSort));
        if (nodes.isEmpty()) {
            throw new ServiceException("项目六阶段尚未配置");
        }
        Info info = new Info();
        info.setNo("XM" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE)
                + String.format("%06d", ledger.getId()));
        info.setTitle(ledger.getProjectName() == null || ledger.getProjectName().isBlank()
                ? ledger.getCustomerName() + "申报项目" : ledger.getProjectName());
        info.setClientId(ledger.getCustomerId());
        info.setClientName(ledger.getCustomerName());
        info.setEstablishTime(LocalDate.now());
        info.setProjectManagementPlanId(plan.getId());
        info.setSource("业务审批");
        info.setManagerId(SecurityUtils.getUserId());
        info.setManagerName(SecurityUtils.getLoginUser().getNickName());
        info.setSalesmanName(ledger.getSalesman());
        info.setIsDelete(0);
        info.setPlanStartTime(LocalDate.now());
        info.setReviewStatus(ReviewStatusEnum.APPROVED.getCode());
        info.setStatus(PlanStageEnum.TO_BEGIN.getCode());
        info.setOrderDate(ledger.getExecutionDate());
        info.setOrderAmount(ledger.getContractAmount() == null ? null : ledger.getContractAmount().toPlainString());
        info.setRemark(ledger.getRemarks());
        info.setAttachment(ledger.getAttachmentMaterials());
        info.setDeptId(ledger.getDeptId());
        info.setPlanStage(nodes.stream().map(node -> new PlanStageDto(node.getId(), node.getName(),
                PlanStageEnum.TO_BEGIN, node.getSort(), node.getEstimatedDuration())).toList());
        infoMapper.insert(info);
        return info.getId();
    }
    private SalesLedger requireLedger(Long id) {
        SalesLedger ledger = salesLedgerMapper.selectById(id);
        if (ledger == null) {
            throw new ServiceException("业务记录不存在");
        }
        return ledger;
    }
}
src/main/resources/application-dev.yml
@@ -74,7 +74,7 @@
    druid:
      # ä¸»åº“数据源
      master:
        url: jdbc:mysql://36.213.90.123:11101/product-inventory-management-thzs?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        url: jdbc:mysql://36.213.150.237:10000/product-inventory-management-xmgl?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=GMT%2B8
        username: root
        password: xd@123456..
      # ä»Žåº“数据源
@@ -260,7 +260,7 @@
  upload-dir: D:/ruoyi/prod/uploads # æ­£å¼ç›®å½• åŽæœŸåˆ é™¤
  path: D:/ruoyi/prod/uploads # ä¸Šä¼ ç›®å½•
  urlPrefix: /common # é“¾æŽ¥å‰ç¼€
  domain: http://127.0.0.1:7006 # åŸŸåå‰ç¼€
  domain: http://127.0.0.1:7005 # åŸŸåå‰ç¼€
  expired: 120 # è¿‡æœŸæ—¶é—´(单位:分钟)
  useLimit: 10 # ä½¿ç”¨æ¬¡æ•°
  compress: true # æ˜¯å¦åŽ‹ç¼©
src/main/resources/mapper/projectManagement/InfoMapper.xml
@@ -14,6 +14,7 @@
            <result property="establishTime" column="establish_time" jdbcType="TIMESTAMP"/>
            <result property="projectManagementPlanId" column="project_management_plan_id" jdbcType="BIGINT"/>
            <result property="source" column="source" jdbcType="VARCHAR"/>
            <result property="projectClassification" column="project_classification" jdbcType="VARCHAR"/>
            <result property="managerId" column="manager_id" jdbcType="BIGINT"/>
            <result property="managerName" column="manager_name" jdbcType="VARCHAR"/>
            <result property="salesmanId" column="salesman_id" jdbcType="BIGINT"/>
src/main/resources/mapper/sales/SalesLedgerMapper.xml
@@ -26,6 +26,13 @@
        T1.entry_person,
        T1.remarks,
        T1.attachment_materials,
        T1.business_approval_status,
        T1.business_approval_remark,
        T1.business_approved_by,
        T1.business_approved_name,
        T1.business_approved_time,
        T1.business_submitted_time,
        T1.project_management_info_id,
        T1.tenant_id,
        T1.contract_amount,
        T1.execution_date,
@@ -54,6 +61,13 @@
        T1.entry_person,
        T1.remarks,
        T1.attachment_materials,
        T1.business_approval_status,
        T1.business_approval_remark,
        T1.business_approved_by,
        T1.business_approved_name,
        T1.business_approved_time,
        T1.business_submitted_time,
        T1.project_management_info_id,
        T1.tenant_id,
        T1.contract_amount,
        T1.net_contract_amount,