7 天以前 91038896d296db86b5dc166777dcdbf8ff2ea56f
Merge remote-tracking branch 'origin/dev_New_pro' into dev_New_pro
已添加6个文件
已修改21个文件
已删除14个文件
1909 ■■■■■ 文件已修改
doc/create_table_customer_user.sql 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerController.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerFollowUpController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerPrivateController.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerPrivatePoolController.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/CustomerDto.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/CustomerPrivateDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/CustomerPrivatePoolDto.java 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerPrivateMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerPrivatePoolMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerUserMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/Customer.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerFollowUp.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerPrivate.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerPrivatePool.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerReturnVisit.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerUser.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/CustomerPrivatePoolService.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/CustomerPrivateService.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/CustomerUserService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/ICustomerService.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerFollowUpServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerPrivatePoolServiceImpl.java 225 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerPrivateServiceImpl.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerReturnVisitServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java 211 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerUserServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/task/ReturnVisitReminderTask.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/vo/CustomerVo.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 194 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerMapper.xml 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerPrivateMapper.xml 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerPrivatePoolMapper.xml 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/create_table_customer_user.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
drop table if exists customer_user;
create table customer_user
(
    id          bigint auto_increment
        primary key,
    customer_id bigint not null default 0 comment '客户id',
    user_id     bigint not null default 0 comment '用户id',
    create_time datetime null comment '录入时间',
    tenant_id   bigint not null default 0 comment '租户id'
);
src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java
@@ -707,6 +707,7 @@
            case 6 -> "报价审批";
            case 7 -> "发货审批";
            case 8 -> "危险作业审批";
            case 9 -> "办公用品审批";
            default -> "类型" + type;
        };
    }
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -301,6 +301,8 @@
                return "发货审批";
            case 8:
                return "危险作业审批";
            case 9:
                return "办公用品审批";
        }
        return null;
    }
@@ -317,12 +319,12 @@
        qualityInspect.setUnit(saleProduct.getUnit());
        qualityInspect.setQuantity(saleProduct.getQuantity());
        qualityInspectMapper.insert(qualityInspect);
        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(saleProduct.getProductId(), 0,null);
        if (qualityTestStandard.size()>0){
        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(saleProduct.getProductId(), 0, null);
        if (qualityTestStandard.size() > 0) {
            qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
            qualityInspectMapper.updateById(qualityInspect);
            qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                            .eq(QualityTestStandardParam::getTestStandardId,qualityTestStandard.get(0).getId()))
                            .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))
                    .forEach(qualityTestStandardParam -> {
                        QualityInspectParam param = new QualityInspectParam();
                        com.ruoyi.common.utils.bean.BeanUtils.copyProperties(qualityTestStandardParam, param);
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -449,6 +449,8 @@
                return "发货审批";
            case 8:
                return "危险作业审批";
            case 9:
                return "办公用品审批";
        }
        return null;
    }
src/main/java/com/ruoyi/basic/controller/CustomerController.java
@@ -5,17 +5,17 @@
import com.ruoyi.basic.dto.CustomerDto;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.service.ICustomerService;
import com.ruoyi.basic.vo.CustomerVo;
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.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.domain.R;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
@@ -35,7 +35,7 @@
     */
    @GetMapping("/list")
    public R list(Page<CustomerDto> page, CustomerDto customer) {
        IPage<CustomerDto> customerDtoIPage = customerService.selectCustomerList(page, customer);
        IPage<CustomerVo> customerDtoIPage = customerService.selectCustomerList(page, customer);
        return R.ok(customerDtoIPage);
    }
