package com.ruoyi.sales.service.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
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.mapper.CustomerMapper;
|
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;
|
import com.ruoyi.common.exception.base.BaseException;
|
import com.ruoyi.common.utils.DateUtils;
|
import com.ruoyi.common.utils.EnumUtil;
|
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.other.mapper.TempFileMapper;
|
import com.ruoyi.other.pojo.TempFile;
|
import com.ruoyi.production.mapper.*;
|
import com.ruoyi.production.pojo.ProcessRoute;
|
import com.ruoyi.production.pojo.ProcessRouteItem;
|
import com.ruoyi.production.service.ProductionProductMainService;
|
|
import com.ruoyi.project.system.domain.SysUser;
|
import com.ruoyi.project.system.mapper.SysDeptMapper;
|
import com.ruoyi.project.system.mapper.SysUserMapper;
|
import com.ruoyi.purchase.dto.SimpleReturnOrderGroupDto;
|
import com.ruoyi.purchase.mapper.PurchaseReturnOrderProductsMapper;
|
import com.ruoyi.quality.mapper.QualityInspectMapper;
|
import com.ruoyi.sales.dto.*;
|
import com.ruoyi.sales.mapper.*;
|
import com.ruoyi.sales.pojo.*;
|
import com.ruoyi.sales.service.ISalesLedgerProcessRouteService;
|
import com.ruoyi.sales.service.ISalesLedgerProductProcessBindService;
|
import com.ruoyi.sales.service.ISalesLedgerProductProcessService;
|
import com.ruoyi.sales.service.ISalesLedgerService;
|
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;
|
import org.springframework.stereotype.Service;
|
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;
|
import java.time.format.DateTimeFormatter;
|
import java.util.*;
|
import java.util.concurrent.TimeUnit;
|
import java.util.function.Function;
|
import java.util.stream.Collectors;
|
|
/**
|
* 销售台账Service业务层处理
|
*
|
* @author ruoyi
|
* @date 2025-05-08
|
*/
|
@Service
|
@RequiredArgsConstructor
|
@Slf4j
|
public class SalesLedgerServiceImpl extends ServiceImpl<SalesLedgerMapper, SalesLedger> implements ISalesLedgerService {
|
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;
|
private final ShippingInfoMapper shippingInfoMapper;
|
private final InvoiceLedgerMapper invoiceLedgerMapper;
|
private final SalesLedgerSchedulingMapper salesLedgerSchedulingMapper;
|
private final SalesLedgerWorkMapper salesLedgerWorkMapper;
|
private final SalesLedgerProductionAccountingMapper salesLedgerProductionAccountingMapper;
|
private final InvoiceRegistrationProductMapper invoiceRegistrationProductMapper;
|
private final InvoiceRegistrationMapper invoiceRegistrationMapper;
|
private final ProductOrderMapper productOrderMapper;
|
private final ProcessRouteMapper processRouteMapper;
|
private final ProductProcessRouteMapper productProcessRouteMapper;
|
private final ProcessRouteItemMapper processRouteItemMapper;
|
private final ProductProcessRouteItemMapper productProcessRouteItemMapper;
|
private final ProductWorkOrderMapper productWorkOrderMapper;
|
private final ProductionProductMainMapper productionProductMainMapper;
|
private final ProductionProductOutputMapper productionProductOutputMapper;
|
private final ProductionProductInputMapper productionProductInputMapper;
|
private final QualityInspectMapper qualityInspectMapper;
|
private final RedisTemplate<String, String> redisTemplate;
|
|
private final ISalesLedgerProductProcessService salesLedgerProductProcessService;
|
|
private final ISalesLedgerProductProcessBindService salesLedgerProductProcessBindService;
|
|
private final ISalesLedgerProcessRouteService salesLedgerProcessRouteService;
|
|
@Autowired
|
private SysDeptMapper sysDeptMapper;
|
@Value("${file.upload-dir}")
|
private String uploadDir;
|
@Autowired
|
private ProductModelMapper productModelMapper;
|
|
@Autowired
|
private ProductMapper productMapper;
|
@Autowired
|
private ProductStructureMapper productStructureMapper;
|
@Autowired
|
private ProductionProductMainService productionProductMainService;
|
@Autowired
|
private PurchaseReturnOrderProductsMapper purchaseReturnOrderProductsMapper;
|
;
|
@Autowired
|
private SysUserMapper sysUserMapper;
|
|
private final ICustomerRegionsService customerRegionsService;
|
|
@Override
|
public List<SalesLedger> selectSalesLedgerList(SalesLedgerDto salesLedgerDto) {
|
return salesLedgerMapper.selectSalesLedgerList(salesLedgerDto);
|
}
|
|
public List<SalesLedgerProduct> getSalesLedgerProductListByRelateId(Long relateId, SaleEnum type) {
|
LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
|
productWrapper.eq(SalesLedgerProduct::getSalesLedgerId, relateId);
|
productWrapper.eq(SalesLedgerProduct::getType, type.getCode());
|
return salesLedgerProductMapper.selectList(productWrapper);
|
}
|
|
@Override
|
public List<SalesLedgerProduct> getSalesLedgerProductListByIds(@Nullable List<Long> relateIds, SaleEnum type) {
|
if (CollectionUtils.isEmpty(relateIds)) {
|
return Collections.emptyList();
|
}
|
LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
|
productWrapper.in(SalesLedgerProduct::getId, relateIds);
|
productWrapper.eq(SalesLedgerProduct::getType, type.getCode());
|
List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(productWrapper);
|
if (type.equals(SaleEnum.PURCHASE)) {
|
// 查询退货信息
|
List<Long> productIds = salesLedgerProducts.stream().map(SalesLedgerProduct::getId).collect(Collectors.toList());
|
List<SimpleReturnOrderGroupDto> groupListByProductIds = new ArrayList<>();
|
if (CollectionUtils.isNotEmpty(productIds)) {
|
groupListByProductIds = purchaseReturnOrderProductsMapper.getReturnOrderGroupListByProductIds(productIds);
|
}
|
Map<Long, BigDecimal> returnOrderGroupDtoMap = groupListByProductIds.stream().collect(Collectors.toMap(SimpleReturnOrderGroupDto::getSalesLedgerProductId, SimpleReturnOrderGroupDto::getSumReturnQuantity));
|
salesLedgerProducts.forEach(item -> {
|
BigDecimal returnQuality = returnOrderGroupDtoMap.getOrDefault(item.getId(), BigDecimal.ZERO);
|
item.setReturnQuality(returnQuality);
|
item.setAvailableQuality(item.getQuantity().subtract(returnQuality));
|
});
|
}
|
return salesLedgerProducts;
|
}
|
|
@Override
|
public SalesLedgerDto getSalesLedgerWithProducts(SalesLedgerDto salesLedgerDto) {
|
// 1. 查询主表
|
SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerDto.getId());
|
if (salesLedger == null) {
|
throw new BaseException("台账不存在");
|
}
|
|
// 2. 查询子表
|
LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
|
productWrapper.eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId());
|
productWrapper.eq(SalesLedgerProduct::getType, 1);
|
List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(productWrapper);
|
for (SalesLedgerProduct product : products) {
|
product.setOriginalNoInvoiceNum(product.getNoInvoiceNum());
|
// 提供临时未开票数,未开票金额供前段计算
|
product.setTempnoInvoiceAmount(product.getNoInvoiceAmount());
|
product.setTempNoInvoiceNum(product.getNoInvoiceNum());
|
product.setRegister(SecurityUtils.getLoginUser().getUser().getNickName());
|
product.setRegisterDate(LocalDateTime.now());
|
// 发货信息
|
ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
|
.eq(ShippingInfo::getSalesLedgerProductId, product.getId())
|
.orderByDesc(ShippingInfo::getCreateTime)
|
.last("limit 1"));
|
if (shippingInfo != null) {
|
product.setShippingStatus(shippingInfo.getStatus());
|
}
|
// 加工明细,先查bind表获取该产品关联的工序及数量
|
List<SalesLedgerProductProcessBind> bindList = salesLedgerProductProcessBindService.list(
|
new LambdaQueryWrapper<SalesLedgerProductProcessBind>()
|
.eq(SalesLedgerProductProcessBind::getSalesLedgerProductId, product.getId()));
|
if (!bindList.isEmpty()) {
|
List<Integer> processIds = bindList.stream()
|
.map(SalesLedgerProductProcessBind::getSalesLedgerProductProcessId)
|
.collect(Collectors.toList());
|
Map<Integer, Integer> processQuantityMap = bindList.stream()
|
.collect(Collectors.toMap(
|
SalesLedgerProductProcessBind::getSalesLedgerProductProcessId,
|
SalesLedgerProductProcessBind::getQuantity,
|
(a, b) -> a));
|
List<SalesLedgerProductProcess> processList = salesLedgerProductProcessService.listByIds(processIds);
|
processList.forEach(p -> p.setQuantity(processQuantityMap.get(p.getId())));
|
product.setSalesProductProcessList(processList);
|
}
|
}
|
|
// 3.查询上传文件
|
LambdaQueryWrapper<CommonFile> salesLedgerFileWrapper = new LambdaQueryWrapper<>();
|
salesLedgerFileWrapper.eq(CommonFile::getCommonId, salesLedger.getId())
|
.eq(CommonFile::getType, FileNameType.SALE.getValue());
|
List<CommonFile> salesLedgerFiles = commonFileMapper.selectList(salesLedgerFileWrapper);
|
|
// 4. 转换 DTO
|
SalesLedgerDto resultDto = new SalesLedgerDto();
|
BeanUtils.copyProperties(salesLedger, resultDto);
|
if (!products.isEmpty()) {
|
resultDto.setHasChildren(true);
|
resultDto.setProductData(products);
|
resultDto.setSalesLedgerFiles(salesLedgerFiles);
|
}
|
return resultDto;
|
}
|
|
@Override
|
public List<Map<String, Object>> getSalesNo() {
|
LambdaQueryWrapper<SalesLedger> queryWrapper = Wrappers.lambdaQuery();
|
queryWrapper.select(SalesLedger::getId, SalesLedger::getSalesContractNo, SalesLedger::getProjectName);
|
|
// 获取原始查询结果
|
List<Map<String, Object>> result = salesLedgerMapper.selectMaps(queryWrapper);
|
|
// 将下划线命名转换为驼峰命名
|
return result.stream().map(map -> map.entrySet().stream()
|
.collect(Collectors.toMap(
|
entry -> underlineToCamel(entry.getKey()),
|
Map.Entry::getValue))
|
).collect(Collectors.toList());
|
}
|
|
@Override
|
public BigDecimal getContractAmount() {
|
LocalDate now = LocalDate.now();
|
YearMonth currentMonth = YearMonth.from(now);
|
|
// 创建LambdaQueryWrapper
|
LambdaQueryWrapper<SalesLedger> queryWrapper = new LambdaQueryWrapper<>();
|
queryWrapper.ge(SalesLedger::getEntryDate, currentMonth.atDay(1).atStartOfDay()) // 大于等于本月第一天
|
.lt(SalesLedger::getEntryDate, currentMonth.plusMonths(1).atDay(1).atStartOfDay()); // 小于下月第一天
|
|
// 执行查询并计算总和
|
List<SalesLedger> salesLedgers = salesLedgerMapper.selectList(queryWrapper);
|
|
BigDecimal totalContractAmount = salesLedgers.stream()
|
.map(SalesLedger::getContractAmount)
|
.filter(Objects::nonNull)
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
return totalContractAmount;
|
}
|
|
@Override
|
public List getTopFiveList() {
|
// 查询原始数据
|
LambdaQueryWrapper<SalesLedger> queryWrapper = Wrappers.lambdaQuery();
|
queryWrapper.select(SalesLedger::getCustomerId,
|
SalesLedger::getCustomerName,
|
SalesLedger::getContractAmount)
|
.orderByDesc(SalesLedger::getContractAmount);
|
List<SalesLedger> records = salesLedgerMapper.selectList(queryWrapper);
|
|
// 按客户ID分组并聚合金额
|
Map<Long, GroupedCustomer> groupedMap = new LinkedHashMap<>(); // 使用LinkedHashMap保持排序
|
for (SalesLedger record : records) {
|
groupedMap.computeIfAbsent(record.getCustomerId(),
|
k -> new GroupedCustomer(record.getCustomerId(), record.getCustomerName()))
|
.addAmount(record.getContractAmount());
|
}
|
|
// 转换为结果列表并取前5
|
return groupedMap.values().stream()
|
.sorted(Comparator.comparing(GroupedCustomer::getTotalAmount).reversed())
|
.limit(5)
|
.map(customer -> {
|
Map<String, Object> result = new HashMap<>();
|
result.put("customerId", customer.getCustomerId());
|
result.put("customerName", customer.getCustomerName());
|
result.put("totalAmount", customer.getTotalAmount());
|
return result;
|
})
|
.collect(Collectors.toList());
|
}
|
|
@Override
|
public List<MonthlyAmountDto> getAmountHalfYear(Integer type) {
|
|
LocalDate now = LocalDate.now();
|
List<MonthlyAmountDto> result = new ArrayList<>();
|
|
for (int i = 5; i >= 0; i--) {
|
YearMonth yearMonth = YearMonth.from(now.minusMonths(i));
|
LocalDateTime startTime = yearMonth.atDay(1).atStartOfDay();
|
LocalDateTime endTime = yearMonth.atEndOfMonth().atTime(23, 59, 59);
|
|
// 回款金额
|
LambdaQueryWrapper<ReceiptPayment> receiptPaymentQuery = new LambdaQueryWrapper<>();
|
receiptPaymentQuery
|
.ge(ReceiptPayment::getCreateTime, startTime)
|
.le(ReceiptPayment::getCreateTime, endTime);
|
|
List<ReceiptPayment> receiptPayments =
|
receiptPaymentMapper.selectList(receiptPaymentQuery);
|
|
BigDecimal receiptAmount = receiptPayments.stream()
|
.map(ReceiptPayment::getReceiptPaymentAmount)
|
.filter(Objects::nonNull)
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
// 开票金额
|
LambdaQueryWrapper<InvoiceLedger> invoiceLedgerQuery = new LambdaQueryWrapper<>();
|
invoiceLedgerQuery
|
.ge(InvoiceLedger::getCreateTime, startTime)
|
.le(InvoiceLedger::getCreateTime, endTime);
|
|
List<InvoiceLedger> invoiceLedgers =
|
invoiceLedgerMapper.selectList(invoiceLedgerQuery);
|
|
BigDecimal invoiceAmount = invoiceLedgers.stream()
|
.map(InvoiceLedger::getInvoiceTotal)
|
.filter(Objects::nonNull)
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
MonthlyAmountDto dto = new MonthlyAmountDto();
|
dto.setMonth(yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM")));
|
dto.setReceiptAmount(receiptAmount);
|
dto.setInvoiceAmount(invoiceAmount);
|
|
result.add(dto);
|
}
|
|
return result;
|
}
|
|
@Override
|
public IPage<SalesLedger> selectSalesLedgerListPage(Page page, SalesLedgerDto salesLedgerDto) {
|
return salesLedgerMapper.selectSalesLedgerListPage(page, salesLedgerDto);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public AjaxResult importData(MultipartFile file) {
|
LoginUser loginUser = SecurityUtils.getLoginUser();
|
try {
|
InputStream inputStream = file.getInputStream();
|
ExcelUtil<SalesLedgerImportDto> salesLedgerImportDtoExcelUtil = new ExcelUtil<>(SalesLedgerImportDto.class);
|
Map<String, List<SalesLedgerImportDto>> stringListMap = salesLedgerImportDtoExcelUtil.importExcelMultiSheet(Arrays.asList("销售台账数据", "销售产品数据"), inputStream, 0);
|
if (CollectionUtils.isEmpty(stringListMap)) return AjaxResult.error("销售表格为空!");
|
// 业务层合并
|
List<SalesLedgerImportDto> salesLedgerImportDtoList = stringListMap.get("销售台账数据");
|
if (CollectionUtils.isEmpty(salesLedgerImportDtoList)) return AjaxResult.error("销售台账数据为空!");
|
List<SalesLedgerImportDto> salesLedgerProductImportDtoList = stringListMap.get("销售产品数据");
|
if (CollectionUtils.isEmpty(salesLedgerProductImportDtoList)) return AjaxResult.error("销售产品数据为空!");
|
// 客户数据
|
List<Customer> customers = customerMapper.selectList(new LambdaQueryWrapper<Customer>().in(Customer::getCustomerName,
|
salesLedgerImportDtoList.stream().map(SalesLedgerImportDto::getCustomerName).collect(Collectors.toList())));
|
List<Map<String, Object>> list = productModelMapper.getProductAndModelList();
|
// 录入人数据
|
List<SysUser> sysUsers = sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>().in(SysUser::getNickName,
|
salesLedgerImportDtoList.stream().map(SalesLedgerImportDto::getEntryPerson).collect(Collectors.toList())));
|
for (SalesLedgerImportDto salesLedgerImportDto : salesLedgerImportDtoList) {
|
SalesLedger salesLedger1 = salesLedgerMapper.selectOne(new LambdaQueryWrapper<SalesLedger>()
|
.eq(SalesLedger::getSalesContractNo, salesLedgerImportDto.getSalesContractNo())
|
.last("LIMIT 1"));
|
if (salesLedger1 != null) {
|
throw new ServiceException("导入失败:合同号 [" + salesLedgerImportDto.getSalesContractNo() + "] 已存在,请检查后重新导入");
|
}
|
SalesLedger salesLedger = new SalesLedger();
|
BeanUtils.copyProperties(salesLedgerImportDto, salesLedger);
|
salesLedger.setExecutionDate(DateUtils.toLocalDate(salesLedgerImportDto.getExecutionDate()));
|
// 通过客户名称查询客户ID,客户合同号
|
salesLedger.setCustomerId(customers.stream()
|
.filter(customer -> customer.getCustomerName().equals(salesLedger.getCustomerName()))
|
.findFirst()
|
.map(Customer::getId)
|
.orElse(null));
|
salesLedger.setCustomerContractNo(customers.stream()
|
.filter(customer -> customer.getCustomerName().equals(salesLedger.getCustomerName()))
|
.findFirst()
|
.map(Customer::getTaxpayerIdentificationNumber)
|
.orElse(null));
|
Long aLong = sysUsers.stream()
|
.filter(sysUser -> sysUser.getNickName().equals(salesLedger.getEntryPerson()))
|
.findFirst()
|
.map(SysUser::getUserId)
|
.orElse(null);
|
if (aLong == null)
|
throw new ServiceException("录入人:" + salesLedger.getEntryPerson() + ",无对应用户!");
|
salesLedger.setEntryPerson(aLong.toString());
|
// 销售产品数据绑定,通过销售单号获取对应销售产品数据
|
List<SalesLedgerProductImportDto> salesLedgerProductImportDtos = salesLedgerProductImportDtoList.stream()
|
.filter(salesLedgerProductImportDto -> salesLedgerProductImportDto.getSalesContractNo().equals(salesLedger.getSalesContractNo()))
|
.collect(Collectors.toList());
|
if (CollectionUtils.isEmpty(salesLedgerProductImportDtos))
|
throw new RuntimeException("销售单号:" + salesLedgerImportDto.getSalesContractNo() + ",无对应产品数据!");
|
salesLedger.setContractAmount(salesLedgerProductImportDtos.stream()
|
.map(SalesLedgerProductImportDto::getTaxInclusiveTotalPrice)
|
.reduce(BigDecimal.ZERO, BigDecimal::add));
|
salesLedgerMapper.insert(salesLedger);
|
|
for (SalesLedgerProductImportDto salesLedgerProductImportDto : salesLedgerProductImportDtos) {
|
SalesLedgerProduct salesLedgerProduct = new SalesLedgerProduct();
|
BeanUtils.copyProperties(salesLedgerProductImportDto, salesLedgerProduct);
|
salesLedgerProduct.setFloorCode(salesLedgerProductImportDto.getFloorNo());
|
salesLedgerProduct.setProcessRequirement(salesLedgerProductImportDto.getProcessingRequirements());
|
salesLedgerProduct.setRemark(salesLedgerProductImportDto.getRemarks());
|
salesLedgerProduct.setSalesLedgerId(salesLedger.getId());
|
salesLedgerProduct.setType(1);
|
// 计算不含税总价
|
salesLedgerProduct.setTaxExclusiveTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice().divide(new BigDecimal(1).add(salesLedgerProduct.getTaxRate().divide(new BigDecimal(100))), 2, RoundingMode.HALF_UP));
|
salesLedgerProduct.setNoInvoiceNum(salesLedgerProduct.getQuantity());
|
salesLedgerProduct.setNoInvoiceAmount(salesLedgerProduct.getTaxExclusiveTotalPrice());
|
list.stream()
|
.filter(map -> map.get("productName").equals(salesLedgerProduct.getProductCategory()) && map.get("model").equals(salesLedgerProduct.getSpecificationModel()))
|
.findFirst()
|
.ifPresent(map -> {
|
salesLedgerProduct.setProductModelId(Long.parseLong(map.get("modelId").toString()));
|
salesLedgerProduct.setProductId(Long.parseLong(map.get("id").toString()));
|
});
|
salesLedgerProduct.setRegister(loginUser.getNickName());
|
salesLedgerProduct.setRegisterDate(LocalDateTime.now());
|
salesLedgerProduct.setApproveStatus(0);
|
salesLedgerProduct.setPendingInvoiceTotal(salesLedgerProductImportDto.getTaxInclusiveTotalPrice());
|
salesLedgerProductMapper.insert(salesLedgerProduct);
|
|
// 处理额外加工信息
|
String extraProcessing = salesLedgerProductImportDto.getExtraProcessing();
|
if (StringUtils.hasText(extraProcessing)) {
|
List<SalesLedgerProductProcess> processList = new ArrayList<>();
|
// 中英文分号
|
String[] items = extraProcessing.split("[;;]");
|
for (String item : items) {
|
if (StringUtils.hasText(item)) {
|
String[] parts = item.split("[-—~~]");
|
if (parts.length >= 2) {
|
String processName = parts[0].trim();
|
String qtyStr = parts[1].trim();
|
try {
|
BigDecimal quantity = new BigDecimal(qtyStr);
|
SalesLedgerProductProcess process = salesLedgerProductProcessService.getOne(
|
new LambdaQueryWrapper<SalesLedgerProductProcess>()
|
.eq(SalesLedgerProductProcess::getProcessName, processName)
|
.last("LIMIT 1")
|
);
|
if (process != null) {
|
SalesLedgerProductProcess p = new SalesLedgerProductProcess();
|
p.setId(process.getId());
|
p.setQuantity(quantity.intValue());
|
processList.add(p);
|
}
|
} catch (Exception e) {
|
log.error("解析额外加工数量失败: {}", qtyStr);
|
}
|
}
|
}
|
}
|
if (!processList.isEmpty()) {
|
salesLedgerProductProcessBindService.updateProductProcessBind(processList, salesLedgerProduct.getId());
|
}
|
}
|
|
// 添加生产数据
|
salesLedgerProductServiceImpl.addProductionData(salesLedgerProduct);
|
}
|
}
|
|
return AjaxResult.success("导入成功");
|
} catch (Exception e) {
|
e.printStackTrace();
|
}
|
return AjaxResult.success("导入失败");
|
}
|
|
@Override
|
public List<LossProductModelDto> getSalesLedgerWithProductsLoss(Long salesLedgerId) {
|
|
|
List<LossProductModelDto> lossProductModelDtos = salesLedgerProductMapper.selectProductBomStructure(salesLedgerId);
|
|
|
return lossProductModelDtos;
|
}
|
|
@Override
|
public IPage<SalesLedgerDto> listSalesLedger(SalesLedgerDto salesLedgerDto, Page page) {
|
IPage<SalesLedgerDto> salesLedgerDtoIPage = salesLedgerMapper.listSalesLedgerAndShipped(page, salesLedgerDto);
|
for (SalesLedgerDto salesLedger : salesLedgerDtoIPage.getRecords()) {
|
LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
|
productWrapper.eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId());
|
productWrapper.eq(SalesLedgerProduct::getType, 1);
|
List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(productWrapper);
|
for (SalesLedgerProduct product : products) {
|
product.setOriginalNoInvoiceNum(product.getNoInvoiceNum());
|
// 提供临时未开票数,未开票金额供前段计算
|
product.setTempnoInvoiceAmount(product.getNoInvoiceAmount());
|
product.setTempNoInvoiceNum(product.getNoInvoiceNum());
|
product.setRegister(SecurityUtils.getLoginUser().getUser().getNickName());
|
product.setRegisterDate(LocalDateTime.now());
|
// 发货信息
|
ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
|
.eq(ShippingInfo::getSalesLedgerProductId, product.getId())
|
.orderByDesc(ShippingInfo::getCreateTime)
|
.last("limit 1"));
|
product.setShippingCarNumber(shippingInfo.getShippingCarNumber());
|
product.setShippingDate(shippingInfo.getShippingDate());
|
if (shippingInfo != null) {
|
product.setShippingStatus(shippingInfo.getStatus());
|
}
|
}
|
// 过滤只保留发货记录
|
products = products.stream().filter(product -> "已发货".equals(product.getShippingStatus())).collect(Collectors.toList());
|
if (!products.isEmpty()) {
|
salesLedger.setHasChildren(true);
|
salesLedger.setProductData(products);
|
}
|
}
|
|
|
return salesLedgerDtoIPage;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void saleProcessBind(SalesLedgerProcessRoute salesLedgerProcessRoute) {
|
if (salesLedgerProcessRoute == null) {
|
throw new ServiceException("绑定失败,数据不能为空");
|
}
|
|
SalesLedger salesLedger = baseMapper.selectById(salesLedgerProcessRoute.getSalesLedgerId());
|
if (salesLedger == null) {
|
throw new ServiceException("绑定失败,销售订单不存在");
|
}
|
ProcessRoute processRoute = processRouteMapper.selectById(salesLedgerProcessRoute.getProcessRouteId());
|
if (processRoute == null) {
|
throw new ServiceException("绑定失败,工艺路线不存在");
|
}
|
// 清除已绑定的数据
|
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()));
|
SalesLedgerProcessRoute ledgerProcessRoute;
|
List<SalesLedgerProcessRoute> salesLedgerProcessRouteList = new ArrayList<>();
|
for (ProcessRouteItem routeItem : routeItems) {
|
ledgerProcessRoute = new SalesLedgerProcessRoute();
|
ledgerProcessRoute.setProcessRouteId(processRoute.getId());
|
ledgerProcessRoute.setSalesLedgerId(salesLedger.getId());
|
ledgerProcessRoute.setProcessRouteItemId(routeItem.getId());
|
ledgerProcessRoute.setDragSort(routeItem.getDragSort());
|
salesLedgerProcessRouteList.add(ledgerProcessRoute);
|
}
|
salesLedgerProcessRouteService.saveBatch(salesLedgerProcessRouteList);
|
}
|
|
/**
|
* 下划线命名转驼峰命名
|
*/
|
private String underlineToCamel(String param) {
|
if (param == null || "".equals(param.trim())) {
|
return "";
|
}
|
int len = param.length();
|
StringBuilder sb = new StringBuilder(len);
|
for (int i = 0; i < len; i++) {
|
char c = param.charAt(i);
|
if (c == '_') {
|
if (++i < len) {
|
sb.append(Character.toUpperCase(param.charAt(i)));
|
}
|
} else {
|
sb.append(Character.toLowerCase(c));
|
}
|
}
|
return sb.toString();
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public int deleteSalesLedgerByIds(Long[] ids) {
|
List<Long> idList = Arrays.stream(ids)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toList());
|
if (CollectionUtils.isEmpty(idList)) {
|
return 0;
|
}
|
// 删除销售管理数据
|
LambdaQueryWrapper<SalesLedgerProduct> queryWrapper = new LambdaQueryWrapper<>();
|
queryWrapper.in(SalesLedgerProduct::getSalesLedgerId, idList)
|
.select(SalesLedgerProduct::getId);
|
|
List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(queryWrapper);
|
List<Long> productIds = products.stream()
|
.map(SalesLedgerProduct::getId)
|
.collect(Collectors.toList());
|
//删除生产数据
|
salesLedgerProductServiceImpl.deleteProductionData(productIds);
|
|
// 批量删除产品子表
|
if (!productIds.isEmpty()) {
|
salesLedgerProductMapper.deleteBatchIds(productIds);
|
}
|
|
// 清除产品的加工
|
salesLedgerProductProcessBindService.remove(new LambdaQueryWrapper<SalesLedgerProductProcessBind>().in(SalesLedgerProductProcessBind::getSalesLedgerProductId, productIds));
|
|
LambdaQueryWrapper<InvoiceRegistrationProduct> wrapper = new LambdaQueryWrapper<>();
|
wrapper.in(InvoiceRegistrationProduct::getSalesLedgerId, idList);
|
List<InvoiceRegistrationProduct> invoiceRegistrationProducts = invoiceRegistrationProductMapper.selectList(wrapper);
|
List<Integer> invoiceLedgerIds = new ArrayList<>();
|
if (CollectionUtils.isNotEmpty(invoiceRegistrationProducts)) {
|
LambdaQueryWrapper<InvoiceLedger> wrapperOne = new LambdaQueryWrapper<>();
|
wrapperOne.in(InvoiceLedger::getInvoiceRegistrationProductId, invoiceRegistrationProducts.stream().map(InvoiceRegistrationProduct::getId).collect(Collectors.toList()));
|
List<InvoiceLedger> invoiceLedgers = invoiceLedgerMapper.selectList(wrapperOne);
|
if (CollectionUtils.isNotEmpty(invoiceLedgers)) {
|
invoiceLedgerIds = invoiceLedgers.stream().map(InvoiceLedger::getId).collect(Collectors.toList());
|
}
|
invoiceLedgerMapper.delete(wrapperOne);
|
}
|
invoiceRegistrationProductMapper.delete(wrapper);
|
LambdaQueryWrapper<InvoiceRegistration> wrapperTwo = new LambdaQueryWrapper<>();
|
wrapperTwo.in(InvoiceRegistration::getSalesLedgerId, idList);
|
invoiceRegistrationMapper.delete(wrapperTwo);
|
|
if (CollectionUtils.isNotEmpty(invoiceLedgerIds)) {
|
LambdaQueryWrapper<ReceiptPayment> wrapperTree = new LambdaQueryWrapper<>();
|
wrapperTree.in(ReceiptPayment::getInvoiceLedgerId, invoiceLedgerIds);
|
receiptPaymentMapper.delete(wrapperTree);
|
}
|
// 删除发货台账记录
|
List<ShippingInfo> shippingInfos = shippingInfoMapper.selectList(new LambdaQueryWrapper<ShippingInfo>()
|
.in(ShippingInfo::getSalesLedgerId, idList));
|
if (CollectionUtils.isNotEmpty(shippingInfos)) {
|
shippingInfoServiceImpl.delete(shippingInfos.stream().map(ShippingInfo::getId).collect(Collectors.toList()));
|
}
|
// 删除附件表
|
commonFileService.deleteByBusinessIds(idList, FileNameType.SALE.getValue());
|
|
// 删除生产管控数据
|
//查询生产报工id
|
ArrayList<Long> mainIdList = productionProductMainService.listMain(idList);
|
if (CollectionUtils.isNotEmpty(mainIdList)) {
|
mainIdList.stream().forEach(mainId -> {
|
productionProductMainService.removeProductMain(mainId);
|
});
|
}
|
// 2. 再删除主表数据
|
return salesLedgerMapper.deleteBatchIds(idList);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto) {
|
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;
|
}
|
|
// 构建正式目录路径(按业务类型和日期分组)
|
String formalDir = uploadDir + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
|
|
Path formalDirPath = Paths.get(formalDir);
|
|
// 确保正式目录存在(递归创建)
|
if (!Files.exists(formalDirPath)) {
|
Files.createDirectories(formalDirPath);
|
}
|
|
for (String tempFileId : tempFileIds) {
|
// 查询临时文件记录
|
TempFile tempFile = tempFileMapper.selectById(tempFileId);
|
if (tempFile == null) {
|
log.warn("临时文件不存在,跳过处理: {}", tempFileId);
|
continue;
|
}
|
|
// 构建正式文件名(包含业务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);
|
}
|
}
|
}
|
|
// 文件迁移方法
|
|
@Override
|
public void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products, SaleEnum type) {
|
// 按ID分组,区分新增和更新的记录
|
Map<Boolean, List<SalesLedgerProduct>> partitionedProducts = products.stream()
|
.peek(p -> p.setSalesLedgerId(salesLedgerId))
|
.collect(Collectors.partitioningBy(p -> p.getId() != null));
|
|
List<SalesLedgerProduct> updateList = partitionedProducts.get(true);
|
List<SalesLedgerProduct> insertList = partitionedProducts.get(false);
|
|
// 执行更新操作
|
if (!updateList.isEmpty()) {
|
for (SalesLedgerProduct product : updateList) {
|
product.setType(type.getCode());
|
salesLedgerProductMapper.updateById(product);
|
// 清空销售产品绑定的加工
|
salesLedgerProductProcessBindService.updateProductProcessBind(product.getSalesProductProcessList(), product.getId());
|
}
|
}
|
// 执行插入操作
|
if (!insertList.isEmpty()) {
|
for (SalesLedgerProduct salesLedgerProduct : insertList) {
|
salesLedgerProduct.setType(type.getCode());
|
salesLedgerProduct.setNoInvoiceNum(salesLedgerProduct.getQuantity());
|
salesLedgerProduct.setNoInvoiceAmount(salesLedgerProduct.getTaxInclusiveTotalPrice());
|
salesLedgerProduct.setPendingInvoiceTotal(salesLedgerProduct.getTaxInclusiveTotalPrice());
|
salesLedgerProductMapper.insert(salesLedgerProduct);
|
// 绑定产品额外加工
|
// 清空销售产品绑定的加工
|
salesLedgerProductProcessBindService.updateProductProcessBind(salesLedgerProduct.getSalesProductProcessList(), salesLedgerProduct.getId());
|
// 添加生产数据
|
salesLedgerProductServiceImpl.addProductionData(salesLedgerProduct);
|
}
|
}
|
}
|
|
private SalesLedger convertToEntity(SalesLedgerDto dto) {
|
SalesLedger entity = new SalesLedger();
|
BeanUtils.copyProperties(dto, entity);
|
return entity;
|
}
|
|
@Transactional(readOnly = true)
|
public String generateSalesContractNo() {
|
LocalDate currentDate = LocalDate.now();
|
String datePart = currentDate.format(DateTimeFormatter.BASIC_ISO_DATE);
|
String lockKey = LOCK_PREFIX + datePart;
|
String lockValue = Thread.currentThread().getId() + "-" + System.nanoTime(); // 唯一标识锁持有者
|
|
try {
|
// 1. 尝试获取分布式锁(循环直到超时)
|
long startWaitTime = System.currentTimeMillis();
|
while (System.currentTimeMillis() - startWaitTime < LOCK_WAIT_TIMEOUT * 1000) {
|
// SET key value NX PX 30000:仅当锁不存在时获取,设置30秒过期
|
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, LOCK_EXPIRE_TIME, TimeUnit.SECONDS);
|
if (Boolean.TRUE.equals(locked)) {
|
break; // 成功获取锁
|
}
|
// 短暂休眠避免忙等待
|
try {
|
Thread.sleep(100);
|
} catch (InterruptedException e) {
|
Thread.currentThread().interrupt();
|
throw new RuntimeException("获取锁时被中断", e);
|
}
|
}
|
|
if (Boolean.FALSE.equals(redisTemplate.hasKey(lockKey))) {
|
throw new RuntimeException("获取合同编号生成锁失败:超时");
|
}
|
|
// 2. 查询当天/公司已存在的序列号(与原逻辑一致)
|
// 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);
|
|
return datePart + String.format("%03d", nextSequence);
|
} finally {
|
// 3. 释放锁
|
String luaScript = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";
|
redisTemplate.execute(
|
new DefaultRedisScript<>(luaScript, Long.class),
|
Collections.singletonList(lockKey),
|
lockValue
|
);
|
}
|
}
|
|
@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;
|
}
|
// 排序后查找第一个缺失的正整数
|
sequences.sort(Integer::compareTo);
|
int next = 1;
|
for (int seq : sequences) {
|
if (seq == next) {
|
next++;
|
} else if (seq > next) {
|
break;
|
}
|
}
|
return next;
|
}
|
|
public <T, S> void updateMainContractAmount(
|
Long mainId,
|
List<T> subList,
|
Function<T, BigDecimal> amountGetter,
|
BaseMapper<S> mainMapper,
|
Class<S> mainEntityClass) {
|
|
if (mainId == null || subList == null || subList.isEmpty()) {
|
return;
|
}
|
|
// 计算子表金额总和
|
BigDecimal totalAmount = subList.stream()
|
.map(amountGetter)
|
.filter(Objects::nonNull)
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
// 构造主表更新对象
|
try {
|
S entity = mainEntityClass.getDeclaredConstructor().newInstance();
|
Field idField = mainEntityClass.getDeclaredField("id");
|
idField.setAccessible(true);
|
idField.set(entity, mainId);
|
|
Field amountField = mainEntityClass.getDeclaredField("contractAmount");
|
amountField.setAccessible(true);
|
amountField.set(entity, totalAmount);
|
|
mainMapper.updateById(entity);
|
} catch (Exception e) {
|
throw new RuntimeException("动态更新主表金额失败", e);
|
}
|
}
|
|
// 内部类用于存储聚合结果
|
private static class GroupedCustomer {
|
private final Long customerId;
|
private final String customerName;
|
private BigDecimal totalAmount = BigDecimal.ZERO;
|
|
public GroupedCustomer(Long customerId, String customerName) {
|
this.customerId = customerId;
|
this.customerName = customerName;
|
}
|
|
public void addAmount(BigDecimal amount) {
|
if (amount != null) {
|
this.totalAmount = this.totalAmount.add(amount);
|
}
|
}
|
|
public Long getCustomerId() {
|
return customerId;
|
}
|
|
public String getCustomerName() {
|
return customerName;
|
}
|
|
public BigDecimal getTotalAmount() {
|
return totalAmount;
|
}
|
}
|
}
|