From cb635802bd0187fd2874c8ad3d6664d4c7aa8555 Mon Sep 17 00:00:00 2001
From: liding <756868258@qq.com>
Date: 星期五, 09 五月 2025 15:09:04 +0800
Subject: [PATCH] 1.销售台账合同号生成 2.关联产品新增删除 3.采购台账

---
 src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java                        |    2 
 src/main/resources/mapper/sales/SalesLedgerMapper.xml                        |   12 +
 src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java         |   23 ++
 src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java            |   15 +
 src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java |   44 ++++
 src/main/java/com/ruoyi/common/config/MyBaseMapper.java                      |   20 ++
 src/main/java/com/ruoyi/sales/pojo/SalesLedger.java                          |    8 
 src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java               |    2 
 src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java          |   38 +++
 src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java          |    4 
 src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java                  |    7 
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java       |  157 +++++++++++++++-
 src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java    |   73 ++++++++
 src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java           |    4 
 src/main/resources/application.yml                                           |    2 
 src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java                    |   97 ++++++++++
 16 files changed, 480 insertions(+), 28 deletions(-)

diff --git a/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
index 3dd42a9..3c12a6f 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -17,6 +17,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 
@@ -113,9 +114,40 @@
     }
 
     @Override
-    public List customerList(Customer customer) {
+    public List<Map<String, Object>> customerList(Customer customer) {
         LambdaQueryWrapper<Customer> queryWrapper = Wrappers.lambdaQuery();
-        queryWrapper.select(Customer::getId, Customer::getCustomerName);
-        return customerMapper.selectMaps(queryWrapper);
+        queryWrapper.select(Customer::getId, Customer::getCustomerName,Customer::getTaxpayerIdentificationNumber);
+
+        // 鑾峰彇鍘熷鏌ヨ缁撴灉
+        List<Map<String, Object>> result = customerMapper.selectMaps(queryWrapper);
+
+        // 灏嗕笅鍒掔嚎鍛藉悕杞崲涓洪┘宄板懡鍚�
+        return result.stream().map(map -> map.entrySet().stream()
+                .collect(Collectors.toMap(
+                        entry -> underlineToCamel(entry.getKey()),
+                        Map.Entry::getValue))
+        ).collect(Collectors.toList());
+    }
+
+    /**
+     * 涓嬪垝绾垮懡鍚嶈浆椹煎嘲鍛藉悕
+     */
+    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();
     }
 }