@@ -44,16 +44,9 @@
     */
    @Log(title = "客户档案", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, Customer customer) {
        Long[] ids = customer.getIds();
        List<Customer> list;
        if (ids != null && ids.length > 0) {
            list = customerService.selectCustomerListByIds(ids);
        } else {
            list = customerService.selectCustomerLists(customer);
        }
        ExcelUtil<Customer> util = new ExcelUtil<Customer>(Customer.class);
        util.exportExcel(response, list, "客户档案数据");
    public void export(HttpServletResponse response, CustomerDto customer) {
        ExcelUtil<CustomerVo> util = new ExcelUtil<CustomerVo>(CustomerVo.class);
        util.exportExcel(response, customerService.selectCustomerLists(customer), "客户档案数据");
    }
    @PostMapping("/downloadTemplate")
@@ -69,17 +62,17 @@
     */
    @Log(title = "客户档案", businessType = BusinessType.IMPORT)
    @PostMapping("/importData")
    public AjaxResult importData(MultipartFile file) throws Exception {
    public R importData(MultipartFile file, Integer type) throws Exception {
        return customerService.importData(file);
        return customerService.importData(file, type);
    }
    /**
     * èŽ·å–å®¢æˆ·æ¡£æ¡ˆè¯¦ç»†ä¿¡æ¯
     */
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Long id) {
        return success(customerService.selectCustomerDetailById(id));
    public R getInfo(@PathVariable("id") Long id) {
        return R.ok(customerService.selectCustomerDetailById(id));
    }
    /**
@@ -87,8 +80,8 @@
     */
    @Log(title = "客户档案", businessType = BusinessType.INSERT)
    @PostMapping("/addCustomer")
    public AjaxResult add(@RequestBody Customer customer) {
        return toAjax(customerService.insertCustomer(customer));
    public R add(@RequestBody Customer customer) {
        return R.ok(customerService.insertCustomer(customer));
    }
    /**
@@ -96,8 +89,8 @@
     */
    @Log(title = "客户档案", businessType = BusinessType.UPDATE)
    @PostMapping("/updateCustomer")
    public AjaxResult edit(@RequestBody Customer customer) {
        return toAjax(customerService.updateCustomer(customer));
    public R edit(@RequestBody Customer customer) {
        return R.ok(customerService.updateCustomer(customer));
    }
    /**
@@ -105,11 +98,11 @@
     */
    @Log(title = "客户档案", businessType = BusinessType.DELETE)
    @DeleteMapping("/delCustomer")
    public AjaxResult remove(@RequestBody Long[] ids) {
    public R remove(@RequestBody Long[] ids) {
        if (ids == null || ids.length == 0) {
            return AjaxResult.error("请传入要删除的ID");
            return R.fail("请传入要删除的ID");
        }
        return toAjax(customerService.deleteCustomerByIds(ids));
        return R.ok(customerService.deleteCustomerByIds(ids));
    }
    /**
@@ -121,4 +114,42 @@
    }
    /**
     * åˆ†é…å®¢æˆ·
     */
    @Log(title = "客户档案", businessType = BusinessType.OTHER)
    @PostMapping("/assignCustomer")
    public R assignCustomer(@RequestBody CustomerDto customer) {
        customerService.assignCustomer(customer);
        return R.ok();
    }
    /**
     * å›žæ”¶å®¢æˆ·
     */
    @Log(title = "客户档案", businessType = BusinessType.OTHER)
    @PostMapping("/recycleCustomer")
    public R recycleCustomer(@RequestBody CustomerDto customer) {
        customerService.recycleCustomer(customer);
        return R.ok();
    }
    /**
     * å…±äº«å®¢æˆ·
     */
    @Log(title = "客户档案", businessType = BusinessType.OTHER)
    @PostMapping("/together")
    public R together(@RequestBody CustomerDto customer) {
        customerService.together(customer);
        return R.ok();
    }
    /**
     * ç§æµ·å®¢æˆ·æµå›žå…¬æµ·
     */
    @Log(title = "客户档案", businessType = BusinessType.OTHER)
    @PostMapping("/back")
    public R back(Long id) {
        return R.ok(customerService.back(id));
    }
}
src/main/java/com/ruoyi/basic/controller/CustomerFollowUpController.java
@@ -44,7 +44,7 @@
    @Operation(summary = "查询客户跟进列表")
    public IPage<CustomerFollowUp> list(Page<CustomerFollowUp> page, CustomerFollowUp customerFollowUp) {
        LambdaQueryWrapper<CustomerFollowUp> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(customerFollowUp.getCustomerPrivatePoolId() != null, CustomerFollowUp::getCustomerPrivatePoolId, customerFollowUp.getCustomerPrivatePoolId())
        queryWrapper.eq(customerFollowUp.getCustomerId() != null, CustomerFollowUp::getCustomerId, customerFollowUp.getCustomerId())
                .like(customerFollowUp.getFollowerUserName() != null, CustomerFollowUp::getFollowerUserName, customerFollowUp.getFollowerUserName())
                .orderByDesc(CustomerFollowUp::getFollowUpTime);
        return customerFollowUpService.page(page, queryWrapper);
src/main/java/com/ruoyi/basic/controller/CustomerPrivateController.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/controller/CustomerPrivatePoolController.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/dto/CustomerDto.java
@@ -25,4 +25,8 @@
    private String togetherUserNames;
}
    /**
     * å…±äº«ç”¨æˆ·ID列表
     */
    private List<Long> userIds;
}
src/main/java/com/ruoyi/basic/dto/CustomerPrivateDto.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/dto/CustomerPrivatePoolDto.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java
@@ -5,6 +5,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.CustomerDto;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.vo.CustomerVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -67,5 +68,7 @@
     */
    int deleteCustomerByIds(Long[] ids);
    IPage<CustomerDto> listPage(Page<CustomerDto> page, @Param("c") CustomerDto customer);
}
    IPage<CustomerVo> listPage(Page<CustomerDto> page, @Param("c") CustomerDto customer, @Param("loginUserId") Long loginUserId);
    List<CustomerVo> list(@Param("c") CustomerDto customer, @Param("loginUserId") Long loginUserId);
}
src/main/java/com/ruoyi/basic/mapper/CustomerPrivateMapper.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/mapper/CustomerPrivatePoolMapper.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/mapper/CustomerUserMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.basic.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.CustomerUser;
import org.apache.ibatis.annotations.Mapper;
/**
 * å®¢æˆ·å…±äº«Mapper接口
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @date 2026-04-29
 */
@Mapper
public interface CustomerUserMapper extends BaseMapper<CustomerUser> {
}
src/main/java/com/ruoyi/basic/pojo/Customer.java
@@ -129,4 +129,9 @@
    @Schema(description = "使用状态")
    private Long usageStatus;
    @Schema(description = "类型 0 ç§æµ·å®¢æˆ· 1 å…¬æµ·å®¢æˆ·")
    private Integer type;
    @Schema(description = "是否被分配:0-未分配,1-已分配")
    private Integer isAssigned;
}
src/main/java/com/ruoyi/basic/pojo/CustomerFollowUp.java
@@ -37,7 +37,7 @@
    /**
     * å…³è”的私海id
     */
    private Long customerPrivatePoolId;
    private Long customerId;
    /**
     * è·Ÿè¿›æ–¹å¼
src/main/java/com/ruoyi/basic/pojo/CustomerPrivate.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/pojo/CustomerPrivatePool.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/pojo/CustomerReturnVisit.java
@@ -36,7 +36,7 @@
    /**
     * å…³è”客户ID
     */
    private Integer customerPrivatePoolId;
    private Long customerId;
    /**
     * æé†’开关 (0:关闭, 1:开启)
src/main/java/com/ruoyi/basic/pojo/CustomerUser.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@TableName(value = "customer_user")
@Data
public class CustomerUser implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * åºå·
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * å®¢æˆ·id
     */
    private Long customerId;
    /**
     * ç”¨æˆ·id
     */
    private Long userId;
    /**
     * ç§Ÿæˆ·id
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    /**
     * å½•入时间
     */
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}
src/main/java/com/ruoyi/basic/service/CustomerPrivatePoolService.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/service/CustomerPrivateService.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/service/CustomerUserService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.basic.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.pojo.CustomerUser;
/**
 * å®¢æˆ·å…±äº«Service接口
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @date 2026-04-29
 */
