From 5f73eb1fb9d04040fe6ba377d5ae582024429f2f Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期日, 20 九月 2026 14:15:02 +0800
Subject: [PATCH] feat(sales): 添加客户交易详情中的付款金额字段

---
 src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java |  318 +++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 287 insertions(+), 31 deletions(-)

diff --git a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
index 4f83c66..a81fca7 100644
--- a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -6,6 +6,7 @@
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.alibaba.fastjson2.JSON;
 import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
 import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
 import com.ruoyi.approve.pojo.ApprovalInstance;
@@ -15,17 +16,20 @@
 import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl;
 import com.ruoyi.basic.enums.ApplicationTypeEnum;
 import com.ruoyi.basic.enums.RecordTypeEnum;
+import com.ruoyi.basic.mapper.OilDepotMapper;
 import com.ruoyi.basic.mapper.ProductMapper;
 import com.ruoyi.basic.mapper.ProductModelMapper;
 import com.ruoyi.basic.mapper.SupplierManageMapper;
+import com.ruoyi.basic.pojo.OilDepot;
 import com.ruoyi.basic.pojo.Product;
 import com.ruoyi.basic.pojo.ProductModel;
 import com.ruoyi.basic.pojo.SupplierManage;
 import com.ruoyi.basic.utils.FileUtil;
-import com.ruoyi.common.exception.base.BaseException;
 import com.ruoyi.common.enums.ApprovalStatusEnum;
 import com.ruoyi.common.enums.ReviewStatusEnum;
 import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
+import com.ruoyi.common.enums.TypeEnums;
+import com.ruoyi.common.exception.base.BaseException;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
@@ -33,6 +37,10 @@
 import com.ruoyi.framework.security.LoginUser;
 import com.ruoyi.framework.web.domain.AjaxResult;
 import com.ruoyi.framework.web.domain.R;
+import com.ruoyi.measuringinstrumentledger.mapper.SparePartsMapper;
+import com.ruoyi.measuringinstrumentledger.pojo.SpareParts;
+import com.ruoyi.measuringinstrumentledger.pojo.SparePartsRecord;
+import com.ruoyi.measuringinstrumentledger.service.SparePartsRecordService;
 import com.ruoyi.other.mapper.TempFileMapper;
 import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
 import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