diff --git a/src/main/java/com/ruoyi/common/config/MyBaseMapper.java b/src/main/java/com/ruoyi/common/config/MyBaseMapper.java
new file mode 100644
index 0000000..7f39c5e
--- /dev/null
+++ b/src/main/java/com/ruoyi/common/config/MyBaseMapper.java
@@ -0,0 +1,20 @@
+package com.ruoyi.common.config;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+import java.util.List;
+
+public interface MyBaseMapper<T> extends BaseMapper<T> {
+
+    /**
+     * 鎵归噺鎻掑叆锛堜粎鎻掑叆闈炵┖瀛楁锛�
+     * @param list 瀹炰綋鍒楄〃
+     * @return 鎻掑叆鎴愬姛鐨勮褰曟暟
+     */
+    int insertBatchSomeColumn(List<T> list);
+
+    /**
+     * 鎵归噺鏇存柊锛堜粎鏇存柊闈炵┖瀛楁锛�
+     */
+    int updateBatchSomeColumn(List<T> list);
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java b/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
new file mode 100644
index 0000000..7f0fb01
--- /dev/null
+++ b/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
@@ -0,0 +1,73 @@
+package com.ruoyi.purchase.controller;
+
+import javax.servlet.http.HttpServletResponse;
+
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.purchase.pojo.PurchaseLedger;
+import com.ruoyi.purchase.service.IPurchaseLedgerService;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
+import com.ruoyi.framework.web.controller.BaseController;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.framework.web.page.TableDataInfo;
+
+import java.util.List;
+
+/**
+ * 閲囪喘鍙拌处Controller
+ * 
+ * @author ruoyi
+ * @date 2025-05-09
+ */
+@RestController
+@RequestMapping("/system/ledger")
+@AllArgsConstructor
+public class PurchaseLedgerController extends BaseController {
+    private IPurchaseLedgerService purchaseLedgerService;
+
+    /**
+     * 鏌ヨ閲囪喘鍙拌处鍒楄〃
+     */
+    @GetMapping("/list")
+    public TableDataInfo list(PurchaseLedger purchaseLedger) {
+        startPage();
+        List<PurchaseLedger> list = purchaseLedgerService.selectPurchaseLedgerList(purchaseLedger);
+        return getDataTable(list);
+    }
+
+    /**
+     * 瀵煎嚭閲囪喘鍙拌处鍒楄〃
+     */
+    @Log(title = "閲囪喘鍙拌处", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, PurchaseLedger purchaseLedger) {
+        List<PurchaseLedger> list = purchaseLedgerService.selectPurchaseLedgerList(purchaseLedger);
+        ExcelUtil<PurchaseLedger> util = new ExcelUtil<PurchaseLedger>(PurchaseLedger.class);
+        util.exportExcel(response, list, "銆愯濉啓鍔熻兘鍚嶇О銆戞暟鎹�");
+    }
+
+    /**
+     * 鏂板淇敼閲囪喘鍙拌处
+     */
+    @Log(title = "閲囪喘鍙拌处", businessType = BusinessType.INSERT)
+    @PostMapping ("/addOrEditPurchase")
+    public AjaxResult addOrEditPurchase(@RequestBody PurchaseLedger purchaseLedger) {
+        return toAjax(purchaseLedgerService.addOrEditPurchase(purchaseLedger));
+    }
+
+    /**
+     * 鍒犻櫎閲囪喘鍙拌处
+     */
+    @Log(title = "閲囪喘鍙拌处", businessType = BusinessType.DELETE)
+	@DeleteMapping("/delPurchase")
+    public AjaxResult remove(@RequestBody Long[] ids) {
+        return toAjax(purchaseLedgerService.deletePurchaseLedgerByIds(ids));
+    }
+}
diff --git a/src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java b/src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java
new file mode 100644
index 0000000..1328588
--- /dev/null
+++ b/src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java
@@ -0,0 +1,15 @@
+package com.ruoyi.purchase.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.purchase.pojo.PurchaseLedger;
+
+/**
+ * 閲囪喘鍙拌处Mapper鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2025-05-09
+ */
+public interface PurchaseLedgerMapper extends BaseMapper<PurchaseLedger> {
+
+}
diff --git a/src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java b/src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java
new file mode 100644
index 0000000..02cdab9
--- /dev/null
+++ b/src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java
@@ -0,0 +1,97 @@
+package com.ruoyi.purchase.pojo;
+
+import java.util.Date;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import lombok.Data;
+
+/**
+ * 閲囪喘鍙拌处瀵硅薄 purchase_ledger
+ *
+ * @author ruoyi
+ * @date 2025-05-09
+ */
+@TableName("purchase_ledger")
+@Data
+public class PurchaseLedger {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鑷涓婚敭ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 閲囪喘鍚堝悓鍙�
+     */
+    @Excel(name = "閲囪喘鍚堝悓鍙�")
+    private String purchaseContractNumber;
+
+    /**
+     * 渚涘簲鍟嗗悕绉�
+     */
+    @Excel(name = "渚涘簲鍟嗗悕绉�")
+    private String supplierName;
+
+    /**
+     * 褰曞叆浜哄鍚�
+     */
+    @Excel(name = "褰曞叆浜哄鍚�")
+    private String recorderName;
+
+    /**
+     * 閿�鍞悎鍚屽彿
+     */
+    @Excel(name = "閿�鍞悎鍚屽彿")
+    private String salesContractNo;
+
+    /**
+     * 椤圭洰鍚嶇О
+     */
+    @Excel(name = "椤圭洰鍚嶇О")
+    private String projectName;
+
+    /**
+     * 褰曞叆鏃ユ湡
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "褰曞叆鏃ユ湡", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date entryDate;
+
+    /**
+     * 澶囨敞
+     */
+    @Excel(name = "澶囨敞")
+    private String remarks;
+
+    /**
+     * 闄勪欢鏉愭枡璺緞鎴栧悕绉�
+     */
+    @Excel(name = "闄勪欢鏉愭枡璺緞鎴栧悕绉�")
+    private String attachmentMaterials;
+
+    /**
+     * 璁板綍鍒涘缓鏃堕棿
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "璁板綍鍒涘缓鏃堕棿", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date createdAt;
+
+    /**
+     * 璁板綍鏈�鍚庢洿鏂版椂闂�
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd")
+    @Excel(name = "璁板綍鏈�鍚庢洿鏂版椂闂�", width = 30, dateFormat = "yyyy-MM-dd")
+    private Date updatedAt;
+
+    /**
+     * 鍏宠仈閿�鍞彴璐︿富琛ㄤ富閿�
+     */
+    private Long salesLedgerId;
+
+}
diff --git a/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java b/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
new file mode 100644
index 0000000..120c15c
--- /dev/null
+++ b/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
@@ -0,0 +1,23 @@
+package com.ruoyi.purchase.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.purchase.pojo.PurchaseLedger;
+
+import java.util.List;
+
+/**
+ * 閲囪喘鍙拌处Service鎺ュ彛
+ *
+ * @author ruoyi
+ * @date 2025-05-09
+ */
+public interface IPurchaseLedgerService extends IService<PurchaseLedger> {
+
+    List<PurchaseLedger> selectPurchaseLedgerList(PurchaseLedger purchaseLedger);
+
+    int addOrEditPurchase(PurchaseLedger purchaseLedger);
+
+    int deletePurchaseLedgerByIds(Long[] ids);
+
+}
diff --git a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
new file mode 100644
index 0000000..a209ccb
--- /dev/null
+++ b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -0,0 +1,44 @@
+package com.ruoyi.purchase.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
+import com.ruoyi.purchase.pojo.PurchaseLedger;
+import com.ruoyi.purchase.service.IPurchaseLedgerService;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 閲囪喘鍙拌处Service涓氬姟灞傚鐞�
+ *
+ * @author ruoyi
+ * @date 2025-05-09
+ */
+@Service
+@AllArgsConstructor
+public class PurchaseLedgerServiceImpl extends ServiceImpl<PurchaseLedgerMapper, PurchaseLedger> implements IPurchaseLedgerService {
+
+    private PurchaseLedgerMapper purchaseLedgerMapper;
+
+    @Override
+    public List<PurchaseLedger> selectPurchaseLedgerList(PurchaseLedger purchaseLedger) {
+        return purchaseLedgerMapper.selectList(new LambdaQueryWrapper<>());
+    }
+
+    @Override
+    public int addOrEditPurchase(PurchaseLedger purchaseLedger) {
+        if (purchaseLedger.getId() == null) {
+            return purchaseLedgerMapper.insert(purchaseLedger);
+        } else {
+            return purchaseLedgerMapper.updateById(purchaseLedger);
+        }
+    }
+
+    @Override
+    public int deletePurchaseLedgerByIds(Long[] ids) {
+        return purchaseLedgerMapper.deleteBatchIds(Arrays.asList(ids));
+    }
+}
diff --git a/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java b/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
index 3c93788..3f325b1 100644
--- a/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
+++ b/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -82,9 +82,9 @@
      */
     @Log(title = "閿�鍞彴璐�", businessType = BusinessType.INSERT)
     @PostMapping ("/addOrUpdateSalesLedger")
-    public AjaxResult add(@RequestBody SalesLedger salesLedger)
+    public AjaxResult add(@RequestBody SalesLedgerDto salesLedgerDto)
     {
-        return toAjax(salesLedgerService.addOrUpdateSalesLedger(salesLedger));
+        return toAjax(salesLedgerService.addOrUpdateSalesLedger(salesLedgerDto));
     }
 
     /**
diff --git a/src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java b/src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
index cc2bcf4..d1a5c9c 100644
--- a/src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
+++ b/src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java
@@ -22,5 +22,5 @@
     private String remarks;
     private String attachmentMaterials;
     private Boolean hasChildren = false;
-    private List<SalesLedgerProduct> children;
+    private List<SalesLedgerProduct> productData;
 }
diff --git a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
index 9980acc..c188733 100644
--- a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
+++ b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
@@ -3,6 +3,7 @@
 import java.util.List;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.ruoyi.sales.pojo.SalesLedger;
+import org.apache.ibatis.annotations.Param;
 
 
 /**
@@ -12,4 +13,10 @@
  * @date 2025-05-08
  */
 public interface SalesLedgerMapper extends BaseMapper<SalesLedger> {
+    /**
+     * 鏌ヨ鎸囧畾鏃ユ湡鐨勬墍鏈夊悎鍚屽簭鍒楀彿
+     * @param datePart 鏃ユ湡閮ㄥ垎锛堟牸寮忥細yyyyMMdd锛�
+     * @return 搴忓垪鍙峰垪琛�
+     */
+    List<Integer> selectSequencesByDate(@Param("datePart") String datePart);
 }
diff --git a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
index 145bf38..69b2b7b 100644
--- a/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
+++ b/src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
@@ -1,6 +1,6 @@
 package com.ruoyi.sales.mapper;
 
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.common.config.MyBaseMapper;
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
 
 /**
@@ -9,5 +9,5 @@
  * @author ruoyi
  * @date 2025-05-08
  */
-public interface SalesLedgerProductMapper extends BaseMapper<SalesLedgerProduct> {
+public interface SalesLedgerProductMapper extends MyBaseMapper<SalesLedgerProduct> {
 }
diff --git a/src/main/java/com/ruoyi/sales/pojo/SalesLedger.java b/src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
index bb4f237..b6eaf67 100644
--- a/src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
+++ b/src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
@@ -1,5 +1,6 @@
 package com.ruoyi.sales.pojo;
 
+import java.math.BigDecimal;
 import java.util.Date;
 
 import com.baomidou.mybatisplus.annotation.*;
@@ -81,11 +82,16 @@
     /**
      * 闄勪欢鏉愭枡锛屽瓨鍌ㄦ枃浠跺悕绛夌浉鍏充俊鎭�
      */
-    @Excel(name = "闄勪欢鏉愭枡锛屽瓨鍌ㄦ枃浠跺悕绛夌浉鍏充俊鎭�")
     private String attachmentMaterials;
 
     @TableField(fill = FieldFill.INSERT)
     private Long tenantId;
 
+
+    /**
+     * 鍚堝悓閲戦锛堜骇鍝佸惈绋庢�讳环锛�
+     */
+    @Excel(name = "绋庣巼")
+    private BigDecimal contractAmount;
 }
 
diff --git a/src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java b/src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
index 7ee8c1f..a716de3 100644
--- a/src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
+++ b/src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
@@ -20,7 +20,7 @@
 
     int deleteSalesLedgerByIds(Long[] ids);
 
-    int addOrUpdateSalesLedger(SalesLedger salesLedger);
+    int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto);
 
     List<SalesLedgerDto> getSalesLedgerWithProducts();
 }
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
index 5048386..a1f884d 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -1,6 +1,7 @@
 package com.ruoyi.sales.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.basic.mapper.CustomerMapper;
 import com.ruoyi.basic.pojo.Customer;