public interface CustomerUserService extends IService<CustomerUser> {
}
src/main/java/com/ruoyi/basic/service/ICustomerService.java
@@ -4,9 +4,9 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.dto.CustomerDto;
import com.ruoyi.basic.dto.CustomerPrivatePoolDto;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.basic.vo.CustomerVo;
import com.ruoyi.framework.web.domain.R;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -33,7 +33,7 @@
     * @param id å®¢æˆ·æ¡£æ¡ˆä¸»é”®
     * @return å®¢æˆ·è¯¦æƒ…DTO
     */
    Customer selectCustomerDetailById(Long id);
    CustomerVo selectCustomerDetailById(Long id);
    /**
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆåˆ—表
@@ -75,9 +75,22 @@
     */
    List<Map<String, Object>> customerList(Customer customer);
    List<Customer> selectCustomerLists(Customer customer);
    List<CustomerVo> selectCustomerLists(CustomerDto customer);
    AjaxResult importData(MultipartFile file);
    R importData(MultipartFile file, Integer type);
    IPage<CustomerDto> selectCustomerList(Page<CustomerDto> page, CustomerDto customer);
    IPage<CustomerVo> selectCustomerList(Page<CustomerDto> page, CustomerDto customer);
    void assignCustomer(CustomerDto customer);
    void recycleCustomer(CustomerDto customer);
    /**
     * å…±äº«å®¢æˆ·ç»™å…¶ä»–用户
     *
     * @param customerDto å®¢æˆ·DTO(包含客户ID和共享用户ID列表)
     */
    void together(CustomerDto customerDto);
    Boolean back(Long id);
}
src/main/java/com/ruoyi/basic/service/impl/CustomerFollowUpServiceImpl.java
@@ -130,7 +130,7 @@
        }
        List<CustomerFollowUp> followUps = list(new LambdaQueryWrapper<CustomerFollowUp>()
                .eq(CustomerFollowUp::getCustomerPrivatePoolId, customerId));
                .eq(CustomerFollowUp::getCustomerId, customerId));
        if (followUps != null && !followUps.isEmpty()) {
            for (CustomerFollowUp followUp : followUps) {
src/main/java/com/ruoyi/basic/service/impl/CustomerPrivatePoolServiceImpl.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/service/impl/CustomerPrivateServiceImpl.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/basic/service/impl/CustomerReturnVisitServiceImpl.java
@@ -75,7 +75,7 @@
            throw new ServiceException("客户ID不能为空");
        }
        LambdaQueryWrapper<CustomerReturnVisit> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CustomerReturnVisit::getCustomerPrivatePoolId, customerId);
        queryWrapper.eq(CustomerReturnVisit::getCustomerId, customerId);
        CustomerReturnVisit returnVisit = baseMapper.selectOne(queryWrapper);
        if (returnVisit == null) {
@@ -94,7 +94,7 @@
            throw new ServiceException("客户ID不能为空");
        }
        LambdaQueryWrapper<CustomerReturnVisit> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CustomerReturnVisit::getCustomerPrivatePoolId, customerId);
        queryWrapper.eq(CustomerReturnVisit::getCustomerId, customerId);
        List<CustomerReturnVisit> returnVisits = baseMapper.selectList(queryWrapper);
        for (CustomerReturnVisit returnVisit : returnVisits) {
@@ -124,7 +124,7 @@
        if (returnVisit == null) {
            throw new ServiceException("回访提醒数据不能为空");
        }
        if (returnVisit.getCustomerPrivatePoolId() == null) {
        if (returnVisit.getCustomerId() == null) {
            throw new ServiceException("客户ID不能为空");
        }
        if (returnVisit.getReminderTime() == null) {
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -8,23 +8,25 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.CustomerDto;
import com.ruoyi.basic.dto.CustomerFollowUpDto;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.CustomerPrivatePoolMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.CustomerFollowUp;
import com.ruoyi.basic.pojo.CustomerPrivatePool;
import com.ruoyi.basic.service.CustomerFollowUpService;
import com.ruoyi.basic.service.CustomerReturnVisitService;
import com.ruoyi.basic.service.ICustomerService;
import com.ruoyi.basic.pojo.CustomerFollowUpFile;
import com.ruoyi.basic.pojo.CustomerUser;
import com.ruoyi.basic.service.*;
import com.ruoyi.basic.vo.CustomerVo;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import lombok.RequiredArgsConstructor;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@@ -42,15 +44,22 @@
 * @date 2025-05-07
 */
@Service
@RequiredArgsConstructor
@AllArgsConstructor
@Slf4j
public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements ICustomerService {
    private final SalesLedgerMapper salesLedgerMapper;
    private final CustomerPrivatePoolMapper customerPrivatePoolMapper;
    private final CustomerMapper customerMapper;
    @Autowired
    private  SalesLedgerMapper salesLedgerMapper;
    @Autowired
    private CustomerMapper customerMapper;
    private final CustomerFollowUpService customerFollowUpService;
    private final CustomerReturnVisitService customerReturnVisitService;
    @Autowired
    private CustomerFollowUpService customerFollowUpService;
    @Autowired
    private CustomerFollowUpFileService customerFollowUpFileService;
    @Autowired
    private CustomerReturnVisitService customerReturnVisitService;
    @Autowired
    private CustomerUserService customerUserService;
    /**
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆ
@@ -70,8 +79,35 @@
     * @return å®¢æˆ·è¯¦æƒ…DTO
     */
    @Override
    public Customer selectCustomerDetailById(Long id) {
        return this.getById( id);
    public CustomerVo selectCustomerDetailById(Long id) {
        CustomerVo customerVo = new CustomerVo();
        BeanUtils.copyProperties(this.getById(id), customerVo);
        // æŸ¥è¯¢è·Ÿè¿›è®°å½•
        List<CustomerFollowUp> followUpList = customerFollowUpService.list(
                new LambdaQueryWrapper<CustomerFollowUp>()
                        .eq(CustomerFollowUp::getCustomerId, id)
                        .orderByDesc(CustomerFollowUp::getFollowUpTime)
        );
        if (!com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(followUpList)) {
            List<CustomerFollowUpDto> followUpDtoList = followUpList.stream().map(followUp -> {
                CustomerFollowUpDto followUpDto = new CustomerFollowUpDto();
                BeanUtils.copyProperties(followUp, followUpDto);
                // æŸ¥è¯¢é™„ä»¶
                List<CustomerFollowUpFile> fileList = customerFollowUpFileService.list(
                        new LambdaQueryWrapper<CustomerFollowUpFile>()
                                .eq(CustomerFollowUpFile::getFollowUpId, followUp.getId())
                );
                followUpDto.setFileList(fileList);
                return followUpDto;
            }).collect(Collectors.toList());
            customerVo.setFollowUpList(followUpDtoList);
        }
        return customerVo;
    }
    /**
@@ -81,16 +117,18 @@
     * @return å®¢æˆ·æ¡£æ¡ˆ
     */
    @Override
    public IPage<CustomerDto> selectCustomerList(Page<CustomerDto> page, CustomerDto customer) {
        IPage<CustomerDto> customerPage = customerMapper.listPage(page, customer);
    public IPage<CustomerVo> selectCustomerList(Page<CustomerDto> page, CustomerDto customer) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Long loginUserId = loginUser.getUserId();
        IPage<CustomerVo> customerPage = customerMapper.listPage(page, customer, loginUserId);
        List<CustomerDto> records = customerPage.getRecords();
        List<CustomerVo> records = customerPage.getRecords();
        if (CollectionUtils.isEmpty(records)) {
            return customerPage;
        }
        List<Long> customerIds = records.stream()
                .map(CustomerDto::getId)
                .map(CustomerVo::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
@@ -109,6 +147,16 @@
                            followUp.getFollowUpTime().atZone(ZoneId.systemDefault()).toInstant()
                    ));
                }
                // è½¬æ¢å…±äº«ç”¨æˆ·ID字符串为List<Long>
                String userIdsStr = c.getUserIdsStr();
                if (StringUtils.isNotEmpty(userIdsStr)) {
                    List<Long> userIds = Arrays.stream(userIdsStr.split(","))
                            .map(String::trim)
                            .map(Long::parseLong)
                            .collect(Collectors.toList());
                    c.setUserIds(userIds);
                }
            });
        }
@@ -118,13 +166,13 @@
    private Map<Long, CustomerFollowUp> getLatestFollowUpMap(List<Long> customerIds) {
        List<CustomerFollowUp> followUps = customerFollowUpService.list(
                new LambdaQueryWrapper<CustomerFollowUp>()
                        .in(CustomerFollowUp::getCustomerPrivatePoolId, customerIds)
                        .in(CustomerFollowUp::getCustomerId, customerIds)
                        .orderByDesc(CustomerFollowUp::getFollowUpTime)
        );
        return followUps.stream()
                .collect(Collectors.toMap(
                        CustomerFollowUp::getCustomerPrivatePoolId,
                        CustomerFollowUp::getCustomerId,
                        followUp -> followUp,
                        (existing, replacement) -> existing
                ));
@@ -172,16 +220,28 @@
        if (!salesLedgers.isEmpty()) {
            throw new RuntimeException("客户档案下有销售合同,请先删除销售合同");
        }
        List<CustomerPrivatePool> customerPrivatePools = customerPrivatePoolMapper.selectList(new QueryWrapper<CustomerPrivatePool>().lambda().in(CustomerPrivatePool::getCustomerId, idList));
        if (!customerPrivatePools.isEmpty()) {
            throw new RuntimeException("客户档案下有客户私海,请先收回私海数据");
        // æŸ¥è¯¢æ˜¯å¦æœ‰å·²åˆ†é…çš„公海客户
        List<Customer> assignedPools = customerMapper.selectList(
                new QueryWrapper<Customer>().lambda()
                        .in(Customer::getId, idList)
                        .eq(Customer::getType, 1).
                        eq(Customer::getIsAssigned, 1)  // å…¬æµ·å®¢æˆ·
        );
        if (!assignedPools.isEmpty()) {
            throw new RuntimeException("客户档案下有已分配的公海客户,请先收回");
        }
        //  åˆ é™¤å®¢æˆ·çš„同时也需要删除对应的客户跟随、附件和回访提醒
        // åˆ é™¤å®¢æˆ·çš„同时也需要删除对应的客户跟随、附件和回访提醒
        for (Long id : ids) {
            customerFollowUpService.deleteByCustomerId(id);
            customerReturnVisitService.deleteByCustomerId(id);
            // åˆ é™¤å®¢æˆ·çš„共享关系
            customerUserService.remove(
                new QueryWrapper<CustomerUser>().lambda()
                    .eq(CustomerUser::getCustomerId, id)
            );
        }
        // åˆ é™¤å®¢æˆ·ä¸»è¡¨æ•°æ®
        return customerMapper.deleteBatchIds(idList);
    }
@@ -193,23 +253,32 @@
    }
    @Override
    public List<Customer> selectCustomerLists(Customer customer) {
        return customerMapper.selectList(null);
    public List<CustomerVo> selectCustomerLists(CustomerDto customer) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Long loginUserId = loginUser.getUserId();
        return customerMapper.list(customer, loginUserId);
    }
    @Override
    public AjaxResult importData(MultipartFile file) {
    public R importData(MultipartFile file, Integer type) {
        try {
            ExcelUtil<Customer> util = new ExcelUtil<Customer>(Customer.class);
            List<Customer> userList = util.importExcel(file.getInputStream());
            if (CollectionUtils.isEmpty(userList)) {
                return AjaxResult.warn("模板错误或导入数据为空");
                return R.fail("模板错误或导入数据为空");
            }
            // æ ¹æ® type å‚数设置客户类型(私海/公海)
            if (type != null) {
                userList.forEach(customer -> {
                    customer.setType(type);
                });
            }
            this.saveOrUpdateBatch(userList);
            return AjaxResult.success(true);
            return R.ok(true);
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("导入失败");
            return R.fail("导入失败");
        }
    }
@@ -230,6 +299,86 @@
        ).collect(Collectors.toList());
    }
    // åˆ†é…å…¬æµ·å®¢æˆ·ç»™ç§æµ·
    @Override
    public void assignCustomer(CustomerDto customerDto) {
        Customer customer = customerMapper.selectById(customerDto.getId());
        if (customer.getType() == 1 && customer.getIsAssigned() == 0) {  // å…¬æµ·ä¸”可分配
            customer.setIsAssigned(1);
            customer.setUsageStatus(1L);
            customer.setUsageUser(customerDto.getUsageUser());
            customerMapper.updateById(customer);
        }
    }
    // å›žæ”¶ç§æµ·å®¢æˆ·åˆ°å…¬æµ·
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void recycleCustomer(CustomerDto customerDto) {
        Customer customer = customerMapper.selectById(customerDto.getId());
        if (customer.getType() == 1 && customer.getIsAssigned() == 1) {  // å…¬æµ·ä¸”已分配
            customer.setIsAssigned(0);
            customer.setUsageStatus(0L);
            customer.setUsageUser(0L);
            customerMapper.updateById(customer);
            // åˆ é™¤è¯¥å®¢æˆ·çš„æ‰€æœ‰å…±äº«å…³ç³»
            customerUserService.remove(
                new QueryWrapper<CustomerUser>().lambda()
                    .eq(CustomerUser::getCustomerId, customerDto.getId())
            );
        }
    }
    // å®¢æˆ·å…±äº«
    @Override
    public void together(CustomerDto customerDto) {
        // æŸ¥è¯¢çŽ°æœ‰çš„å…±äº«è®°å½•
        List<CustomerUser> existingUsers = customerUserService.list(
                new QueryWrapper<CustomerUser>().lambda().eq(CustomerUser::getCustomerId, customerDto.getId())
        );
        // èŽ·å–å·²å­˜åœ¨çš„ç”¨æˆ·ID列表
        List<Long> existingUserIds = existingUsers.stream()
                .map(CustomerUser::getUserId)
                .collect(Collectors.toList());
        // è¿‡æ»¤æŽ‰å·²å­˜åœ¨çš„用户,只保留新用户
        List<Long> newUserIds = customerDto.getUserIds().stream()
                .filter(userId -> !existingUserIds.contains(userId))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(newUserIds)) {
            return;
        }
        // èŽ·å–å½“å‰ç§Ÿæˆ·ID
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Long tenantId = loginUser.getTenantId();
        // æ‰¹é‡ä¿å­˜æ–°çš„共享记录
        List<CustomerUser> customerUsers = newUserIds.stream()
                .map(userId -> {
                    CustomerUser customerUser = new CustomerUser();
                    customerUser.setCustomerId(customerDto.getId());
                    customerUser.setUserId(userId);
                    customerUser.setTenantId(tenantId);
                    return customerUser;
                })
                .collect(Collectors.toList());
        customerUserService.saveBatch(customerUsers);
    }
    @Override
    public Boolean back(Long id) {
        //将客户的type改为1 ä¸”直接分配给当前用户
        Customer customer = customerMapper.selectById(id);
        customer.setType(1);
        customer.setIsAssigned(1);
        return this.updateById(customer);
    }
    /**
     * ä¸‹åˆ’线命名转驼峰命名
     */