@@ -92,6 +100,11 @@
 @RequiredArgsConstructor
 public class PurchaseLedgerServiceImpl extends ServiceImpl<PurchaseLedgerMapper, PurchaseLedger> implements IPurchaseLedgerService {
 
+    /**
+     * 鏄庣粏绫诲瀷锛氶噰璐紙sales_ledger_product.type锛夈�備粎閲囪喘鏄庣粏鎵嶅洖鍐欏浠跺簱瀛�
+     */
+    private static final Integer LEDGER_TYPE_PURCHASE = 2;
+
     private final PurchaseLedgerMapper purchaseLedgerMapper;
     private final SalesLedgerMapper salesLedgerMapper;
     private final SalesLedgerProductMapper salesLedgerProductMapper;
@@ -116,6 +129,9 @@
     private final StockInRecordService stockInRecordService;
     private final StockUtils stockUtils;
     private final ApprovalTemplateMapper approvalTemplateMapper;
+    private final OilDepotMapper oilDepotMapper;
+    private final SparePartsMapper sparePartsMapper;
+    private final SparePartsRecordService sparePartsRecordService;
 
     @Override
     public List<PurchaseLedger> selectPurchaseLedgerList(PurchaseLedger purchaseLedger) {
@@ -129,7 +145,15 @@
         if (purchaseLedger.getApprovalStatus() != null) {
             queryWrapper.eq(PurchaseLedger::getApprovalStatus, purchaseLedger.getApprovalStatus());
         }
+        // 鍙煡璇㈠叆搴撳凡瀹℃壒閫氳繃鐨勯噰璐彴璐�
+        queryWrapper.inSql(PurchaseLedger::getId,
+                "SELECT DISTINCT record_id FROM stock_in_record WHERE approval_status = 1");
         return purchaseLedgerMapper.selectList(queryWrapper);
+    }
+
+    @Override
+    public List<PurchaseLedger> selectReturnablePurchaseLedgerList(PurchaseLedger purchaseLedger) {
+        return purchaseLedgerMapper.selectReturnablePurchaseLedgerList(purchaseLedger);
     }
 
     @Override
@@ -161,22 +185,24 @@
         purchaseLedger.setSupplierName(supplierManage.getSupplierName());
         purchaseLedger.setRecorderId(purchaseLedgerDto.getRecorderId());
 
-        purchaseLedger.setApprovalStatus(1);
-        // 3. 鏂板鎴栨洿鏂颁富琛�
-        if (purchaseLedger.getId() == null) {
-            purchaseLedgerMapper.insert(purchaseLedger);
-        } else {
-            // 鍒犻櫎閲囪喘瀹℃牳锛岄噸鏂版彁浜�
-            ApproveProcess one = approveProcessService.getOne(new LambdaQueryWrapper<ApproveProcess>()
-                    .eq(ApproveProcess::getApproveType, 5)
-                    .eq(ApproveProcess::getApproveReason, purchaseLedger.getPurchaseContractNumber())
-                    .eq(ApproveProcess::getApproveDelete, 0)
-                    .last("limit 1"));
-            if (one != null) {
-                approveProcessService.delByIds(Collections.singletonList(one.getId()));
+        // 閲囪喘娌瑰簱锛氭牴鎹补搴搃d鍥炲~娌瑰簱鍚嶇О
+        if (purchaseLedgerDto.getOilDepotId() != null) {
+            OilDepot oilDepot = oilDepotMapper.selectById(purchaseLedgerDto.getOilDepotId());
+            if (oilDepot != null) {
+                purchaseLedger.setOilDepotName(oilDepot.getDepotName());
             }
-            purchaseLedgerMapper.updateById(purchaseLedger);
         }
+
+        purchaseLedger.setApprovalStatus(1);
+        // 瀹為檯鍚堝悓閲戦濮嬬粓绛変簬鍚堝悓閲戦
+        purchaseLedger.setNetContractAmount(purchaseLedger.getContractAmount());
+        // 3. 鏂板鎴栨洿鏂颁富琛�
+        if (purchaseLedger.getId() == null && !StringUtils.hasText(purchaseLedger.getPurchaseContractNumber())) {
+            purchaseLedger.setPurchaseContractNumber(generatePurchaseContractNo(purchaseLedger.getEntryDate()));
+        }
+        this.saveOrUpdate(purchaseLedger);
+        // 鍥炲啓涓婚敭锛屼緵璋冪敤鏂癸紙濡傞噰璐敵璇疯浆璁㈠崟锛夎幏鍙栨柊鐢熸垚鐨勯噰璐彴璐d
+        purchaseLedgerDto.setId(purchaseLedger.getId());
 
         // 4. 澶勭悊瀛愯〃鏁版嵁
         List<SalesLedgerProduct> productList = purchaseLedgerDto.getProductData();
@@ -184,7 +210,17 @@
             handleSalesLedgerProducts(purchaseLedger.getId(), productList, purchaseLedgerDto.getType());
         }
         // 6.閲囪喘瀹℃牳鏂板锛涘鎵圭鐞嗘湭閰嶇疆閲囪喘瀹℃壒浜烘椂锛屽鎵规湇鍔′細鑷姩缃负瀹℃壒閫氳繃銆�
-        addApproveByPurchase(loginUser, purchaseLedger);
+        //鏌ヨ褰撳墠閲囪喘鍚堝悓鍙锋槸鍚﹀凡娣诲姞瀹℃壒锛屽鏋滄湁涓斾笉鏄┏鍥炵姸鎬侊紝鍒欎笉閲嶅娣诲姞
+        ApprovalInstance oldApprovalInstance = approvalInstanceService.getOne(Wrappers.<ApprovalInstance>lambdaQuery()
+                .eq(ApprovalInstance::getDeleted, 0)
+                .eq(ApprovalInstance::getBusinessType, TypeEnums.PURCHASE_APPROVAL.getCode())
+                .eq(ApprovalInstance::getBusinessId, purchaseLedger.getId())
+                .ne(ApprovalInstance::getStatus,"REJECTED")
+                .last("limit 1")
+        );
+        if(Objects.isNull(oldApprovalInstance)){
+            addApproveByPurchase(loginUser, purchaseLedger, productList);
+        }
         // 5. 杩佺Щ涓存椂鏂囦欢鍒版寮忕洰褰�
         fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.PURCHASE_LEDGER, purchaseLedger.getId(), purchaseLedgerDto.getStorageBlobDTOS());
         return 1;
@@ -683,6 +719,10 @@
                 salesLedgerProduct.setRegisterDate(localDateTime);
                 salesLedgerProductMapper.insert(salesLedgerProduct);
             }