@@ -12,12 +13,16 @@
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import com.ruoyi.sales.service.ISalesLedgerService;
 import lombok.AllArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -35,6 +40,12 @@
     private CustomerMapper customerMapper;
 
     private SalesLedgerProductMapper salesLedgerProductMapper;
+
+    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 RedisTemplate<String, String> redisTemplate;
 
     @Override
     public List<SalesLedger> selectSalesLedgerList(SalesLedger salesLedger) {
@@ -55,7 +66,7 @@
             List<SalesLedgerProduct> ledgerProducts = productMap.getOrDefault(ledger.getId(), Collections.emptyList());
             if (!ledgerProducts.isEmpty()) {
                 dto.setHasChildren(true);
-                dto.setChildren(ledgerProducts);
+                dto.setProductData(ledgerProducts);
             }
             return dto;
         }).collect(Collectors.toList());
@@ -67,27 +78,139 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public int deleteSalesLedgerByIds(Long[] ids) {
-        return salesLedgerMapper.deleteBatchIds(Arrays.asList(ids));
+        List<Long> idList = Arrays.stream(ids)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (CollectionUtils.isEmpty(idList)) {
+            return 0;
+        }
+        // 1. 鍏堝垹闄ゅ瓙琛ㄦ暟鎹�
+        LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
+        productWrapper.in(SalesLedgerProduct::getSalesLedgerId, idList);
+        salesLedgerProductMapper.delete(productWrapper);
+
+        // 2. 鍐嶅垹闄や富琛ㄦ暟鎹�
+        return salesLedgerMapper.deleteBatchIds(idList);
     }
 