src/main/java/com/ruoyi/basic/service/impl/CustomerUserServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.basic.service.impl;
import com.ruoyi.basic.mapper.CustomerUserMapper;
import com.ruoyi.basic.pojo.CustomerUser;
import com.ruoyi.basic.service.CustomerUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
 * å®¢æˆ·å…±äº«Service实现类
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @date 2026-04-29
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class CustomerUserServiceImpl extends ServiceImpl<CustomerUserMapper, CustomerUser> implements CustomerUserService {
}
src/main/java/com/ruoyi/basic/task/ReturnVisitReminderTask.java
@@ -72,7 +72,7 @@
        }
        try {
            unipushService.sendReturnVisitReminder(returnVisitId, client.getCid(), returnVisit.getContent(), returnVisit.getCustomerPrivatePoolId());
            unipushService.sendReturnVisitReminder(returnVisitId, client.getCid(), returnVisit.getContent(), returnVisit.getCustomerId());
            CustomerReturnVisit updateObj = new CustomerReturnVisit();
            updateObj.setId(returnVisitId);
            updateObj.setIsCompleted(1);
src/main/java/com/ruoyi/basic/vo/CustomerVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.basic.vo;
import com.ruoyi.basic.dto.CustomerFollowUpDto;
import com.ruoyi.basic.pojo.Customer;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
public class CustomerVo extends Customer {
    @ApiModelProperty(value = "跟进记录")
    private List<CustomerFollowUpDto> followUpList;
    private String usageUserName;
    private String togetherUserNames;
    /**
     * å…±äº«ç”¨æˆ·ID列表
     */
    private List<Long> userIds;
    /**
     * å…±äº«ç”¨æˆ·ID字符串(SQL查询返回,用于转换为List)
     */
    private String userIdsStr;
}
src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java
@@ -136,7 +136,7 @@
    /**
     * å‘送回访提醒
     */
    public void sendReturnVisitReminder(Long returnVisitId, String cid, String content, Integer customerId) {
    public void sendReturnVisitReminder(Long returnVisitId, String cid, String content, Long customerId) {
        String targetPath = "pages/cooperativeOffice/customerManage/detail?customerId=" + customerId;
        sendRoutingPush(returnVisitId, cid, "客户回访提醒", content, targetPath, false);
    }
src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java
@@ -3,7 +3,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -16,55 +16,58 @@
public class SalesQuotation {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @Schema(description = "报价单编号")
    @ApiModelProperty(value = "报价单编号")
    @Excel(name = "报价单编号")
    private String quotationNo;
    @Schema(description = "客户名称")
    @ApiModelProperty(value = "客户名称")
    @Excel(name = "客户名称")
    private String customer;
    @Schema(description = "业务员")
    @ApiModelProperty(value = "客户id")
    private Long customerId;
    @ApiModelProperty(value = "业务员")
    @Excel(name = "业务员")
    private String salesperson;
    @Schema(description = "报价日期")
    @ApiModelProperty(value = "报价日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "报价日期", width = 30, dateFormat = "yyyy-MM-dd")
    private LocalDate quotationDate;
    @Schema(description = "有效期至")
    @ApiModelProperty(value = "有效期至")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "有效期至", width = 30, dateFormat = "yyyy-MM-dd")
    private LocalDate validDate;
    @Schema(description = "付款方式")
    @ApiModelProperty(value = "付款方式")
    private String paymentMethod;
    @Schema(description = "交货周期天数")
    @ApiModelProperty(value = "交货周期天数")
    private String deliveryPeriod;
    @Schema(description = "状态")
    @ApiModelProperty(value = "状态")
    private String status;
    @Schema(description = "报价总金额")
    @ApiModelProperty(value = "报价总金额")
    @Excel(name = "报价金额")
    private BigDecimal totalAmount;
    @Schema(description = "备注")
    @ApiModelProperty(value = "备注")
    private String remark;
    @Schema(description = "创建时间")
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @Schema(description = "修改时间")
    @ApiModelProperty(value = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @Schema(description = "创建用户")
    @ApiModelProperty(value = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @Schema(description = "修改用户")
    @ApiModelProperty(value = "修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @Schema(description = "租户ID")
    @ApiModelProperty(value = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -9,15 +9,10 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.service.AccountIncomeService;
import com.ruoyi.basic.dto.CustomerPrivatePoolDto;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.CustomerPrivatePoolMapper;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.SaleEnum;
import com.ruoyi.common.exception.base.BaseException;
@@ -29,9 +24,8 @@
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.other.mapper.TempFileMapper;
import com.ruoyi.production.mapper.ProductionProductInputMapper;
import com.ruoyi.production.mapper.ProductionProductMainMapper;
import com.ruoyi.production.mapper.ProductionProductOutputMapper;
import com.ruoyi.other.pojo.TempFile;
import com.ruoyi.production.mapper.*;
import com.ruoyi.production.service.ProductionProductMainService;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysUser;
@@ -47,8 +41,10 @@
import com.ruoyi.sales.vo.SalesLedgerVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
@@ -56,10 +52,15 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
@@ -82,11 +83,13 @@
    private static final String LOCK_PREFIX = "contract_no_lock:";
    private static final long LOCK_WAIT_TIMEOUT = 10; // é”ç­‰å¾…超时时间(秒)
    private static final long LOCK_EXPIRE_TIME = 30;  // é”è‡ªåŠ¨è¿‡æœŸæ—¶é—´ï¼ˆç§’ï¼‰
    private final AccountIncomeService accountIncomeService;
    private final SalesLedgerMapper salesLedgerMapper;
    private final CustomerMapper customerMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final SalesLedgerProductServiceImpl salesLedgerProductServiceImpl;
    private final CommonFileMapper commonFileMapper;
    private final TempFileMapper tempFileMapper;
    private final ReceiptPaymentMapper receiptPaymentMapper;
    private final ShippingInfoServiceImpl shippingInfoServiceImpl;
    private final CommonFileServiceImpl commonFileService;
@@ -94,14 +97,27 @@
    private final InvoiceLedgerMapper invoiceLedgerMapper;
    private final InvoiceRegistrationProductMapper invoiceRegistrationProductMapper;
    private final InvoiceRegistrationMapper invoiceRegistrationMapper;
    private final ProductModelMapper productModelMapper;
    private final ProductionProductMainMapper productionProductMainMapper;
    private final ProductionProductOutputMapper productionProductOutputMapper;
    private final ProductionProductInputMapper productionProductInputMapper;
    private final QualityInspectMapper qualityInspectMapper;
    private final RedisTemplate<String, String> redisTemplate;
    private final SysDeptMapper sysDeptMapper;
    private final ProductionProductMainService productionProductMainService;
    private final PurchaseReturnOrderProductsMapper purchaseReturnOrderProductsMapper;
    private final SysUserMapper sysUserMapper;
    private final CustomerPrivatePoolMapper customerPrivatePoolMapper;
    private final FileUtil fileUtil;
    @Autowired
    private SysDeptMapper sysDeptMapper;
    @Value("${file.upload-dir}")
    private String uploadDir;
    @Autowired
    private ProductModelMapper productModelMapper;
    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private ProductionProductMainService productionProductMainService;
    @Autowired
    private PurchaseReturnOrderProductsMapper purchaseReturnOrderProductsMapper;
    ;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) {
@@ -185,9 +201,6 @@
            resultDto.setProductData(products);
            resultDto.setSalesLedgerFiles(salesLedgerFiles);
        }
        // 5. æŸ¥è¯¢é™„ä»¶
        List<StorageBlobVO> StorageBlobVOs = fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.SALES_LEDGER, salesLedger.getId());
        resultDto.setStorageBlobVOs(StorageBlobVOs);
        return resultDto;
    }
@@ -514,7 +527,7 @@
        List<Long> productIds = products.stream()
                .map(SalesLedgerProduct::getId)
                .collect(Collectors.toList());
        //删除生产计划
        //删除生产数据
        salesLedgerProductServiceImpl.deleteProductionData(productIds);
        // æ‰¹é‡åˆ é™¤äº§å“å­è¡¨
@@ -569,43 +582,122 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto) {
        // 1. æ ¡éªŒå®¢æˆ·ä¿¡æ¯
        CustomerPrivatePoolDto customer = customerPrivatePoolMapper.selectInfo(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, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType()));
                updateMainContractAmount(
                        salesLedger.getId(),
                        productList,
                        SalesLedgerProduct::getTaxInclusiveTotalPrice,
                        salesLedgerMapper,
                        SalesLedger.class
                );
            }
            // 5. è¿ç§»ä¸´æ—¶æ–‡ä»¶åˆ°æ­£å¼ç›®å½•
            if (salesLedgerDto.getTempFileIds() != null && !salesLedgerDto.getTempFileIds().isEmpty()) {
                migrateTempFilesToFormal(salesLedger.getId(), salesLedgerDto.getTempFileIds());
            }
            return 1;
        } catch (IOException e) {
            throw new BaseException("文件迁移失败: " + e.getMessage());
        }
    }
    /**
     * å°†ä¸´æ—¶æ–‡ä»¶è¿ç§»åˆ°æ­£å¼ç›®å½•
     *
     * @param businessId  ä¸šåŠ¡ID(销售台账ID)
     * @param tempFileIds ä¸´æ—¶æ–‡ä»¶ID列表
     * @throws IOException æ–‡ä»¶æ“ä½œå¼‚常
     */
    private void migrateTempFilesToFormal(Long businessId, List<String> tempFileIds) throws IOException {
        if (CollectionUtils.isEmpty(tempFileIds)) {
            return;
        }
        // 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);
        // æž„建正式目录路径(按业务类型和日期分组)
        String formalDir = uploadDir + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
        Path formalDirPath = Paths.get(formalDir);
        // ç¡®ä¿æ­£å¼ç›®å½•存在(递归创建)
        if (!Files.exists(formalDirPath)) {
            Files.createDirectories(formalDirPath);
        }
        // 4. å¤„理子表数据
        List<SalesLedgerProduct> productList = salesLedgerDto.getProductData();
        if (productList != null && !productList.isEmpty()) {
            handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType()));
            updateMainContractAmount(
                    salesLedger.getId(),
                    productList,
                    SalesLedgerProduct::getTaxInclusiveTotalPrice,
                    salesLedgerMapper,
                    SalesLedger.class
            );
        }
        for (String tempFileId : tempFileIds) {
            // æŸ¥è¯¢ä¸´æ—¶æ–‡ä»¶è®°å½•
            TempFile tempFile = tempFileMapper.selectById(tempFileId);
            if (tempFile == null) {
                log.warn("临时文件不存在,跳过处理: {}", tempFileId);
                continue;
            }
        // 5. ä¿å­˜æ–‡ä»¶
        if (salesLedgerDto.getStorageBlobDTOs() != null && !salesLedgerDto.getStorageBlobDTOs().isEmpty()) {
            fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.SALES_LEDGER, salesLedger.getId(), salesLedgerDto.getStorageBlobDTOs());
            // æž„建正式文件名(包含业务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
//                );
                // åŽŸå­ç§»åŠ¨å¤±è´¥ï¼Œä½¿ç”¨å¤åˆ¶+删除
                Files.copy(Paths.get(tempFile.getTempPath()), formalFilePath, StandardCopyOption.REPLACE_EXISTING);
                Files.deleteIfExists(Paths.get(tempFile.getTempPath()));
                log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath);
                // æ›´æ–°æ–‡ä»¶è®°å½•(关联到业务ID)
                CommonFile fileRecord = new CommonFile();
                fileRecord.setCommonId(businessId);
                fileRecord.setName(originalFilename);
                fileRecord.setUrl(formalFilePath.toString());
                fileRecord.setCreateTime(LocalDateTime.now());
                //销售
                fileRecord.setType(FileNameType.SALE.getValue());
                commonFileMapper.insert(fileRecord);
                // åˆ é™¤ä¸´æ—¶æ–‡ä»¶è®°å½•
                tempFileMapper.deleteById(tempFile);
                log.info("文件迁移成功: {} -> {}", tempFile.getTempPath(), formalFilePath);
            } catch (IOException e) {
                log.error("文件迁移失败: {}", tempFile.getTempPath(), e);
                // å¯é€‰æ‹©å›žæ»šäº‹åŠ¡æˆ–è®°å½•å¤±è´¥æ–‡ä»¶
                throw new IOException("文件迁移异常", e);
            }
        }
        return 1;
    }
    // æ–‡ä»¶è¿ç§»æ–¹æ³•