+            // 浠呮柊澧炴槑缁嗗洖鍐欏浠讹紝缂栬緫璧� update 鍒嗘敮澶╃劧涓嶉噸澶嶈鏁�
+            if (LEDGER_TYPE_PURCHASE.equals(ledgerType)) {
+                recordSparePartsInbound(insertList, purchaseLedger);
+            }
         }
 
         // 璁$畻鎬诲惈绋庨噾棰�
@@ -695,6 +735,47 @@
         if (salesLedgerId != null) {
             // 鐩存帴鏇存柊鎸囧畾ID鐨勮褰曠殑contractAmount瀛楁涓簍otalTaxInclusiveAmount
             purchaseLedgerMapper.updateContractAmountById(salesLedgerId, totalTaxInclusiveAmount);
+        }
+    }
+
+    /**
+     * 閲囪喘鍙拌处鏂板鏄庣粏鎸夎鏍煎尮閰嶅浠讹細绱姞澶囦欢搴撳瓨骞惰涓�鏉″叆搴撴祦姘淬��
+     * 鏈淮鎶よ鏍肩殑澶囦欢涓嶅弬涓庯紝闈欓粯璺宠繃銆�
+     */
+    private void recordSparePartsInbound(List<SalesLedgerProduct> insertList, PurchaseLedger purchaseLedger) {
+        List<Long> modelIds = insertList.stream()
+                .map(SalesLedgerProduct::getProductModelId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        if (modelIds.isEmpty()) {
+            return;
+        }
+        List<SpareParts> sparePartsList = sparePartsMapper.selectList(new LambdaQueryWrapper<SpareParts>()
+                .in(SpareParts::getProductModelId, modelIds));
+        if (CollectionUtils.isEmpty(sparePartsList)) {
+            return;
+        }
+        Map<Long, SpareParts> sparePartsByModelId = sparePartsList.stream()
+                .collect(Collectors.toMap(SpareParts::getProductModelId, s -> s, (a, b) -> a));
+        for (SalesLedgerProduct product : insertList) {
+            SpareParts spareParts = sparePartsByModelId.get(product.getProductModelId());
+            if (spareParts == null || product.getQuantity() == null
+                    || product.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
+                continue;
+            }
+            BigDecimal quantity = product.getQuantity();
+            spareParts.setQuantity((spareParts.getQuantity() == null ? BigDecimal.ZERO : spareParts.getQuantity()).add(quantity));
+            sparePartsMapper.updateById(spareParts);
+
+            SparePartsRecord record = new SparePartsRecord();
+            record.setSparePartsId(spareParts.getId());
+            record.setDirection(1);
+            record.setQuantity(quantity);
+            record.setSourceType(1);
+            record.setSourceId(purchaseLedger.getId());
+            record.setSourceNo(purchaseLedger.getPurchaseContractNumber());
+            sparePartsRecordService.save(record);
         }
     }
 
@@ -751,13 +832,30 @@
         for (Long id : ids) {
             PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(id);
             if(purchaseLedger != null){
-                ApproveProcess one = approveProcessService.getOne(new LambdaQueryWrapper<ApproveProcess>()
+                List<ApprovalInstance> approvalInstances = approvalInstanceService.list(new LambdaQueryWrapper<ApprovalInstance>()
+                        .eq(ApprovalInstance::getBusinessId, purchaseLedger.getId())
+                        .eq(ApprovalInstance::getBusinessType, 5L)
+                        .eq(ApprovalInstance::getDeleted, 0));
+                if (CollectionUtils.isNotEmpty(approvalInstances)) {
+                    approvalInstanceService.delete(
+                            approvalInstances.stream()
+                                    .map(ApprovalInstance::getId)
+                                    .filter(Objects::nonNull)
+                                    .collect(Collectors.toList())
+                    );
+                }
+
+                List<ApproveProcess> approveProcesses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
                         .eq(ApproveProcess::getApproveType, 5)
                         .eq(ApproveProcess::getApproveDelete, 0)
-                        .eq(ApproveProcess::getApproveReason, purchaseLedger.getPurchaseContractNumber())
-                        .last("limit 1"));
-                if (one != null) {
-                    approveProcessService.delByIds(Collections.singletonList(one.getId()));
+                        .eq(ApproveProcess::getApproveReason, purchaseLedger.getPurchaseContractNumber()));
+                if (CollectionUtils.isNotEmpty(approveProcesses)) {
+                    approveProcessService.delByIds(
+                            approveProcesses.stream()
+                                    .map(ApproveProcess::getId)
+                                    .filter(Objects::nonNull)
+                                    .collect(Collectors.toList())
+                    );
                 }
             }
         }
@@ -938,6 +1036,7 @@
                 salesLedger.setContractAmount(salesLedgerProductImportDtos.stream()
                         .map(PurchaseLedgerProductImportDto::getTaxInclusiveTotalPrice)
                         .reduce(BigDecimal.ZERO,BigDecimal::add));
+                salesLedger.setNetContractAmount(salesLedger.getContractAmount());
                 // 閫氳繃閿�鍞崟鍙风粦瀹氶攢鍞�
                 SalesLedger salesLedger1 = salesLedgerMapper.selectOne(new LambdaQueryWrapper<SalesLedger>()
                         .eq(SalesLedger::getSalesContractNo, salesLedger.getSalesContractNo())
@@ -987,7 +1086,12 @@
                     salesLedgerProductMapper.insert(salesLedgerProduct);
                 }
                 // 閲囪喘瀹℃牳
-                addApproveByPurchase(loginUser,salesLedger);
+                List<SalesLedgerProduct> snapshotProducts = salesLedgerProductMapper.selectList(
+                        new LambdaQueryWrapper<SalesLedgerProduct>()
+                                .eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId())
+                                .eq(SalesLedgerProduct::getType, 2)
+                );
+                addApproveByPurchase(loginUser, salesLedger, snapshotProducts);
             }
 
             return AjaxResult.success("瀵煎叆鎴愬姛");
