已添加12个文件
已修改12个文件
1700 ■■■■■ 文件已修改
doc/河南鹤壁天沐钢化玻璃厂.sql 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerRegionsController.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/CustomerDto.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/CustomerRegionsTreeDto.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerRegionsMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/Customer.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerRegions.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/ICustomerRegionsService.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/ICustomerService.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerRegionsServiceImpl.java 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java 167 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesInvoicesDto.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLabelDto.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLedgerProcessRouteDto.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesOrdersDto.java 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesProcessCardDto.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProcessRoute.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 514 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerRegionsMapper.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerMapper.xml 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/ºÓÄϺױÚÌìãå¸Ö»¯²£Á§³§.sql
@@ -61,4 +61,27 @@
ALTER TABLE `product-inventory-management-hbtmblc`.`process_route_item`
    MODIFY COLUMN `product_model_id` bigint NULL DEFAULT 0 COMMENT '产品id' AFTER `route_id`,
    MODIFY COLUMN `process_id` bigint NULL DEFAULT 0 COMMENT '工序id' AFTER `product_model_id`,
    ADD COLUMN `process_name` varchar(255) NULL COMMENT '工序名称' AFTER `process_id`;
    ADD COLUMN `process_name` varchar(255) NULL COMMENT '工序名称' AFTER `process_id`;