src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
@@ -11,8 +11,8 @@
import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl;
import com.ruoyi.approve.bean.vo.ApproveGetAndUpdateVo;
import com.ruoyi.approve.bean.vo.ApproveProcessVO;
import com.ruoyi.basic.dto.CustomerPrivatePoolDto;
import com.ruoyi.basic.mapper.CustomerPrivatePoolMapper;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
@@ -42,7 +42,7 @@
    private final SalesQuotationProductService salesQuotationProductService;
    private final ApproveProcessServiceImpl approveProcessService;
    private final CustomerPrivatePoolMapper customerPrivatePoolMapper;
    private final CustomerMapper customerMapper;
    @Override
    public IPage<SalesQuotationDto> listPage(Page page, SalesQuotationDto salesQuotationDto) {
@@ -61,10 +61,9 @@
    public boolean add(SalesQuotationDto salesQuotationDto) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        SalesQuotation salesQuotation = new SalesQuotation();
        CustomerPrivatePoolDto customerPrivatePoolDto = customerPrivatePoolMapper.selectInfo(Long.valueOf(salesQuotationDto.getCustomer()));
        if (ObjectUtils.isNotEmpty(customerPrivatePoolDto))  {
            BeanUtils.copyProperties(salesQuotationDto, salesQuotation);
            salesQuotation.setCustomer(customerPrivatePoolDto.getCustomerName());
        Customer customer = customerMapper.selectById(Long.valueOf(salesQuotationDto.getCustomerId()));
        if (ObjectUtils.isNotEmpty(customer))  {
            salesQuotation.setCustomer(customer.getCustomerName());
        }
        String quotationNo = OrderUtils.countTodayByCreateTime(salesQuotationMapper, "QT","quotation_no");
        salesQuotation.setQuotationNo(quotationNo);
@@ -93,7 +92,7 @@
            approveProcessService.addApprove(approveProcessVO);
        }catch (Exception e){
            log.error("SalesQuotationServiceImpl error:{}", e);
            throw new RuntimeException("审批失败");
            throw                                new RuntimeException("审批失败");
        }
        return true;
    }
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java
@@ -1,17 +1,12 @@
package com.ruoyi.staff.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.service.PersonalAttendanceLocationConfigService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -30,7 +25,7 @@
@RequiredArgsConstructor
public class PersonalAttendanceLocationConfigController {
    private PersonalAttendanceLocationConfigService personalAttendanceLocationConfigService;
    private final PersonalAttendanceLocationConfigService personalAttendanceLocationConfigService;
    @Operation(summary = "新增/修改人员打卡规则配置")
    @PostMapping("/add")
src/main/resources/mapper/basic/CustomerMapper.xml
@@ -6,17 +6,23 @@
    <resultMap id="BaseResultMap" type="com.ruoyi.basic.pojo.Customer">
        <id column="id" property="id" />
    </resultMap>
    <select id="listPage" resultType="com.ruoyi.basic.dto.CustomerDto">
    <select id="listPage" resultType="com.ruoyi.basic.vo.CustomerVo">
        select
        c.*,
        u.user_name usage_user_name,
        (
        select group_concat(u2.user_name separator ', ')
        from customer_private_pool cpp2
        left join sys_user u2 on cpp2.bound_id = u2.user_id
        where cpp2.customer_id = c.id and cpp2.delete_flag = 0
        and cpp2.bound_id != c.usage_user
        ) as together_user_names
        from customer_user cu
        left join sys_user u2 on cu.user_id = u2.user_id
        where cu.customer_id = c.id
        and cu.user_id != c.usage_user
        ) as together_user_names,
        (
        select group_concat(cu.user_id separator ',')
        from customer_user cu
        where cu.customer_id = c.id
        and cu.user_id != c.usage_user
        ) as user_ids_str
        from customer c
        left join sys_user u on c.usage_user = u.user_id
        <where>