@@ -999,21 +1103,91 @@
 
     @Override
     public PurchaseLedgerDto getPurchaseByCode(PurchaseLedgerDto purchaseLedgerDto) {
-        // 1. 鏌ヨ涓昏〃
-        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
-                .eq(PurchaseLedger::getPurchaseContractNumber, purchaseLedgerDto.getPurchaseContractNumber())
-                .last("LIMIT 1"));
-        if (purchaseLedger == null) {
-            throw new BaseException("閲囪喘鍙拌处涓嶅瓨鍦�");
+        PurchaseLedger purchaseLedger = null;
+        ApprovalInstance approvalSnapshotInstance = null;
+
+        // 1. 浼樺厛鎸夊綋鍓嶅鎵瑰疄渚嬫煡璇㈠揩鐓�
+        if (purchaseLedgerDto.getApprovalInstanceId() != null) {
+            approvalSnapshotInstance = approvalInstanceService.getById(purchaseLedgerDto.getApprovalInstanceId());
+            if (approvalSnapshotInstance != null && approvalSnapshotInstance.getBusinessId() != null) {
+                purchaseLedger = purchaseLedgerMapper.selectById(approvalSnapshotInstance.getBusinessId());
+            }
         }
 
-        // 2. 鏌ヨ瀛愯〃
+        // 2. 娌′紶瀹℃壒瀹炰緥ID鏃讹紝閫�鍥炲埌鎸夐噰璐悎鍚屽彿鏌ヨ
+        if (purchaseLedger == null) {
+            purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
+                    .eq(PurchaseLedger::getPurchaseContractNumber, purchaseLedgerDto.getPurchaseContractNumber())
+                    .last("LIMIT 1"));
+            if (purchaseLedger == null) {
+                throw new BaseException("閲囪喘鍙拌处涓嶅瓨鍦�");
+            }
+        }
+
+        // 3. 鏌ヨ褰撳墠閲囪喘鍗曞搴旂殑瀹℃壒蹇収
+        if (approvalSnapshotInstance == null) {
+            approvalSnapshotInstance = approvalInstanceService.getOne(new LambdaQueryWrapper<ApprovalInstance>()
+                    .eq(ApprovalInstance::getBusinessId, purchaseLedger.getId())
+                    .eq(ApprovalInstance::getBusinessType, 5L)
+                    .eq(ApprovalInstance::getDeleted, 0)
+                    .orderByDesc(ApprovalInstance::getId)
+                    .last("LIMIT 1"));
+        }
+
+        if (approvalSnapshotInstance != null && StringUtils.hasText(approvalSnapshotInstance.getFormConfig())) {
+            try {
+                PurchaseLedgerDto snapshot = JSON.parseObject(approvalSnapshotInstance.getFormConfig(), PurchaseLedgerDto.class);
+                if (snapshot != null) {
+                    snapshot.setId(purchaseLedger.getId());
+                    if (snapshot.getPurchaseContractNumber() == null) {
+                        snapshot.setPurchaseContractNumber(purchaseLedger.getPurchaseContractNumber());
+                    }
+                    if (snapshot.getSupplierId() == null) {
+                        snapshot.setSupplierId(purchaseLedger.getSupplierId());
+                    }
+                    if (snapshot.getSupplierName() == null) {
+                        snapshot.setSupplierName(purchaseLedger.getSupplierName());
+                    }
+                    if (snapshot.getSalesContractNo() == null) {
+                        snapshot.setSalesContractNo(purchaseLedger.getSalesContractNo());
+                    }
+                    if (snapshot.getSalesContractNoId() == null) {
+                        snapshot.setSalesContractNoId(purchaseLedger.getSalesLedgerId());
+                    }
+                    if (snapshot.getProjectName() == null) {
+                        snapshot.setProjectName(purchaseLedger.getProjectName());
+                    }
+                    if (snapshot.getEntryDate() == null) {
+                        snapshot.setEntryDate(purchaseLedger.getEntryDate());
+                    }
+                    if (snapshot.getExecutionDate() == null) {
+                        snapshot.setExecutionDate(purchaseLedger.getExecutionDate());
+                    }
+                    if (snapshot.getRemarks() == null) {
+                        snapshot.setRemarks(purchaseLedger.getRemarks());
+                    }
+                    if (snapshot.getRecorderId() == null) {
+                        snapshot.setRecorderId(purchaseLedger.getRecorderId());
+                    }
+                    if (snapshot.getRecorderName() == null) {
+                        snapshot.setRecorderName(purchaseLedger.getRecorderName());
+                    }
+                    snapshot.setProductData(snapshot.getProductData() == null ? Collections.emptyList() : snapshot.getProductData());
+                    snapshot.setHasChildren(CollectionUtils.isNotEmpty(snapshot.getProductData()));
+                    return snapshot;
+                }
+            } catch (Exception e) {
+                log.warn("瑙f瀽閲囪喘瀹℃壒蹇収澶辫触: {}", e.getMessage());
+            }
+        }
+
+        // 4. 鏌ヨ瀛愯〃
         LambdaQueryWrapper<SalesLedgerProduct> productWrapper = new LambdaQueryWrapper<>();
         productWrapper.eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId())
                 .eq(SalesLedgerProduct::getType, 2);
         List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(productWrapper);
 