-- ----------------------------
-- Table structure for customer_regions
-- ----------------------------
DROP TABLE IF EXISTS `customer_regions`;
CREATE TABLE `customer_regions`
(
    `id`           bigint                                                        NOT NULL AUTO_INCREMENT COMMENT '主键ID',
    `parent_id`    bigint                                                        NULL DEFAULT NULL COMMENT '父类ID',
    `regions_name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL COMMENT '地区名称',
    `tenant_id`    bigint                                                        NULL DEFAULT NULL COMMENT '租户ID',
    PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
  CHARACTER SET = utf8mb3
  COLLATE = utf8mb3_general_ci
  ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
ALTER TABLE `product-inventory-management-hbtmblc`.`customer_regions` COMMENT = '客户地区表';
ALTER TABLE `product-inventory-management-hbtmblc`.`customer`
    ADD COLUMN `regions_id` bigint NULL COMMENT '地区ID' AFTER `regions`;
src/main/java/com/ruoyi/basic/controller/CustomerController.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.CustomerDto;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.service.ICustomerService;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -9,7 +10,6 @@
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -33,7 +33,7 @@
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆåˆ—表
     */
    @GetMapping("/list")
    public IPage<Customer> list(Page<Customer> page, Customer customer) {
    public IPage<CustomerDto> list(Page<Customer> page, Customer customer) {
        return customerService.selectCustomerList(page, customer);
    }
@@ -118,10 +118,4 @@
        return customerService.customerList(customer);
    }
    @GetMapping("/regions")
    @ApiOperation("获取客户地区")
    public AjaxResult getRegions() {
        List<String> regionsList = customerService.regionsList();
        return AjaxResult.success(regionsList);
    }
}
src/main/java/com/ruoyi/basic/controller/CustomerRegionsController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
package com.ruoyi.basic.controller;
import com.ruoyi.basic.dto.CustomerRegionsTreeDto;
import com.ruoyi.basic.pojo.CustomerRegions;
import com.ruoyi.basic.service.ICustomerRegionsService;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
 * å®¢æˆ·åœ°åŒºè¡¨ å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author deslrey
 * @since 2026-03-27
 */
@RestController
@RequestMapping("/customerRegions")
public class CustomerRegionsController {
    @Autowired
    private ICustomerRegionsService customerRegionsService;
    @GetMapping("list")
    @ApiOperation("客户地区-列表")
    public AjaxResult customerRegionsList(CustomerRegions customerRegions) {
        List<CustomerRegionsTreeDto> list = customerRegionsService.customerRegionsList(customerRegions);
        return AjaxResult.success(list);
    }
    @PostMapping("/add")
    @ApiOperation("客户地区-新增")
    public AjaxResult addCustomerRegions(@RequestBody CustomerRegions customerRegions) {
        customerRegionsService.addCustomerRegions(customerRegions);
        return AjaxResult.success();
    }
    @PutMapping("/update")
    @ApiOperation("客户地区-更新")
    public AjaxResult updateCustomerRegions(@RequestBody CustomerRegions customerRegions) {
        customerRegionsService.updateCustomerRegions(customerRegions);
        return AjaxResult.success();
    }
    @DeleteMapping("/{id}")
    @ApiOperation("客户地区-删除")
    public AjaxResult delCustomerRegions(@PathVariable Long id) {
        customerRegionsService.delCustomerRegions(id);
        return AjaxResult.success();
    }
}
src/main/java/com/ruoyi/basic/dto/CustomerDto.java
@@ -1,6 +1,7 @@
package com.ruoyi.basic.dto;
import com.ruoyi.basic.pojo.Customer;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -21,4 +22,10 @@
    private List<CustomerFollowUpDto> followUpList;
    /**
     * åœ°åŒº
     */
    @ApiModelProperty("地区")
    private String regionsName;
}
src/main/java/com/ruoyi/basic/dto/CustomerRegionsTreeDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package com.ruoyi.basic.dto;
import lombok.Data;
import java.util.List;
/**
 * <br>
 * å®¢æˆ·åœ°åŒºæ ‘å½¢Dto
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/27 11:41
 */
@Data
public class CustomerRegionsTreeDto {
    /**
     * ID
     */
    private Long id;
    /**
     * çˆ¶ç±»ID
     */
    private Long parentId;
    /**
     * åœ°åŒºåç§°
     */
    private String regionsName;
    /**
     * åœ°åŒºæ ‡ç­¾
     */
    private String label;
    /**
     * å­åœ°åŒº
     */
    private List<CustomerRegionsTreeDto> children;
}
src/main/java/com/ruoyi/basic/mapper/CustomerRegionsMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.basic.mapper;
import com.ruoyi.basic.pojo.CustomerRegions;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * <p>
 * å®¢æˆ·åœ°åŒºè¡¨ Mapper æŽ¥å£
 * </p>
 *
 * @author deslrey
 * @since 2026-03-27
 */
public interface CustomerRegionsMapper extends BaseMapper<CustomerRegions> {
}
src/main/java/com/ruoyi/basic/pojo/Customer.java
@@ -29,6 +29,12 @@
    private Long id;
    /**
     * åœ°åŒºID
     */
    @ApiModelProperty("地区ID")
    private Long regionsId;
    /**
     * å®¢æˆ·åç§°
     */
    @Excel(name = "客户名称")
@@ -117,7 +123,4 @@
    @Excel(name = "开户行号")
    private String bankCode;
    @ApiModelProperty("地区")
    @Excel(name = "地区")
    private String regions;
}
src/main/java/com/ruoyi/basic/pojo/CustomerRegions.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
 * <p>
 * å®¢æˆ·åœ°åŒºè¡¨
 * </p>
 *
 * @author deslrey
 * @since 2026-03-27
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("customer_regions")
@ApiModel(value = "CustomerRegions对象", description = "客户地区表")
public class CustomerRegions implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "父类ID")
    private Long parentId;
    @ApiModelProperty(value = "地区名称")
    private String regionsName;
    @ApiModelProperty(value = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
}
src/main/java/com/ruoyi/basic/service/ICustomerRegionsService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.basic.service;
import com.ruoyi.basic.dto.CustomerRegionsTreeDto;
import com.ruoyi.basic.pojo.CustomerRegions;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
 * <p>
 * å®¢æˆ·åœ°åŒºè¡¨ æœåŠ¡ç±»
 * </p>
 *
 * @author deslrey
 * @since 2026-03-27
 */
public interface ICustomerRegionsService extends IService<CustomerRegions> {
    List<CustomerRegionsTreeDto> customerRegionsList(CustomerRegions customerRegions);
    void addCustomerRegions(CustomerRegions customerRegions);
    void updateCustomerRegions(CustomerRegions customerRegions);
    void delCustomerRegions(Long id);
    List<Long> regionsChildrenIds(Long regionsId);
}
src/main/java/com/ruoyi/basic/service/ICustomerService.java
@@ -40,7 +40,7 @@
     * @param customer å®¢æˆ·æ¡£æ¡ˆ
     * @return å®¢æˆ·æ¡£æ¡ˆé›†åˆ
     */
    IPage<Customer> selectCustomerList(Page<Customer> page, Customer customer);
    IPage<CustomerDto> selectCustomerList(Page<Customer> page, Customer customer);
    /**
     * æ–°å¢žå®¢æˆ·æ¡£æ¡ˆ
@@ -78,7 +78,4 @@
    List<Customer> selectCustomerLists(Customer customer);
    AjaxResult importData(MultipartFile file);
    List<String> regionsList();
}
src/main/java/com/ruoyi/basic/service/impl/CustomerRegionsServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,216 @@
package com.ruoyi.basic.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.CustomerRegionsTreeDto;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.CustomerRegionsMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.CustomerRegions;
import com.ruoyi.basic.service.ICustomerRegionsService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * <p>
 * å®¢æˆ·åœ°åŒºè¡¨ æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author deslrey
 * @since 2026-03-27
 */
@Service
public class CustomerRegionsServiceImpl extends ServiceImpl<CustomerRegionsMapper, CustomerRegions> implements ICustomerRegionsService {
    @Autowired
    private CustomerMapper customerMapper;
    /**
     * æŸ¥è¯¢æ ‘形地区列表
     */
    @Override
    public List<CustomerRegionsTreeDto> customerRegionsList(CustomerRegions customerRegions) {
        List<CustomerRegions> allRegions = baseMapper.selectList(null);
        if (allRegions == null || allRegions.isEmpty()) {
            return new ArrayList<>();
        }
        Map<Long, CustomerRegionsTreeDto> nodeMap = new HashMap<>();
        List<CustomerRegionsTreeDto> allDtoList = new ArrayList<>();
        for (CustomerRegions region : allRegions) {
            CustomerRegionsTreeDto dto = new CustomerRegionsTreeDto();
            dto.setId(region.getId());
            dto.setParentId(region.getParentId());
            dto.setRegionsName(region.getRegionsName());
            dto.setLabel(region.getRegionsName()); // ç»Ÿä¸€ label å­—段
            dto.setChildren(new ArrayList<>());
            nodeMap.put(dto.getId(), dto);
            allDtoList.add(dto);
        }
        List<CustomerRegionsTreeDto> treeList = new ArrayList<>();
        for (CustomerRegionsTreeDto node : allDtoList) {
            Long parentId = node.getParentId();
            // å¦‚果是根节点 (parentId ä¸º 0 æˆ– null) æˆ– æ‰¾ä¸åˆ°çˆ¶èŠ‚ç‚¹ï¼Œåˆ™ä½œä¸ºé¡¶çº§èŠ‚ç‚¹
            if (parentId == null || parentId == 0 || !nodeMap.containsKey(parentId)) {
                treeList.add(node);
            } else {
                // å¦åˆ™å°†å…¶æ·»åŠ åˆ°çˆ¶èŠ‚ç‚¹çš„ children ä¸­
                nodeMap.get(parentId).getChildren().add(node);
            }
        }
        String keyword = customerRegions != null ? customerRegions.getRegionsName() : null;
        if (StringUtils.isNotEmpty(keyword)) {
            return filterTree(treeList, keyword);
        }
        return treeList;
    }
    /**
     * é€’归过滤树节点
     */
    private List<CustomerRegionsTreeDto> filterTree(List<CustomerRegionsTreeDto> list, String keyword) {
        List<CustomerRegionsTreeDto> filteredList = new ArrayList<>();
        for (CustomerRegionsTreeDto node : list) {
            // é€’归处理子节点
            List<CustomerRegionsTreeDto> filteredChildren = filterTree(node.getChildren(), keyword);
            node.setChildren(filteredChildren);
            // å¦‚果当前节点名称包含关键字,或者子节点过滤后不为空,则保留该节点
            if (node.getRegionsName().contains(keyword) || !filteredChildren.isEmpty()) {
                filteredList.add(node);
            }
        }
        return filteredList;
    }
    /**
     * æ·»åŠ åœ°åŒº
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addCustomerRegions(CustomerRegions customerRegions) {
        validateRegion(customerRegions);
        checkUnique(customerRegions);
        if (baseMapper.insert(customerRegions) <= 0) {
            throw new ServiceException("添加失败");
        }
    }
    /**
     * ä¿®æ”¹åœ°åŒº
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCustomerRegions(CustomerRegions customerRegions) {
        if (customerRegions.getId() == null) {
            throw new ServiceException("修改失败, ID不能为空");
        }
        validateRegion(customerRegions);
        if (customerRegions.getId().equals(customerRegions.getParentId())) {
            throw new ServiceException("修改失败, ä¸Šçº§åœ°åŒºä¸èƒ½æ˜¯è‡ªå·±");
        }
        checkUnique(customerRegions);
        if (baseMapper.updateById(customerRegions) <= 0) {
            throw new ServiceException("修改失败");
        }
    }
    /**
     * åˆ é™¤åœ°åŒº
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delCustomerRegions(Long id) {
        if (id == null) {
            throw new ServiceException("删除失败, ID不能为空");
        }
        boolean hasChildren = baseMapper.selectCount(new LambdaQueryWrapper<CustomerRegions>()
                .eq(CustomerRegions::getParentId, id)) > 0;
        if (hasChildren) {
            throw new ServiceException("删除失败, è¯¥åœ°åŒºä¸‹å­˜åœ¨å­åœ°åŒº");
        }
        boolean isUsedByCustomer = customerMapper.selectCount(new LambdaQueryWrapper<Customer>()
                .eq(Customer::getRegionsId, id)) > 0;
        if (isUsedByCustomer) {
            throw new ServiceException("删除失败, è¯¥åœ°åŒºå·²è¢«å®¢æˆ·æ¡£æ¡ˆå¼•用");
        }
        if (baseMapper.deleteById(id) <= 0) {
            throw new ServiceException("删除失败, æ•°æ®ä¸å­˜åœ¨");
        }
    }
    /**
     * æŸ¥è¯¢å½“前地区及其所有子集的 ID é›†åˆ
     */
    @Override
    public List<Long> regionsChildrenIds(Long regionsId) {
        List<Long> childIds = new ArrayList<>();
        if (regionsId == null) {
            return childIds;
        }
        List<CustomerRegions> allRegions = baseMapper.selectList(null);
        if (allRegions == null || allRegions.isEmpty()) {
            return childIds;
        }
        childIds.add(regionsId);
        findAllChildren(allRegions, regionsId, childIds);
        return childIds;
    }
    /**
     * é€’归地区方法
     *
     * @param allRegions æ‰€æœ‰åœ°åŒºåˆ—表
     * @param parentId   å½“前父级 ID
     * @param result     å­˜æ”¾ç»“果的集合
     */
    private void findAllChildren(List<CustomerRegions> allRegions, Long parentId, List<Long> result) {
        for (CustomerRegions region : allRegions) {
            // åˆ¤æ–­ parentId æ˜¯å¦åŒ¹é…
            if (parentId.equals(region.getParentId())) {
                result.add(region.getId());
                // ç»§ç»­é€’归查找该节点的子节点
                findAllChildren(allRegions, region.getId(), result);
            }
        }
    }
    private void validateRegion(CustomerRegions region) {
        if (region == null) {
            throw new ServiceException("操作失败, æ•°æ®ä¸èƒ½ä¸ºç©º");
        }
        if (StringUtils.isEmpty(region.getRegionsName())) {
            throw new ServiceException("操作失败, åœ°åŒºåç§°ä¸èƒ½ä¸ºç©º");
        }
        if (region.getParentId() == null) {
            region.setParentId(0L);
        }
    }
    private void checkUnique(CustomerRegions region) {
        CustomerRegions existing = baseMapper.selectOne(new LambdaQueryWrapper<CustomerRegions>()
                .eq(CustomerRegions::getRegionsName, region.getRegionsName())
                .eq(CustomerRegions::getParentId, region.getParentId())
                .ne(region.getId() != null, CustomerRegions::getId, region.getId()));
        if (existing != null) {
            throw new ServiceException("该层级下已存在名为 [" + region.getRegionsName() + "] çš„地区");
        }
    }
}
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -1,8 +1,6 @@
package com.ruoyi.basic.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -15,16 +13,13 @@
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.CustomerFollowUp;
import com.ruoyi.basic.pojo.CustomerFollowUpFile;
import com.ruoyi.basic.service.CustomerFollowUpFileService;
import com.ruoyi.basic.service.CustomerFollowUpService;
import com.ruoyi.basic.service.CustomerReturnVisitService;
import com.ruoyi.basic.service.ICustomerService;
import com.ruoyi.basic.pojo.CustomerRegions;
import com.ruoyi.basic.service.*;
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.project.system.domain.SysUser;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import lombok.AllArgsConstructor;
@@ -35,9 +30,10 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
@@ -59,6 +55,8 @@
    private CustomerFollowUpFileService customerFollowUpFileService;
    private CustomerReturnVisitService customerReturnVisitService;
    private final ICustomerRegionsService customerRegionsService;
    /**
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆ
@@ -88,96 +86,112 @@
        BeanUtils.copyProperties(customer, dto);
        // æŸ¥è¯¢è·Ÿè¿›è®°å½•
        List<CustomerFollowUp> followUpList = customerFollowUpService.list(
                new LambdaQueryWrapper<CustomerFollowUp>()
                        .eq(CustomerFollowUp::getCustomerId, id)
                        .orderByDesc(CustomerFollowUp::getFollowUpTime)
        );
        if (!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());
            dto.setFollowUpList(followUpDtoList);
//        List<CustomerFollowUp> followUpList = customerFollowUpService.list(
//                new LambdaQueryWrapper<CustomerFollowUp>()
//                        .eq(CustomerFollowUp::getCustomerId, id)
//                        .orderByDesc(CustomerFollowUp::getFollowUpTime)
//        );
//
//        if (!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());
//
//            dto.setFollowUpList(followUpDtoList);
//        }
        //  åœ°åŒºåç§°
        CustomerRegions customerRegions = customerRegionsService.getById(customer.getRegionsId());
        if (customerRegions != null) {
            dto.setRegionsName(customerRegions.getRegionsName());
        }
        return dto;
    }
    /**
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆåˆ—表
     *
     * @param customer å®¢æˆ·æ¡£æ¡ˆ
     * @return å®¢æˆ·æ¡£æ¡ˆ
     * @param page     åˆ†é¡µå¯¹è±¡
     * @param customer å®¢æˆ·æŸ¥è¯¢æ¡ä»¶
     * @return å®¢æˆ·æ¡£æ¡ˆåˆ†é¡µåˆ—表
     */
    @Override
    public IPage<Customer> selectCustomerList(Page<Customer> page, Customer customer) {
        // 1. å¤„理空值场景(参数校验)
        if (page == null) {
            page = Page.of(1, 10); // é»˜è®¤ç¬¬1页,每页10条数据
        }
        if (customer == null) {
            customer = new Customer(); // é¿å…ç©ºå¯¹è±¡å¯¼è‡´çš„NPE
        }
    public IPage<CustomerDto> selectCustomerList(Page<Customer> page, Customer customer) {
        if (page == null) page = Page.of(1, 10);
        if (customer == null) customer = new Customer();
        // 2. æž„建查询条件(增强空值安全)
        LambdaQueryWrapper<Customer> queryWrapper = new LambdaQueryWrapper<>();
        String customerName = customer.getCustomerName();
        String customerType = customer.getCustomerType();
        Long regionsId = customer.getRegionsId();
        if (StringUtils.isNotBlank(customerName)) {
            queryWrapper.like(Customer::getCustomerName, customerName);
        }
        if (StringUtils.isNotBlank(customerType)) {
            queryWrapper.like(Customer::getCustomerType, customerType);
            queryWrapper.eq(Customer::getCustomerType, customerType);
        }
        // 3. æ‰§è¡Œåˆ†é¡µæŸ¥è¯¢ï¼ˆä¿ç•™åˆ†é¡µå…ƒæ•°æ®ï¼‰
        if (regionsId != null) {
            // è°ƒç”¨ regionsService èŽ·å–å½“å‰åœ°åŒºåŠå…¶æ‰€æœ‰åŽä»£çš„ ID é›†åˆ
            List<Long> allRegionsIds = customerRegionsService.regionsChildrenIds(regionsId);
            if (!CollectionUtils.isEmpty(allRegionsIds)) {
                queryWrapper.in(Customer::getRegionsId, allRegionsIds);
            } else {
                queryWrapper.eq(Customer::getRegionsId, regionsId);
            }
        }
        IPage<Customer> customerPage = customerMapper.selectPage(page, queryWrapper);
        // 4. æ•°æ®å¤„理(增强空值安全 & ä»£ç å¯è¯»æ€§ï¼‰
        List<Customer> processedList = customerPage.getRecords().stream()
                .filter(Objects::nonNull) // è¿‡æ»¤ç©ºå¯¹è±¡ï¼ˆé¿å…åŽç»­æ“ä½œNPE)
                .peek(c -> {
                    // å®‰å…¨èŽ·å–å­—æ®µï¼Œé¿å…null值拼接
        List<CustomerDto> dtoList = customerPage.getRecords().stream()
                .filter(Objects::nonNull)
                .map(c -> {
                    CustomerDto dto = new CustomerDto();
                    BeanUtils.copyProperties(c, dto);
                    // åœ°å€ç”µè¯æ‹¼æŽ¥
                    String address = StringUtils.defaultString(c.getCompanyAddress(), "");
                    String phone = StringUtils.defaultString(c.getCompanyPhone(), "");
                    c.setAddressPhone(address + "(" + phone + ")");
                    dto.setAddressPhone(address + "(" + phone + ")");
                    // å¡«å……地区名称
                    if (c.getRegionsId() != null) {
                        CustomerRegions regions = customerRegionsService.getById(c.getRegionsId());
                        if (regions != null) {
                            dto.setRegionsName(regions.getRegionsName());
                        }
                    }
                    // æŸ¥è¯¢æœ€æ–°çš„跟进记录
                    CustomerFollowUp followUp = customerFollowUpService.getOne(
                            new LambdaQueryWrapper<CustomerFollowUp>()
                                    .eq(CustomerFollowUp::getCustomerId, c.getId())
                                    .orderByDesc(CustomerFollowUp::getFollowUpTime)
                                    .last("LIMIT 1")
                    );
                    if (followUp != null) {
                        c.setFollowUpLevel(followUp.getFollowUpLevel());
                        c.setFollowUpTime(
                                Date.from(
                                        followUp.getFollowUpTime().atZone(ZoneId.systemDefault()).toInstant()
                                )
                        );
                    }
//                    CustomerFollowUp followUp = customerFollowUpService.getOne(
//                            new LambdaQueryWrapper<CustomerFollowUp>()
//                                    .eq(CustomerFollowUp::getCustomerId, c.getId())
//                                    .orderByDesc(CustomerFollowUp::getFollowUpTime)
//                                    .last("LIMIT 1")
//                    );
//
//                    if (followUp != null) {
//                        dto.setFollowUpLevel(followUp.getFollowUpLevel());
//                        dto.setFollowUpTime(Date.from(followUp.getFollowUpTime().atZone(ZoneId.systemDefault()).toInstant()));
//                    }
                    return dto;
                })
                .collect(Collectors.toList());
        // 5. æ›´æ–°åˆ†é¡µç»“果中的数据(保持分页信息完整)
        IPage<Customer> resultPage = new Page<>(customerPage.getCurrent(), customerPage.getSize(), customerPage.getTotal());
        resultPage.setRecords(processedList);
        IPage<CustomerDto> resultPage = new Page<>(customerPage.getCurrent(), customerPage.getSize(), customerPage.getTotal());
        resultPage.setRecords(dtoList);
        return customerPage; // è¿”回包含分页信息的IPage对象
        return resultPage;
    }
    /**
@@ -274,17 +288,6 @@
                        entry -> underlineToCamel(entry.getKey()),
                        Map.Entry::getValue))
        ).collect(Collectors.toList());
    }
    @Override
    public List<String> regionsList() {
        return this.baseMapper.selectObjs(new QueryWrapper<Customer>()
                        .select("DISTINCT regions")
                        .isNotNull("regions")
                        .ne("regions", "")
                ).stream()
                .map(Object::toString)
                .collect(Collectors.toList());
    }
    /**
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -11,8 +11,7 @@
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.page.TableDataInfo;
import com.ruoyi.sales.dto.InvoiceLedgerDto;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.dto.*;
import com.ruoyi.sales.mapper.InvoiceLedgerMapper;
import com.ruoyi.sales.mapper.ReceiptPaymentMapper;
import com.ruoyi.sales.pojo.ReceiptPayment;
@@ -360,4 +359,40 @@
    public R getSalesLedgerWithProductsLoss(Long salesLedgerId) {
        return R.ok(salesLedgerService.getSalesLedgerWithProductsLoss(salesLedgerId));
    }
    @ApiOperation("获取销售订单绑定的工艺路线")
    @GetMapping("/salesProcess/{salesLedgerId}")
    public AjaxResult salesProcess(@PathVariable Long salesLedgerId) {
        SalesLedgerProcessRouteDto dto = salesLedgerService.salesProcess(salesLedgerId);
        return AjaxResult.success(dto);
    }
    @GetMapping("/processCard/{salesLedgerId}")
    @ApiOperation("打印生产流程卡")
    public AjaxResult processCard(@PathVariable Long salesLedgerId) {
        SalesProcessCardDto dto = salesLedgerService.processCard(salesLedgerId);
        return AjaxResult.success(dto);
    }
    @GetMapping("/salesOrders/{salesLedgerId}")
    @ApiOperation("打印销售订单")
    public AjaxResult salesOrders(@PathVariable Long salesLedgerId) {
        SalesOrdersDto salesOrdersDto = salesLedgerService.salesOrders(salesLedgerId);
        return AjaxResult.success(salesOrdersDto);
    }
    @PostMapping("/salesInvoices")
    @ApiOperation("打印销售发货单")
    public AjaxResult salesInvoices(@RequestBody List<Long> salesLedgerIds) {
        SalesInvoicesDto dto = salesLedgerService.salesInvoices(salesLedgerIds);
        return AjaxResult.success(dto);
    }
    @GetMapping("/salesLabel/{salesLedgerId}")
    @ApiOperation("打印订单标签")
    public AjaxResult salesLabel(@PathVariable Long salesLedgerId) {
        List<SalesLabelDto> list = salesLedgerService.salesLabel(salesLedgerId);
        return AjaxResult.success(list);
    }
}
src/main/java/com/ruoyi/sales/dto/SalesInvoicesDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
package com.ruoyi.sales.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/**
 * <br>
 * æ‰“印销售发货单Dto
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/27 15:30
 */
@Data
public class SalesInvoicesDto {
    @ApiModelProperty("客户名称")
    private String customerName;
    @ApiModelProperty("联系人")
    private String contactPerson;
    @ApiModelProperty("联系电话")
    private String contactPhone;
    @ApiModelProperty("送货地址")
    private String companyAddress;
    @ApiModelProperty("发货单号")
    private String deliveryNo;
    @ApiModelProperty("对方单号")
    private String externalOrderNo;
    @ApiModelProperty("总合计数量")
    private BigDecimal totalQuantity;
    @ApiModelProperty("总合计面积")
    private BigDecimal totalArea;
    @ApiModelProperty("制单员")
    private String orderMaker;
    @ApiModelProperty("制单日期")
    @JsonFormat(pattern = "yyyy/MM/dd")
    private LocalDateTime executionDate;
    @ApiModelProperty("发货订单分组")
    private List<InvoiceOrderGroupDto> groups;
    /**
     * è®¢å•分组 DTO
     */
    @Data
    public static class InvoiceOrderGroupDto {
        @ApiModelProperty("订单编号")
        private String salesContractNo;
        @ApiModelProperty("产品名称")
        private String productName;
        @ApiModelProperty("明细列表")
        private List<InvoiceItemDto> items;
        @ApiModelProperty("小计数量")
        private BigDecimal groupTotalQuantity;
        @ApiModelProperty("小计面积")
        private BigDecimal groupTotalArea;
    }
    /**
     * å‘货单明细 DTO
     */
    @Data
    public static class InvoiceItemDto {
        @ApiModelProperty("楼层编号")
        private String floorCode;
        @ApiModelProperty("宽*高")
        private String widthHeight;
        @ApiModelProperty("数量")
        private BigDecimal quantity;
        @ApiModelProperty("面积")
        private BigDecimal area;
        @ApiModelProperty("备注")
        private String remark;
        @ApiModelProperty("加工要求")
        private String processRequirement;
    }
}
src/main/java/com/ruoyi/sales/dto/SalesLabelDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package com.ruoyi.sales.dto;
/**
 * <br>
 *
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/27 15:55
 */
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * <br>
 * é”€å”®æ ‡ç­¾Dto
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/27 15:55
 */
@Data
public class SalesLabelDto {
    @ApiModelProperty("客户名称")
    private String customerName;
    @ApiModelProperty("销售订单编号")
    private String salesContractNo;
    @ApiModelProperty("产品名称")
    private String productName;
    @ApiModelProperty("规格(宽*高=数量)")
    private String specification;
    @ApiModelProperty("楼层编号/地址")
    private String floorCode;
}
src/main/java/com/ruoyi/sales/dto/SalesLedgerProcessRouteDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.ruoyi.sales.dto;
import com.ruoyi.sales.pojo.SalesLedgerProcessRoute;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
 * <br>
 * é”€å”®è®¢å•绑定的工艺路线Dto
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/27 9:54
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class SalesLedgerProcessRouteDto extends SalesLedgerProcessRoute {
    @ApiModelProperty("工艺路线ID")
    private Long routeId;
    @ApiModelProperty(value = "路线名称")
    private String routeName;
    @ApiModelProperty("销售订单绑定的工艺路线")
    List<SalesLedgerProcessRoute> list;
}
src/main/java/com/ruoyi/sales/dto/SalesOrdersDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,158 @@
package com.ruoyi.sales.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
 * <br>
 * é”€å”®è®¢å•打印Dto
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/27 14:36
 */
@Data
public class SalesOrdersDto {
    @ApiModelProperty("客户名称")
    private String customerName;
    @ApiModelProperty("项目名称")
    private String projectName;
    @ApiModelProperty("产品名称")
    private String productName;
    @ApiModelProperty("业务员")
    private String salesman;
    @ApiModelProperty("制单日期")
    @JsonFormat(pattern = "yyyy/MM/dd")
    private LocalDateTime executionDate;
    @ApiModelProperty("交货日期")
    @JsonFormat(pattern = "yyyy/MM/dd")
    private LocalDate deliveryDate;
    @ApiModelProperty("送货地址")
    private String companyAddress;
    @ApiModelProperty("订单编号")
    private String salesContractNo;
    @ApiModelProperty("产品明细")
    private List<SalesOrderItemDto> items;
    @ApiModelProperty("小计数量")
    private BigDecimal subtotalQuantity;
    @ApiModelProperty("小计面积")
    private BigDecimal subtotalArea;
    @ApiModelProperty("小计金额")
    private BigDecimal subtotalAmount;
    @ApiModelProperty("合计数量")
    private BigDecimal totalQuantity;
    @ApiModelProperty("合计面积")
    private BigDecimal totalArea;
    @ApiModelProperty("合计金额")
    private BigDecimal totalAmount;
    @ApiModelProperty("总金额")
    private String totalAmountDisplay;
    @ApiModelProperty("加工要求和备注")
    private String remakes;
    @ApiModelProperty("制单员")
    private String orderMaker;
    @ApiModelProperty("审核员")
    private String auditor;
    @ApiModelProperty("打印人")
    private String printPeople;
    @ApiModelProperty("审核日期")
    @JsonFormat(pattern = "yyyy/MM/dd")
    private LocalDateTime auditDate;
    @ApiModelProperty("制单日期(底部)")
    @JsonFormat(pattern = "yyyy/MM/dd")
    private LocalDateTime orderMakerDate;
    @ApiModelProperty("打印时间")
    @JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss")
    private LocalDateTime printTime;
    @ApiModelProperty("公司名称")
    private String companyName;
    @ApiModelProperty("其他费用明细")
    private List<OtherFeeDto> otherFees;
    /**
     * é”€å”®è®¢å•明细 DTO
     */
    @Data
    public static class SalesOrderItemDto {
        @ApiModelProperty("楼层编号")
        private String floorCode;
        @ApiModelProperty("产品描述")
        private String productDescription;
        @ApiModelProperty("宽(弧长)")
        private BigDecimal width;
        @ApiModelProperty("高")
        private BigDecimal height;
        @ApiModelProperty("数量")
        private BigDecimal quantity;
        @ApiModelProperty("结算面积(㎡)")
        private BigDecimal area;
        @ApiModelProperty("单价")
        private BigDecimal unitPrice;
        @ApiModelProperty("金额")
        private BigDecimal amount;
        @ApiModelProperty("备注")
        private String remark;
        @ApiModelProperty("加工要求")
        private String processRequirement;
    }
    /**
     * å…¶ä»–费用 DTO
     */
    @Data
    public static class OtherFeeDto {
        @ApiModelProperty("费用名称")
        private String feeName;
        @ApiModelProperty("单价")
        private BigDecimal unitPrice;
        @ApiModelProperty("数量")
        private BigDecimal quantity;
        @ApiModelProperty("金额")
        private BigDecimal amount;
    }
}
src/main/java/com/ruoyi/sales/dto/SalesProcessCardDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
package com.ruoyi.sales.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
 * é”€å”®è®¢å•流程卡 DTO
 * æ•´åˆäº†å°è´¦ä¿¡æ¯ã€äº§å“åˆ—表以及绑定的工艺路线
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/03/26
 */
@Data
public class SalesProcessCardDto {
    @ApiModelProperty("订单编号")
    private String salesContractNo;
    @ApiModelProperty("客户名称")
    private String customerName;
    @ApiModelProperty("交货日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private LocalDate deliveryDate;
    @ApiModelProperty("工艺路线总览")
    private String processPathDisplay;
    @ApiModelProperty("订单加工要求")
    private String orderProcessRequirement;
    @ApiModelProperty("该绑定的工艺路线节点")
    private List<ProcessNodeDto> routeNodes;
    @ApiModelProperty("流程卡行明细")
    private List<ProcessCardItemDto> items;
    //  åº•部信息
    @ApiModelProperty("总合计数量")
    private BigDecimal totalQuantity;
    @ApiModelProperty("总合计面积")
    private BigDecimal totalArea;
    @ApiModelProperty("制单员")
    private String register;
    @ApiModelProperty("制单日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime registerDate;
    /**
     * å†…部类:对应产品表格行
     */
    @Data
    public static class ProcessCardItemDto {
        @ApiModelProperty("楼层编号")
        private String floorCode;
        @ApiModelProperty("产品描述")
        private String productDescription;
        @ApiModelProperty("宽(弧长)")
        private BigDecimal width;
        @ApiModelProperty("高")
        private BigDecimal height;
        @ApiModelProperty("数量")
        private BigDecimal quantity;
        @ApiModelProperty("面积(㎡)")
        private BigDecimal area;
        @ApiModelProperty("明细加工要求")
        private String processRequirement;
        @ApiModelProperty("额外加工明细")
        private List<String> extraProcesses;
    }
    /**
     * å†…部类:对应 SalesLedgerProcessRoute çš„节点信息
     */
    @Data
    public static class ProcessNodeDto {
        @ApiModelProperty("工艺节点ID")
        private Long processRouteItemId;
        @ApiModelProperty("工艺节点名称")
        private String processRouteItemName;
        @ApiModelProperty("排序号")
        private Integer dragSort;
        @ApiModelProperty("备注")
        private String remark;
    }
}
src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
@@ -10,6 +10,7 @@
import com.ruoyi.sales.dto.SalesTrendDto;
import com.ruoyi.sales.dto.StatisticsTableDto;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerProcessRoute;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@@ -85,4 +86,7 @@
    List<SalesTrendDto> statisticsTable(@Param("statisticsTableDto")StatisticsTableDto statisticsTableDto);
    IPage<SalesLedgerDto> listSalesLedgerAndShipped(Page page, @Param("ew") SalesLedgerDto salesLedgerDto);
    List<SalesLedgerProcessRoute> selectSalesProcess(Long salesLedgerId);
}
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProcessRoute.java
@@ -55,5 +55,8 @@
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(exist = false)
    @ApiModelProperty(value = "工序名称")
    private String processName;
}
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
@@ -6,9 +6,7 @@
import com.ruoyi.aftersalesservice.pojo.AfterSalesService;
import com.ruoyi.common.enums.SaleEnum;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.dto.LossProductModelDto;
import com.ruoyi.sales.dto.MonthlyAmountDto;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.dto.*;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerProcessRoute;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
@@ -33,7 +31,8 @@
    int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto);
    List<SalesLedgerProduct> getSalesLedgerProductListByRelateId(@NotNull Long relateId,@NotNull SaleEnum type);
    List<SalesLedgerProduct> getSalesLedgerProductListByRelateId(@NotNull Long relateId, @NotNull SaleEnum type);
    List<SalesLedgerProduct> getSalesLedgerProductListByIds(@Nullable List<Long> relateIds, @NotNull SaleEnum type);
    void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, SaleEnum type);
@@ -57,4 +56,14 @@
    IPage<SalesLedgerDto> listSalesLedger(SalesLedgerDto salesLedgerDto, Page page);
    void saleProcessBind(SalesLedgerProcessRoute salesLedgerProcessRoute);
    SalesProcessCardDto processCard(Long salesLedgerId);
    SalesLedgerProcessRouteDto salesProcess(Long salesLedgerId);
    SalesOrdersDto salesOrders(Long salesLedgerId);
    SalesInvoicesDto salesInvoices(List<Long> salesLedgerIds);
    List<SalesLabelDto> salesLabel(Long salesLedgerId);
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -4,7 +4,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -13,6 +13,8 @@
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.CustomerRegions;
import com.ruoyi.basic.service.ICustomerRegionsService;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.SaleEnum;
import com.ruoyi.common.exception.ServiceException;
@@ -30,7 +32,7 @@
import com.ruoyi.production.pojo.ProcessRoute;
import com.ruoyi.production.pojo.ProcessRouteItem;
import com.ruoyi.production.service.ProductionProductMainService;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.mapper.SysUserMapper;
@@ -141,6 +143,8 @@
    ;
    @Autowired
    private SysUserMapper sysUserMapper;
    private final ICustomerRegionsService customerRegionsService;
    @Override
    public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) {
@@ -543,7 +547,7 @@
            throw new ServiceException("绑定失败,工艺路线不存在");
        }
        //  æ¸…除已绑定的数据
        salesLedgerProcessRouteService.remove(new LambdaQueryWrapper<SalesLedgerProcessRoute>().eq(SalesLedgerProcessRoute::getSalesLedgerId, salesLedger.getId()).eq(SalesLedgerProcessRoute::getProcessRouteId, processRoute.getId()));
        salesLedgerProcessRouteService.remove(new LambdaQueryWrapper<SalesLedgerProcessRoute>().eq(SalesLedgerProcessRoute::getSalesLedgerId, salesLedger.getId()));
        //  å°†æ•°æ®è¿ç§»åˆ°sales_ledger_process_route
        List<ProcessRouteItem> routeItems = processRouteItemMapper.selectList(new LambdaQueryWrapper<ProcessRouteItem>().eq(ProcessRouteItem::getRouteId, processRoute.getId()));
@@ -554,6 +558,7 @@
            ledgerProcessRoute.setProcessRouteId(processRoute.getId());
            ledgerProcessRoute.setSalesLedgerId(salesLedger.getId());
            ledgerProcessRoute.setProcessRouteItemId(routeItem.getId());
            ledgerProcessRoute.setDragSort(routeItem.getDragSort());
            salesLedgerProcessRouteList.add(ledgerProcessRoute);
        }
        salesLedgerProcessRouteService.saveBatch(salesLedgerProcessRouteList);
@@ -844,19 +849,20 @@
                }
            }
            if (!redisTemplate.hasKey(lockKey)) {
            if (Boolean.FALSE.equals(redisTemplate.hasKey(lockKey))) {
                throw new RuntimeException("获取合同编号生成锁失败:超时");
            }
            // 2. æŸ¥è¯¢å½“天/公司已存在的序列号(与原逻辑一致)
            Long tenantId = SecurityUtils.getLoginUser().getTenantId();
            if (null != tenantId) {
                //获取公司编号
                SysDept sysDept = sysDeptMapper.selectDeptById(tenantId.longValue());
                if (!ObjectUtils.isEmpty(sysDept)) {
                    datePart = (StringUtils.isEmpty(sysDept.getDeptNick()) ? "" : sysDept.getDeptNick()) + datePart;
                }
            }
//            Long tenantId = SecurityUtils.getLoginUser().getTenantId();
//            if (null != tenantId) {
//                //获取公司编号
//                SysDept sysDept = sysDeptMapper.selectDeptById(tenantId);
//                if (!ObjectUtils.isEmpty(sysDept)) {
//                    datePart = (StringUtils.isEmpty(sysDept.getDeptNick()) ? "" : sysDept.getDeptNick()) + datePart;
//                }
//            }
            datePart = "D" + datePart;
            List<Integer> existingSequences = salesLedgerMapper.selectSequencesByDate(datePart);
            int nextSequence = findFirstMissingSequence(existingSequences);
@@ -872,6 +878,490 @@
        }
    }
    @Override
    public SalesLedgerProcessRouteDto salesProcess(Long salesLedgerId) {
        SalesLedgerProcessRouteDto dto = new SalesLedgerProcessRouteDto();
        List<SalesLedgerProcessRoute> list = baseMapper.selectSalesProcess(salesLedgerId);
        if (CollectionUtils.isNotEmpty(list)) {
            Long processRouteId = list.get(0).getProcessRouteId();
            ProcessRoute processRoute = processRouteMapper.selectById(processRouteId);
            if (processRoute != null) {
                dto.setRouteId(processRoute.getId());
                dto.setRouteName(processRoute.getProcessRouteName());
            }
        } else {
            //  è¦æ˜¯list查询为空的话,就查询默认的工艺路线返回
            ProcessRoute defaultRoute = processRouteMapper.selectOne(new LambdaQueryWrapper<ProcessRoute>().eq(ProcessRoute::getIsDefault, 1).last("limit 1"));
            if (defaultRoute != null) {
                dto.setRouteId(defaultRoute.getId());
                dto.setRouteName(defaultRoute.getProcessRouteName());
                List<ProcessRouteItem> routeItems = processRouteItemMapper.selectList(new LambdaQueryWrapper<ProcessRouteItem>().eq(ProcessRouteItem::getRouteId, defaultRoute.getId()).orderByAsc(ProcessRouteItem::getDragSort));
                list = routeItems.stream().map(item -> {
                    SalesLedgerProcessRoute salesLedgerProcessRoute = new SalesLedgerProcessRoute();
                    salesLedgerProcessRoute.setProcessRouteId(defaultRoute.getId());
                    salesLedgerProcessRoute.setSalesLedgerId(salesLedgerId);
                    salesLedgerProcessRoute.setProcessRouteItemId(item.getId());
                    salesLedgerProcessRoute.setProcessName(item.getProcessName());
                    salesLedgerProcessRoute.setDragSort(item.getDragSort());
                    return salesLedgerProcessRoute;
                }).collect(Collectors.toList());
            }
        }
        dto.setList(list);
        return dto;
    }
    @Override
    public SalesProcessCardDto processCard(Long salesLedgerId) {
        if (salesLedgerId == null) {
            throw new ServiceException("流程卡打印失败,打印销售订单不能为空");
        }
        //  æŸ¥è¯¢é”€å”®è®¢å•
        SalesLedger salesLedger = baseMapper.selectById(salesLedgerId);
        if (salesLedger == null) {
            throw new ServiceException("流程卡打印失败,销售订单不存在");
        }
        SalesProcessCardDto dto = new SalesProcessCardDto();
        dto.setSalesContractNo(salesLedger.getSalesContractNo());
        dto.setCustomerName(salesLedger.getCustomerName());
        dto.setDeliveryDate(salesLedger.getDeliveryDate());
        dto.setRegister(SecurityUtils.getLoginUser().getUser().getNickName());
        dto.setRegisterDate(LocalDateTime.now());
        dto.setOrderProcessRequirement(salesLedger.getRemarks());
        //  æŸ¥è¯¢äº§å“åˆ—表
        List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(
                new LambdaQueryWrapper<SalesLedgerProduct>().eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerId));
        BigDecimal totalQuantity = BigDecimal.ZERO;
        BigDecimal totalArea = BigDecimal.ZERO;
        List<SalesProcessCardDto.ProcessCardItemDto> itemDtos = new ArrayList<>();
        for (SalesLedgerProduct p : products) {
            SalesProcessCardDto.ProcessCardItemDto itemDto = new SalesProcessCardDto.ProcessCardItemDto();
            itemDto.setFloorCode(p.getFloorCode());
            // ç»„装产品描述:大类 + (规格)
            String desc = (p.getProductCategory() != null ? p.getProductCategory() : "") +
                    (StringUtils.isNotBlank(p.getSpecificationModel()) ? " " + p.getSpecificationModel() : "");
            itemDto.setProductDescription(desc.trim());
            itemDto.setWidth(p.getWidth());
            itemDto.setHeight(p.getHeight());
            itemDto.setQuantity(p.getQuantity());
            // é¢ç§¯è®¡ç®—(平米)
            BigDecimal area = p.getActualPieceArea() != null ? p.getActualPieceArea() : p.getSettlePieceArea();
            if (area == null && p.getWidth() != null && p.getHeight() != null) {
                area = p.getWidth().multiply(p.getHeight()).divide(new BigDecimal(1000000), 2, RoundingMode.HALF_UP);
            }
            itemDto.setArea(area);
            itemDto.setProcessRequirement(p.getProcessRequirement());
            BigDecimal qty = p.getQuantity() != null ? p.getQuantity() : BigDecimal.ZERO;
            totalQuantity = totalQuantity.add(qty);
            if (area != null) {
                totalArea = totalArea.add(area.multiply(qty));
            }
            itemDtos.add(itemDto);
        }
        dto.setItems(itemDtos);
        dto.setTotalQuantity(totalQuantity);
        dto.setTotalArea(totalArea.setScale(2, RoundingMode.HALF_UP));
        //  å·¥è‰ºè·¯çº¿
        List<SalesLedgerProcessRoute> salesLedgerProcessRoutes = salesLedgerProcessRouteService.list(
                new LambdaQueryWrapper<SalesLedgerProcessRoute>()
                        .eq(SalesLedgerProcessRoute::getSalesLedgerId, salesLedgerId)
                        .orderByAsc(SalesLedgerProcessRoute::getDragSort));
        List<SalesProcessCardDto.ProcessNodeDto> nodeDtos = new ArrayList<>();
        if (CollectionUtils.isEmpty(salesLedgerProcessRoutes)) {
            // æ— è‡ªå®šä¹‰è·¯çº¿ï¼Œå–默认路线
            ProcessRoute defaultRoute = processRouteMapper.selectOne(
                    new LambdaQueryWrapper<ProcessRoute>().eq(ProcessRoute::getIsDefault, 1).last("LIMIT 1"));
            if (defaultRoute != null) {
                List<ProcessRouteItem> routeItems = processRouteItemMapper.selectList(
                        new LambdaQueryWrapper<ProcessRouteItem>()
                                .eq(ProcessRouteItem::getRouteId, defaultRoute.getId())
                                .orderByAsc(ProcessRouteItem::getDragSort));
                for (ProcessRouteItem i : routeItems) {
                    SalesProcessCardDto.ProcessNodeDto node = new SalesProcessCardDto.ProcessNodeDto();
                    node.setProcessRouteItemId(i.getId());
                    node.setProcessRouteItemName(i.getProcessName());
                    node.setDragSort(i.getDragSort());
                    nodeDtos.add(node);
                }
            }
        } else {
            // ä½¿ç”¨è‡ªå®šä¹‰è·¯çº¿ç»‘定的节点
            List<Long> itemIds = salesLedgerProcessRoutes.stream()
                    .map(SalesLedgerProcessRoute::getProcessRouteItemId)
                    .collect(Collectors.toList());
            List<ProcessRouteItem> rawItems = processRouteItemMapper.selectBatchIds(itemIds);
            Map<Long, ProcessRouteItem> itemMap = rawItems.stream()
                    .collect(Collectors.toMap(ProcessRouteItem::getId, i -> i, (a, b) -> a));
            for (SalesLedgerProcessRoute r : salesLedgerProcessRoutes) {
                ProcessRouteItem pi = itemMap.get(r.getProcessRouteItemId());
                if (pi != null) {
                    SalesProcessCardDto.ProcessNodeDto node = new SalesProcessCardDto.ProcessNodeDto();
                    node.setProcessRouteItemId(pi.getId());
                    node.setProcessRouteItemName(pi.getProcessName());
                    node.setDragSort(r.getDragSort() != null ? r.getDragSort() : pi.getDragSort());
                    node.setRemark(r.getRemark());
                    nodeDtos.add(node);
                }
            }
        }
        if (!nodeDtos.isEmpty()) {
            //  dragSort è¿›è¡Œå‡åºæŽ’序
            nodeDtos.sort(Comparator.comparing(
                    SalesProcessCardDto.ProcessNodeDto::getDragSort,
                    Comparator.nullsLast(Comparator.naturalOrder())
            ));
            //  é‡æ–°ç”ŸæˆæŽ’序后的路径名称列表
            List<String> sortedPathNames = nodeDtos.stream()
                    .map(SalesProcessCardDto.ProcessNodeDto::getProcessRouteItemName)
                    .collect(Collectors.toList());
            //  æ‹¼æŽ¥å­—符串
            dto.setProcessPathDisplay(String.join(" -> ", sortedPathNames));
            // è®¾ç½®é¡¶å±‚节点的工艺路线
            dto.setRouteNodes(nodeDtos);
        }
        return dto;
    }
    @Override
    public SalesOrdersDto salesOrders(Long salesLedgerId) {
        if (salesLedgerId == null) {
            throw new ServiceException("打印销售订单失败,销售订单ID不能为空");
        }
        SalesLedger salesLedger = baseMapper.selectById(salesLedgerId);
        if (salesLedger == null) {
            throw new ServiceException("打印销售订单失败,销售订单不存在");
        }
        SalesOrdersDto dto = new SalesOrdersDto();
        dto.setSalesContractNo(salesLedger.getSalesContractNo());
        dto.setCustomerName(salesLedger.getCustomerName());
        dto.setProjectName(salesLedger.getProjectName());
        dto.setSalesman(salesLedger.getSalesman());
        dto.setExecutionDate(salesLedger.getExecutionDate() != null ? salesLedger.getExecutionDate().atStartOfDay() : null);
        dto.setDeliveryDate(salesLedger.getDeliveryDate());
        dto.setRemakes(salesLedger.getRemarks());
        dto.setCompanyName("鹤壁天沐钢化玻璃厂");
        // é€è´§åœ°å€
        if (salesLedger.getCustomerId() != null) {
            Customer customer = customerMapper.selectById(salesLedger.getCustomerId());
            if (customer != null) {
                StringBuilder address = new StringBuilder();
                if (customer.getRegionsId() != null) {
                    CustomerRegions regions = customerRegionsService.getById(customer.getRegionsId());
                    if (regions != null) {
                        address.append(regions.getRegionsName());
                    }
                }
                if (StringUtils.isNotEmpty(customer.getCompanyAddress())) {
                    address.append(customer.getCompanyAddress());
                }
                dto.setCompanyAddress(address.toString());
            }
        }
        // åˆ¶å•员
        if (StringUtils.isNotEmpty(salesLedger.getEntryPerson())) {
            try {
                SysUser user = sysUserMapper.selectUserById(Long.parseLong(salesLedger.getEntryPerson()));
                if (user != null) {
                    dto.setOrderMaker(user.getNickName());
                }
            } catch (Exception e) {
                log.error("获取制单员信息失败: {}", e.getMessage());
            }
        }
        // åˆ¶å•日期 (底部)
        dto.setOrderMakerDate(salesLedger.getExecutionDate() != null ? salesLedger.getExecutionDate().atStartOfDay() : null);
        // æ‰“印信息
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser != null && loginUser.getUser() != null) {
            dto.setPrintPeople(loginUser.getUser().getNickName());
        }
        dto.setPrintTime(LocalDateTime.now());
        // æŸ¥è¯¢äº§å“åˆ—表
        List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(
                new LambdaQueryWrapper<SalesLedgerProduct>().eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerId));
        if (CollectionUtils.isNotEmpty(products)) {
            SalesLedgerProduct firstProduct = products.get(0);
            dto.setProductName(firstProduct.getProductCategory() != null ? firstProduct.getProductCategory() : "");
        }
        List<SalesOrdersDto.SalesOrderItemDto> itemDtos = new ArrayList<>();
        BigDecimal subtotalQuantity = BigDecimal.ZERO;
        BigDecimal subtotalArea = BigDecimal.ZERO;
        BigDecimal subtotalAmount = BigDecimal.ZERO;
        for (SalesLedgerProduct p : products) {
            SalesOrdersDto.SalesOrderItemDto itemDto = new SalesOrdersDto.SalesOrderItemDto();
            itemDto.setFloorCode(p.getFloorCode());
            String desc = (p.getProductCategory() != null ? p.getProductCategory() : "") +
                    (StringUtils.isNotBlank(p.getSpecificationModel()) ? " " + p.getSpecificationModel() : "");
            itemDto.setProductDescription(desc.trim());
            itemDto.setWidth(p.getWidth());
            itemDto.setHeight(p.getHeight());
            itemDto.setQuantity(p.getQuantity());
            // é¢ç§¯è®¡ç®—
            BigDecimal area = p.getSettleTotalArea() != null ? p.getSettleTotalArea() : p.getActualTotalArea();
            if (area == null && p.getWidth() != null && p.getHeight() != null && p.getQuantity() != null) {
                area = p.getWidth().multiply(p.getHeight()).multiply(p.getQuantity()).divide(new BigDecimal(1000000), 2, RoundingMode.HALF_UP);
            }
            itemDto.setArea(area);
            itemDto.setUnitPrice(p.getTaxInclusiveUnitPrice());
            itemDto.setAmount(p.getTaxInclusiveTotalPrice());
            itemDto.setRemark(p.getRemark());
            itemDto.setProcessRequirement(p.getProcessRequirement());
            subtotalQuantity = subtotalQuantity.add(p.getQuantity() != null ? p.getQuantity() : BigDecimal.ZERO);
            subtotalArea = subtotalArea.add(area != null ? area : BigDecimal.ZERO);
            subtotalAmount = subtotalAmount.add(p.getTaxInclusiveTotalPrice() != null ? p.getTaxInclusiveTotalPrice() : BigDecimal.ZERO);
            itemDtos.add(itemDto);
        }
        dto.setItems(itemDtos);
        dto.setSubtotalQuantity(subtotalQuantity);
        dto.setSubtotalArea(subtotalArea.setScale(2, RoundingMode.HALF_UP));
        dto.setSubtotalAmount(subtotalAmount);
        // å¤„理其他费用
        List<Long> productIds = products.stream().map(SalesLedgerProduct::getId).collect(Collectors.toList());
        BigDecimal otherFeesTotal = BigDecimal.ZERO;
        if (CollectionUtils.isNotEmpty(productIds)) {
            List<SalesLedgerProductProcessBind> binds = salesLedgerProductProcessBindService.list(
                    new LambdaQueryWrapper<SalesLedgerProductProcessBind>().in(SalesLedgerProductProcessBind::getSalesLedgerProductId, productIds));
            if (CollectionUtils.isNotEmpty(binds)) {
                Map<Integer, Integer> processQuantityMap = binds.stream()
                        .collect(Collectors.groupingBy(SalesLedgerProductProcessBind::getSalesLedgerProductProcessId,
                                Collectors.summingInt(b -> b.getQuantity() != null ? b.getQuantity() : 0)));
                List<Integer> processIds = new ArrayList<>(processQuantityMap.keySet());
                List<SalesLedgerProductProcess> processes = salesLedgerProductProcessService.listByIds(processIds);
                List<SalesOrdersDto.OtherFeeDto> otherFeeDtos = new ArrayList<>();
                for (SalesLedgerProductProcess proc : processes) {
                    SalesOrdersDto.OtherFeeDto feeDto = new SalesOrdersDto.OtherFeeDto();
                    feeDto.setFeeName(proc.getProcessName());
                    feeDto.setUnitPrice(proc.getUnitPrice());
                    Integer qty = processQuantityMap.get(proc.getId());
                    feeDto.setQuantity(new BigDecimal(qty != null ? qty : 0));
                    BigDecimal amount = proc.getUnitPrice() != null ? proc.getUnitPrice().multiply(feeDto.getQuantity()) : BigDecimal.ZERO;
                    feeDto.setAmount(amount);
                    otherFeeDtos.add(feeDto);
                    otherFeesTotal = otherFeesTotal.add(amount);
                }
                dto.setOtherFees(otherFeeDtos);
            }
        }
        dto.setTotalQuantity(subtotalQuantity);
        dto.setTotalArea(dto.getSubtotalArea());
        dto.setTotalAmount(subtotalAmount.add(otherFeesTotal));
        dto.setTotalAmountDisplay(dto.getTotalAmount().setScale(2, RoundingMode.HALF_UP).toString() + "元");
        return dto;
    }
    @Override
    public SalesInvoicesDto salesInvoices(List<Long> salesLedgerIds) {
        if (CollectionUtils.isEmpty(salesLedgerIds)) {
            throw new ServiceException("销售发货单打印失败,销售订单不能为空");
        }
        List<SalesLedger> ledgers = salesLedgerMapper.selectBatchIds(salesLedgerIds);
        if (CollectionUtils.isEmpty(ledgers)) {
            throw new ServiceException("销售发货单打印失败,未找到对应台账记录");
        }
        Long customerId = ledgers.get(0).getCustomerId();
        for (SalesLedger ledger : ledgers) {
            if (!Objects.equals(customerId, ledger.getCustomerId())) {
                throw new ServiceException("销售发货单合并打印只能是同一个客户");
            }
        }
        SalesInvoicesDto dto = new SalesInvoicesDto();
        Customer customer = customerMapper.selectById(customerId);
        if (customer != null) {
            dto.setCustomerName(customer.getCustomerName());
            dto.setContactPerson(customer.getContactPerson());
            dto.setContactPhone(customer.getContactPhone());
            StringBuilder address = new StringBuilder();
            if (customer.getRegionsId() != null) {
                CustomerRegions regions = customerRegionsService.getById(customer.getRegionsId());
                if (regions != null) {
                    address.append(regions.getRegionsName());
                }
            }
            if (StringUtils.isNotEmpty(customer.getCompanyAddress())) {
                address.append(customer.getCompanyAddress());
            }
            dto.setCompanyAddress(address.toString());
        }
        //  å‘货单号 (XF + æ—¥æœŸ + åºåˆ—)
        String dateStr = LocalDate.now().format(DateTimeFormatter.ofPattern("yyMMdd"));
        String redisKey = "sales:delivery:seq:" + dateStr;
        Long seq = redisTemplate.opsForValue().increment(redisKey);
        if (seq != null && seq == 1) {
            redisTemplate.expire(redisKey, 48, TimeUnit.HOURS);
        }
        dto.setDeliveryNo("XF" + dateStr + String.format("%03d", seq != null ? seq : 1));
        // å¯¹æ–¹å•号
//        dto.setExternalOrderNo(ledgers.get(0).getCustomerContractNo());
        //  æŸ¥è¯¢æ‰€æœ‰äº§å“
        List<SalesLedgerProduct> allProducts = salesLedgerProductMapper.selectList(
                new LambdaQueryWrapper<SalesLedgerProduct>().in(SalesLedgerProduct::getSalesLedgerId, salesLedgerIds));
        if (CollectionUtils.isNotEmpty(allProducts)) {
            Map<Long, SalesLedger> ledgerMap = ledgers.stream()
                    .collect(Collectors.toMap(SalesLedger::getId, Function.identity()));
            Map<Long, List<SalesLedgerProduct>> groupedData = new LinkedHashMap<>();
            for (SalesLedgerProduct p : allProducts) {
                groupedData.computeIfAbsent(p.getSalesLedgerId(), k -> new ArrayList<>()).add(p);
            }
            List<SalesInvoicesDto.InvoiceOrderGroupDto> groups = new ArrayList<>();
            BigDecimal totalQty = BigDecimal.ZERO;
            BigDecimal totalArea = BigDecimal.ZERO;
            for (Map.Entry<Long, List<SalesLedgerProduct>> ledgerEntry : groupedData.entrySet()) {
                SalesLedger ledger = ledgerMap.get(ledgerEntry.getKey());
                String orderNo = ledger != null ? ledger.getSalesContractNo() : "";
                List<SalesLedgerProduct> products = ledgerEntry.getValue();
                SalesInvoicesDto.InvoiceOrderGroupDto group = new SalesInvoicesDto.InvoiceOrderGroupDto();
                group.setSalesContractNo(orderNo);
                if (CollectionUtils.isNotEmpty(products)) {
                    group.setProductName(products.get(0).getProductCategory());
                }
                List<SalesInvoicesDto.InvoiceItemDto> itemDtos = new ArrayList<>();
                BigDecimal groupQty = BigDecimal.ZERO;
                BigDecimal groupArea = BigDecimal.ZERO;
                for (SalesLedgerProduct p : products) {
                    SalesInvoicesDto.InvoiceItemDto item = new SalesInvoicesDto.InvoiceItemDto();
                    item.setFloorCode(p.getFloorCode());
                    item.setWidthHeight((p.getWidth() != null ? p.getWidth().stripTrailingZeros().toPlainString() : "0") +
                            " * " + (p.getHeight() != null ? p.getHeight().stripTrailingZeros().toPlainString() : "0"));
                    item.setQuantity(p.getQuantity());
                    // é¢ç§¯
                    BigDecimal area = p.getSettleTotalArea() != null ? p.getSettleTotalArea() : p.getActualTotalArea();
                    if (area == null && p.getWidth() != null && p.getHeight() != null && p.getQuantity() != null) {
                        area = p.getWidth().multiply(p.getHeight()).multiply(p.getQuantity()).divide(new BigDecimal(1000000), 2, RoundingMode.HALF_UP);
                    }
                    item.setArea(area);
                    item.setRemark(p.getRemark());
                    item.setProcessRequirement(p.getProcessRequirement());
                    itemDtos.add(item);
                    groupQty = groupQty.add(p.getQuantity() != null ? p.getQuantity() : BigDecimal.ZERO);
                    groupArea = groupArea.add(area != null ? area : BigDecimal.ZERO);
                }
                group.setItems(itemDtos);
                group.setGroupTotalQuantity(groupQty);
                group.setGroupTotalArea(groupArea.setScale(2, RoundingMode.HALF_UP));
                groups.add(group);
                totalQty = totalQty.add(groupQty);
                totalArea = totalArea.add(groupArea);
            }
            dto.setGroups(groups);
            dto.setTotalQuantity(totalQty);
            dto.setTotalArea(totalArea.setScale(2, RoundingMode.HALF_UP));
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (loginUser != null && loginUser.getUser() != null) {
            dto.setOrderMaker(loginUser.getUser().getNickName());
        }
        dto.setExecutionDate(LocalDateTime.now());
        return dto;
    }
    @Override
    public List<SalesLabelDto> salesLabel(Long salesLedgerId) {
        if (salesLedgerId == null) {
            throw new ServiceException("打印标签失败,数据不能为空");
        }
        SalesLedger salesLedger = baseMapper.selectById(salesLedgerId);
        if (salesLedger == null) {
            throw new ServiceException("打印失败,销售订单不存在");
        }
        // æŸ¥è¯¢äº§å“åˆ—表
        List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(
                new LambdaQueryWrapper<SalesLedgerProduct>().eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerId));
        // æŸ¥è¯¢å®¢æˆ·åœ°å€
        String fullAddress = "";
        if (salesLedger.getCustomerId() != null) {
            Customer customer = customerMapper.selectById(salesLedger.getCustomerId());
            if (customer != null) {
                StringBuilder addressSb = new StringBuilder();
                if (customer.getRegionsId() != null) {
                    CustomerRegions regions = customerRegionsService.getById(customer.getRegionsId());
                    if (regions != null) {
                        addressSb.append(regions.getRegionsName());
                    }
                }
                if (StringUtils.isNotEmpty(customer.getCompanyAddress())) {
                    addressSb.append(customer.getCompanyAddress());
                }
                fullAddress = addressSb.toString();
            }
        }
        List<SalesLabelDto> list = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(products)) {
            for (SalesLedgerProduct p : products) {
                SalesLabelDto dto = new SalesLabelDto();
                dto.setCustomerName(salesLedger.getCustomerName());
                dto.setSalesContractNo(salesLedger.getSalesContractNo());
                dto.setProductName(p.getProductCategory());
                // å®½*高=数量
                String specification = (p.getWidth() != null ? p.getWidth().stripTrailingZeros().toPlainString() : "0") + "*" +
                        (p.getHeight() != null ? p.getHeight().stripTrailingZeros().toPlainString() : "0") + "=" +
                        (p.getQuantity() != null ? p.getQuantity().stripTrailingZeros().toPlainString() : "0");
                dto.setSpecification(specification);
                // å®¢æˆ·åœ°å€ + æ¥¼å±‚编号
                dto.setFloorCode(fullAddress + (StringUtils.isNotEmpty(p.getFloorCode()) ? " " + p.getFloorCode() : ""));
                list.add(dto);
            }
        }
        return list;
    }
    private int findFirstMissingSequence(List<Integer> sequences) {
        if (sequences.isEmpty()) {
            return 1;
src/main/resources/mapper/basic/CustomerRegionsMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.basic.mapper.CustomerRegionsMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.basic.pojo.CustomerRegions">
        <id column="id" property="id" />
        <result column="parent_id" property="parentId" />
        <result column="regions_name" property="regionsName" />
        <result column="tenant_id" property="tenantId" />
    </resultMap>
</mapper>
src/main/resources/mapper/sales/SalesLedgerMapper.xml
@@ -5,12 +5,12 @@
<mapper namespace="com.ruoyi.sales.mapper.SalesLedgerMapper">
    <select id="selectSequencesByDate" resultType="java.lang.Integer">
        SELECT CAST(SUBSTR(sales_contract_no,LENGTH(#{datePart})+1 , 3) AS SIGNED)
        SELECT CAST(SUBSTR(sales_contract_no, LENGTH(#{datePart}) + 1, 3) AS SIGNED)
        FROM sales_ledger
        WHERE sales_contract_no LIKE CONCAT('%',#{datePart},'%')
        WHERE sales_contract_no LIKE CONCAT('%', #{datePart}, '%')
    </select>
    <select id="getSalesNo" resultType="com.ruoyi.sales.pojo.SalesLedger">
    </select>
    <select id="selectSalesLedgerList" resultType="com.ruoyi.sales.pojo.SalesLedger">
@@ -56,9 +56,9 @@
        T1.attachment_materials,
        T1.tenant_id,
        T1.contract_amount,
        T1.contract_amount                    as noInvoiceAmountTotal,
        T1.contract_amount as noInvoiceAmountTotal,
        T1.execution_date,
        T2.nick_name                          AS entry_person_name,
        T2.nick_name AS entry_person_name,
        T1.payment_method,
        T1.delivery_date,
        DATEDIFF(T1.delivery_date, CURDATE()) AS delivery_days_diff,
@@ -78,25 +78,25 @@
        ) shipping_status_counts ON T1.id = shipping_status_counts.sales_ledger_id
        <where>
            <if test="salesLedgerDto.customerName != null and salesLedgerDto.customerName != '' ">
                AND  T1.customer_name LIKE CONCAT('%',#{salesLedgerDto.customerName},'%')
                AND T1.customer_name LIKE CONCAT('%',#{salesLedgerDto.customerName},'%')
            </if>
            <if test="salesLedgerDto.customerContractNo != null and salesLedgerDto.customerContractNo !='' ">
                AND  T1.customer_contract_no LIKE CONCAT('%',#{salesLedgerDto.customerContractNo},'%')
                AND T1.customer_contract_no LIKE CONCAT('%',#{salesLedgerDto.customerContractNo},'%')
            </if>
            <if test="salesLedgerDto.salesContractNo != null and salesLedgerDto.salesContractNo != '' ">
                AND  T1.sales_contract_no LIKE CONCAT('%',#{salesLedgerDto.salesContractNo},'%')
                AND T1.sales_contract_no LIKE CONCAT('%',#{salesLedgerDto.salesContractNo},'%')
            </if>
            <if test="salesLedgerDto.projectName != null and salesLedgerDto.projectName != '' ">
                AND T1.project_name LIKE CONCAT('%',#{salesLedgerDto.projectName},'%')
            </if>
            <if test="salesLedgerDto.entryDateStart != null and salesLedgerDto.entryDateStart != '' ">
               AND T1.entry_date &gt;= DATE_FORMAT(#{salesLedgerDto.entryDateStart},'%Y-%m-%d')
                AND T1.entry_date &gt;= DATE_FORMAT(#{salesLedgerDto.entryDateStart},'%Y-%m-%d')
            </if>
            <if test="salesLedgerDto.entryDateEnd != null and salesLedgerDto.entryDateEnd != '' ">
                AND  T1.entry_date &lt;= DATE_FORMAT(#{salesLedgerDto.entryDateEnd},'%Y-%m-%d')
                AND T1.entry_date &lt;= DATE_FORMAT(#{salesLedgerDto.entryDateEnd},'%Y-%m-%d')
            </if>
        </where>
    order by T1.entry_date desc
        order by T1.entry_date desc
    </select>
    <select id="selectIncomeStats" resultType="com.ruoyi.home.dto.IncomeExpenseAnalysisDto">
@@ -117,10 +117,26 @@
        left join sales_ledger_product slp on sl.id = slp.sales_ledger_id
        left join shipping_info si on slp.id = si.sales_ledger_product_id
        where si.status = '已发货'
            <if test="ew.customerName != null and ew.customerName != '' ">
        <if test="ew.customerName != null and ew.customerName != '' ">
            and sl.customer_name like concat('%',#{ew.customerName},'%')
            </if>
        </if>
        order by sl.execution_date desc
    </select>
    <select id="selectSalesProcess" resultType="com.ruoyi.sales.pojo.SalesLedgerProcessRoute"
            parameterType="java.lang.Long">
        select slpr.*,
        pri.process_name as processName
        from sales_ledger_process_route slpr
        left join process_route_item pri on pri.id = slpr.process_route_item_id
        <where>
            <if test="salesLedgerId != null">
                AND slpr.sales_ledger_id = #{salesLedgerId}
            </if>
            <if test="salesLedgerId == null">
                AND 1 = 2
            </if>
        </where>
    </select>
</mapper>