@@ -26,8 +32,79 @@
            <if test="c.customerType != null and c.customerType != ''">
                and customer_type = #{c.customerType}
            </if>
            <!-- å…¬æµ·æŸ¥è¯¢ï¼štype = 1(公海客户)-->
            <if test="c.type != null and c.type == 1">
                and type = #{c.type}
            </if>
            <!-- ç§æµ·æŸ¥è¯¢ï¼štype = 0(私海客户)或者 type = 1(公海客户)且已被分配,并且是自己领用、自己创建或者共享给自己的客户 -->
            <if test="c.type != null and c.type == 0">
                and (
                    (type = #{c.type} or (type = 1 and is_assigned = 1))
                    and (
                        c.usage_user = #{loginUserId}
                        or c.create_user = #{loginUserId}
                        or exists (
                            select 1 from customer_user cu
                            where cu.customer_id = c.id
                            and cu.user_id = #{loginUserId}
                        )
                    )
                )
            </if>
        </where>
    </select>
</mapper>
    <select id="list" resultType="com.ruoyi.basic.vo.CustomerVo">
        select
        c.*,
        u.user_name usage_user_name,
        (
        select group_concat(u2.user_name separator ', ')
        from customer_user cu
        left join sys_user u2 on cu.user_id = u2.user_id
        where cu.customer_id = c.id
        and cu.user_id != c.usage_user
        ) as together_user_names,
        (
        select group_concat(cu.user_id separator ',')
        from customer_user cu
        where cu.customer_id = c.id
        and cu.user_id != c.usage_user
        ) as user_ids_str
        from customer c
        left join sys_user u on c.usage_user = u.user_id
        <where>
            <if test="c.ids != null and c.ids.length > 0">
                and c.id in
                <foreach collection="c.ids" item="id" open="(" separator="," close=")">
                    #{id}
                </foreach>
            </if>
            <if test="c.customerName != null and c.customerName != ''">
                and customer_name like concat('%', #{c.customerName}, '%')
            </if>
            <if test="c.customerType != null and c.customerType != ''">
                and customer_type = #{c.customerType}
            </if>
            <!-- å…¬æµ·æŸ¥è¯¢ï¼štype = 1(公海客户)-->
            <if test="c.type != null and c.type == 1">
                and type = #{c.type}
            </if>
            <!-- ç§æµ·æŸ¥è¯¢ï¼štype = 0(私海客户)或者 type = 1(公海客户)且已被分配,并且是自己领用、自己创建或者共享给自己的客户 -->
            <if test="c.type != null and c.type == 0">
                and (
                    (type = #{c.type} or (type = 1 and is_assigned = 1))
                    and (
                        c.usage_user = #{loginUserId}
                        or c.create_user = #{loginUserId}
                        or exists (
                            select 1 from customer_user cu
                            where cu.customer_id = c.id
                            and cu.user_id = #{loginUserId}
                        )
                    )
                )
            </if>
        </where>
    </select>
</mapper>
src/main/resources/mapper/basic/CustomerPrivateMapper.xml
ÎļþÒÑɾ³ý
src/main/resources/mapper/basic/CustomerPrivatePoolMapper.xml
ÎļþÒÑɾ³ý