-    public int addOrUpdateSalesLedger(SalesLedger salesLedger) {
-        LambdaQueryWrapper<Customer> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(Customer::getId, salesLedger.getCustomerId());
-        Customer customer = customerMapper.selectOne(queryWrapper);
+    @Transactional(rollbackFor = Exception.class)
+    public int addOrUpdateSalesLedger(SalesLedgerDto salesLedgerDto) {
+        // 1. 鏍¢獙瀹㈡埛淇℃伅
+        Customer customer = customerMapper.selectById(salesLedgerDto.getCustomerId());
         if (customer == null) {
-            throw new BaseException("鏈煡璇㈠埌瀵瑰簲鐨� Customer 淇℃伅");
+            throw new BaseException("瀹㈡埛涓嶅瓨鍦�");
         }
+
+        // 2. DTO杞珽ntity
+        SalesLedger salesLedger = convertToEntity(salesLedgerDto);
         salesLedger.setCustomerName(customer.getCustomerName());
         salesLedger.setTenantId(customer.getTenantId());
-        return saveOrUpdates(salesLedger);
+
+        // 3. 鏂板鎴栨洿鏂颁富琛�
+        if (salesLedger.getId() == null) {
+            // 鐢熸垚鍚堝悓缂栧彿
+            String contractNo = generateSalesContractNo();
+            salesLedger.setSalesContractNo(contractNo);
+            salesLedgerMapper.insert(salesLedger);
+        } else {
+            salesLedgerMapper.updateById(salesLedger);
+        }
+
+        // 4. 澶勭悊瀛愯〃鏁版嵁
+        if (salesLedgerDto.getProductData() != null && !salesLedgerDto.getProductData().isEmpty()) {
+            handleSalesLedgerProducts(salesLedger.getId(), salesLedgerDto.getProductData());
+        }
+
+        return 1; // 鎿嶄綔鎴愬姛杩斿洖1
     }
 
-    private int saveOrUpdates(SalesLedger salesLedger) {
-        if (salesLedger.getId() == null) {
-            return salesLedgerMapper.insert(salesLedger);
-        } else {
-            return salesLedgerMapper.updateById(salesLedger);
+    private void handleSalesLedgerProducts(Long salesLedgerId, List<SalesLedgerProduct> products) {
+        // 鎸塈D鍒嗙粍锛屽尯鍒嗘柊澧炲拰鏇存柊鐨勮褰�
+        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()) {
+            salesLedgerProductMapper.updateBatchSomeColumn(updateList);
         }
+        // 鎵ц鎻掑叆鎿嶄綔
+        if (!insertList.isEmpty()) {
+            salesLedgerProductMapper.insertBatchSomeColumn(insertList);
+        }
+    }
+
+    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 (!redisTemplate.hasKey(lockKey)) {
+                throw new RuntimeException("鑾峰彇鍚堝悓缂栧彿鐢熸垚閿佸け璐ワ細瓒呮椂");
+            }
+
+            // 2. 鏌ヨ褰撳ぉ宸插瓨鍦ㄧ殑搴忓垪鍙凤紙涓庡師閫昏緫涓�鑷达級
+            List<Integer> existingSequences = salesLedgerMapper.selectSequencesByDate(datePart);
+            int nextSequence = findFirstMissingSequence(existingSequences);
+
+            return datePart + String.format("%02d", nextSequence);
+        } finally {
+            // 3. 閲婃斁閿侊紙浣跨敤Lua鑴氭湰淇濊瘉鍘熷瓙鎬э紝閬垮厤璇垹鍏朵粬绾跨▼鐨勯攣锛�
+            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 // 鍙湁鎸佹湁鐩稿悓鍊肩殑绾跨▼鎵嶈兘鍒犻櫎閿�
+            );
+        }
+    }
+
+    private int findFirstMissingSequence(List<Integer> sequences) {
+        if (sequences.isEmpty()) {
+            return 1;
+        }
+        // 鎺掑簭鍚庢煡鎵剧涓�涓己澶辩殑姝f暣鏁帮紙涓庡師閫昏緫涓�鑷达級
+        sequences.sort(Integer::compareTo);
+        int next = 1;
+        for (int seq : sequences) {
+            if (seq == next) {
+                next++;
+            } else if (seq > next) {
+                break;
+            }
+        }
+        return next;
     }
 }
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 1a89bcf..2154aae 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -100,7 +100,7 @@
 # MyBatis Plus閰嶇疆
 mybatis-plus:
   # 鎼滅储鎸囧畾鍖呭埆鍚�   鏍规嵁鑷繁鐨勯」鐩潵
-  typeAliasesPackage: com.ruoyi.basic.**.pojo
+  typeAliasesPackage: com.ruoyi.**.pojo
   # 閰嶇疆mapper鐨勬壂鎻忥紝鎵惧埌鎵�鏈夌殑mapper.xml鏄犲皠鏂囦欢
   mapperLocations: classpath*:mapper/**/*Mapper.xml
   # 鍔犺浇鍏ㄥ眬鐨勯厤缃枃浠�
diff --git a/src/main/resources/mapper/sales/SalesLedgerMapper.xml b/src/main/resources/mapper/sales/SalesLedgerMapper.xml
new file mode 100644
index 0000000..16ba140
--- /dev/null
+++ b/src/main/resources/mapper/sales/SalesLedgerMapper.xml
@@ -0,0 +1,12 @@
+<?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.sales.mapper.SalesLedgerMapper">
+
+    <select id="selectSequencesByDate" resultType="java.lang.Integer">
+        SELECT CAST(SUBSTR(sales_contract_no, 9, 2) AS SIGNED)
+        FROM sales_ledger
+        WHERE SUBSTR(sales_contract_no, 1, 8) = #{datePart}
+    </select>
+</mapper>
\ No newline at end of file

--
Gitblit v1.9.3