-        // 4. 杞崲 DTO
+        // 5. 杞崲 DTO
         PurchaseLedgerDto resultDto = new PurchaseLedgerDto();
         BeanUtils.copyProperties(purchaseLedger, resultDto);
         if (!products.isEmpty()) {
@@ -1023,7 +1197,7 @@
         return resultDto;
     }
 
-    public void addApproveByPurchase(LoginUser loginUser,PurchaseLedger purchaseLedger) throws Exception {
+    public void addApproveByPurchase(LoginUser loginUser, PurchaseLedger purchaseLedger, List<SalesLedgerProduct> productList) {
         if (loginUser == null) {
             return;
         }
@@ -1048,6 +1222,10 @@
         approvalInstance.setTitle(purchaseLedger.getPurchaseContractNumber()+"瀹℃壒");
         approvalInstance.setApplicantName(loginUser.getNickName());
         approvalInstance.setApplyTime(LocalDateTime.now());
+        PurchaseLedgerDto snapshot = new PurchaseLedgerDto();
+        BeanUtils.copyProperties(purchaseLedger, snapshot);
+        snapshot.setProductData(productList == null ? Collections.emptyList() : new ArrayList<>(productList));
+        approvalInstance.setFormConfig(JSON.toJSONString(snapshot));
         approvalInstanceService.add(approvalInstance);
 
     }
@@ -1073,4 +1251,82 @@
         }
         return sb.toString();
     }
+
+    private static final String PURCHASE_LOCK_PREFIX = "purchase_contract_no:";
+    private static final long PURCHASE_LOCK_WAIT_TIMEOUT = 10;
+    private static final long PURCHASE_LOCK_EXPIRE_TIME = 30;
+
+    private String generatePurchaseContractNo(Date entryDate) {
+        LocalDate currentDate = entryDate != null ? DateUtils.toLocalDate(entryDate) : LocalDate.now();
+        String datePart = currentDate.format(DateTimeFormatter.BASIC_ISO_DATE);
+        String lockKey = PURCHASE_LOCK_PREFIX + datePart;
+        String lockValue = Thread.currentThread().getId() + "-" + System.nanoTime();
+
+        try {
+            long startWaitTime = System.currentTimeMillis();
+            while (System.currentTimeMillis() - startWaitTime < PURCHASE_LOCK_WAIT_TIMEOUT * 1000) {
+                Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, PURCHASE_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("鑾峰彇閲囪喘鍚堝悓缂栧彿鐢熸垚閿佸け璐ワ細瓒呮椂");
+            }
+
+            String prefix = "CG-" + datePart + "-";
+            List<PurchaseLedger> existingList = purchaseLedgerMapper.selectList(
+                    new LambdaQueryWrapper<PurchaseLedger>()
+                            .likeRight(PurchaseLedger::getPurchaseContractNumber, prefix)
+                            .select(PurchaseLedger::getPurchaseContractNumber));
+            List<Integer> existingSequences = existingList.stream()
+                    .map(PurchaseLedger::getPurchaseContractNumber)
+                    .filter(Objects::nonNull)
+                    .map(no -> {
+                        int lastDash = no.lastIndexOf('-');
+                        if (lastDash >= 0) {
+                            try {
+                                return Integer.parseInt(no.substring(lastDash + 1));
+                            } catch (NumberFormatException e) {
+                                return 0;
+                            }
+                        }
+                        return 0;
+                    })
+                    .collect(Collectors.toList());
+            int nextSequence = findFirstMissingSequence(existingSequences);
+
+            return prefix + String.format("%03d", nextSequence);
+        } finally {
+            String luaScript = "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end";
+            redisTemplate.execute(
+                    new org.springframework.data.redis.core.script.DefaultRedisScript<>(luaScript, Long.class),
+                    Collections.singletonList(lockKey),
+                    lockValue
+            );
+        }
+    }
+
+    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;
+    }
 }

--
Gitblit v1.9.3