自动化立体仓库 - WMS系统
chen.llin
2025-12-25 c7b54b961679677b84fbbd5f0555748064479382
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
package com.zy.asrs.service.impl;
 
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.core.common.Cools;
import com.core.common.SnowflakeIdWorker;
import com.core.exception.CoolException;
import com.zy.asrs.entity.MonthlySettle;
import com.zy.asrs.entity.MonthlySettleDetail;
import com.zy.asrs.entity.result.MaterialInOutRawDTO;
import com.zy.asrs.entity.result.MaterialInOutStatDTO;
import com.zy.asrs.entity.result.MonthlySettleResultVO;
import com.zy.asrs.entity.result.MonthlySettleStatisticsVO;
import com.zy.asrs.entity.result.PreviousSettleEndingQtyDTO;
import com.zy.asrs.mapper.MonthlySettleDetailMapper;
import com.zy.asrs.mapper.MonthlySettleMapper;
import com.zy.asrs.service.ManLocDetlService;
import com.zy.asrs.service.MonthlySettleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
@Slf4j
@Service("monthlySettleService")
public class MonthlySettleServiceImpl extends ServiceImpl<MonthlySettleMapper, MonthlySettle> implements MonthlySettleService {
 
    @Autowired
    private MonthlySettleDetailMapper monthlySettleDetailMapper;
    @Autowired
    private ManLocDetlService manLocDetlService;
    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;
 
    @Override
    public MonthlySettle getLatestSettle() {
        return this.baseMapper.selectLatestSettle();
    }
 
    @Override
    public Date getNextStartDate() {
        MonthlySettle latestSettle = getLatestSettle();
        if (latestSettle == null) {
            // 如果没有月结记录,返回null,由前端选择起始日期
            return null;
        }
        // 返回最晚月结记录结束日期的下一天(起始日期应该是结束日期+1天的00:00:00)
        Calendar cal = Calendar.getInstance();
        cal.setTime(latestSettle.getEndDate());
        // 先设置为当天的00:00:00,然后加1天,确保返回的是下一天的00:00:00
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        cal.add(Calendar.DAY_OF_MONTH, 1);
        return cal.getTime();
    }
 
    @Override
    public Date getLatestEndDate() {
        MonthlySettle latestSettle = getLatestSettle();
        if (latestSettle == null) {
            return null;
        }
        // 返回最晚月结记录的结束日期(格式化为当天的00:00:00,用于显示)
        Calendar cal = Calendar.getInstance();
        cal.setTime(latestSettle.getEndDate());
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        return cal.getTime();
    }
 
    @Override
    public boolean hasUnfinishedOrders(Date startDate, Date endDate) {
        // 结束日期+23:59:59
        Calendar cal = Calendar.getInstance();
        cal.setTime(endDate);
        cal.set(Calendar.HOUR_OF_DAY, 23);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.SECOND, 59);
        cal.set(Calendar.MILLISECOND, 999);
        endDate = cal.getTime();
        
        // 注意:order_time 是 varchar 类型,需要格式化为完整的日期时间字符串进行比较
        SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 起始日期设置为当天的 00:00:00
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(startDate);
        startCal.set(Calendar.HOUR_OF_DAY, 0);
        startCal.set(Calendar.MINUTE, 0);
        startCal.set(Calendar.SECOND, 0);
        startCal.set(Calendar.MILLISECOND, 0);
        String startDateStr = dateTimeFormat.format(startCal.getTime());
        String endDateStr = dateTimeFormat.format(endDate);
        // 分两次查询:入库和出库
        int pakinCount = this.baseMapper.countUnfinishedOrdersInRangePakin(startDateStr, endDateStr);
        int pakoutCount = this.baseMapper.countUnfinishedOrdersInRangePakout(startDateStr, endDateStr);
        return (pakinCount + pakoutCount) > 0;
    }
 
    @Override
    @Transactional
    public MonthlySettleResultVO startSettle(Date startDate, Date endDate, Long userId) {
        // 结束日期+23:59:59
        Calendar cal = Calendar.getInstance();
        cal.setTime(endDate);
        cal.set(Calendar.HOUR_OF_DAY, 23);
        cal.set(Calendar.MINUTE, 59);
        cal.set(Calendar.SECOND, 59);
        cal.set(Calendar.MILLISECOND, 999);
        endDate = cal.getTime();
        
        // 检查起始日期必须大于最晚月结记录的结束日期
        MonthlySettle latestSettle = getLatestSettle();
        if (latestSettle != null) {
            Calendar startCal = Calendar.getInstance();
            startCal.setTime(startDate);
            startCal.set(Calendar.HOUR_OF_DAY, 0);
            startCal.set(Calendar.MINUTE, 0);
            startCal.set(Calendar.SECOND, 0);
            startCal.set(Calendar.MILLISECOND, 0);
            
            Calendar latestEndCal = Calendar.getInstance();
            latestEndCal.setTime(latestSettle.getEndDate());
            latestEndCal.set(Calendar.HOUR_OF_DAY, 0);
            latestEndCal.set(Calendar.MINUTE, 0);
            latestEndCal.set(Calendar.SECOND, 0);
            latestEndCal.set(Calendar.MILLISECOND, 0);
            
            // 起始日期必须大于最晚结束日期,不能等于或小于
            if (!startCal.getTime().after(latestEndCal.getTime())) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                throw new CoolException("起始日期必须大于最晚月结记录的结束日期:" + sdf.format(latestSettle.getEndDate()));
            }
        }
        
        // 检查是否有未完成的订单
        if (hasUnfinishedOrders(startDate, endDate)) {
            throw new CoolException("月结时间范围内存在未完成的订单,无法进行月结");
        }
 
        // 统计物料出入库数量(分别查询两个表,在Java代码中合并)
        // 注意:order_time 是 varchar 类型,需要格式化为完整的日期时间字符串进行比较
        SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 起始日期设置为当天的 00:00:00
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(startDate);
        startCal.set(Calendar.HOUR_OF_DAY, 0);
        startCal.set(Calendar.MINUTE, 0);
        startCal.set(Calendar.SECOND, 0);
        startCal.set(Calendar.MILLISECOND, 0);
        String startDateStr = dateTimeFormat.format(startCal.getTime());
        String endDateStr = dateTimeFormat.format(endDate);
        
        // 分别查询入库表和出库表
        List<MaterialInOutRawDTO> pakinDataList = this.baseMapper.statisticsMaterialInOutFromPakin(startDateStr, endDateStr);
        List<MaterialInOutRawDTO> pakoutDataList = this.baseMapper.statisticsMaterialInOutFromPakout(startDateStr, endDateStr);
        
        // 合并两个表的数据
        List<MaterialInOutRawDTO> rawDataList = new java.util.ArrayList<>();
        if (pakinDataList != null && !pakinDataList.isEmpty()) {
            rawDataList.addAll(pakinDataList);
        }
        if (pakoutDataList != null && !pakoutDataList.isEmpty()) {
            rawDataList.addAll(pakoutDataList);
        }
        
        // 检查是否有出入库历史订单
        if (rawDataList == null || rawDataList.isEmpty()) {
            throw new CoolException("月结时间范围内没有出入库历史订单,无法进行月结");
        }
        
        // 在Java代码中处理分类统计逻辑
        // 1. 处理NULL值(batch、brand、maktx)
        // 2. 根据物料编码、批次、品牌分组
        // 3. 根据 pakin_pakout_status 区分入库和出库
        Map<String, MaterialInOutStatDTO> materialStatsMap = new HashMap<>();
        Map<String, String> maktxMap = new HashMap<>(); // 存储物料名称(取第一个非空值)
        
        for (MaterialInOutRawDTO raw : rawDataList) {
            // 处理NULL值
            String matnr = raw.getMatnr();
            String batch = raw.getBatch() != null ? raw.getBatch() : "";
            String brand = raw.getBrand() != null ? raw.getBrand() : "";
            String maktx = raw.getMaktx() != null ? raw.getMaktx() : "";
            String key = matnr + "_" + batch + "_" + brand;
            
            // 保存物料名称(取第一个非空值)
            if (maktx != null && !maktx.isEmpty() && !maktxMap.containsKey(key)) {
                maktxMap.put(key, maktx);
            }
            
            MaterialInOutStatDTO stat = materialStatsMap.get(key);
            if (stat == null) {
                stat = new MaterialInOutStatDTO();
                stat.setMatnr(matnr);
                stat.setMaktx(maktxMap.getOrDefault(key, ""));
                stat.setBatch(batch);
                stat.setBrand(brand);
                stat.setInQty(BigDecimal.ZERO);
                stat.setOutQty(BigDecimal.ZERO);
                materialStatsMap.put(key, stat);
            }
            
            // 根据 pakin_pakout_status 分类统计
            // 1 = 入库,2 = 出库,0 = 未知(已在SQL中过滤)
            BigDecimal qty = raw.getQty() != null ? raw.getQty() : BigDecimal.ZERO;
            Integer status = raw.getPakinPakoutStatus();
            if (status != null && qty.compareTo(BigDecimal.ZERO) > 0) {
                if (status == 1) {
                    // 入库
                    stat.setInQty(stat.getInQty().add(qty));
                } else if (status == 2) {
                    // 出库
                    stat.setOutQty(stat.getOutQty().add(qty));
                }
            }
        }
        
        // 转换为List
        List<MaterialInOutStatDTO> materialStats = new java.util.ArrayList<>(materialStatsMap.values());
 
        // 获取上一个月结记录(用于计算期初库存和承接上一期的物料)
        MonthlySettle   previousSettle = getLatestSettle();
        Map<String, BigDecimal> previousEndingQtyMap = new HashMap<>();
        Map<String, PreviousSettleEndingQtyDTO> previousDetailMap = new HashMap<>(); // 存储上一期的完整明细信息
        if (previousSettle != null) {
            List<PreviousSettleEndingQtyDTO> previousDetails = this.baseMapper.getPreviousSettleEndingQty(previousSettle.getId());
            for (PreviousSettleEndingQtyDTO detail : previousDetails) {
                String key = detail.getMatnr() + "_" + 
                            (detail.getBatch() != null ? detail.getBatch() : "") + "_" + 
                            (detail.getBrand() != null ? detail.getBrand() : "");
                BigDecimal endingQty = detail.getEndingQty() != null ? detail.getEndingQty() : BigDecimal.ZERO;
                previousEndingQtyMap.put(key, endingQty);
                previousDetailMap.put(key, detail); // 保存完整信息,用于后续创建明细
            }
        }
 
        // 生成月结编号
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String settleNo = "MS" + sdf.format(new Date()) + String.format("%04d", snowflakeIdWorker.nextId() % 10000);
 
        // 创建月结主记录
        MonthlySettle monthlySettle = new MonthlySettle();
        monthlySettle.setSettleNo(settleNo);
        monthlySettle.setStartDate(startDate);
        monthlySettle.setEndDate(endDate);
        monthlySettle.setStatus(1); // 已月结
        monthlySettle.setIsDeleted(0); // 未删除
        monthlySettle.setCreateBy(userId);
        monthlySettle.setCreateTime(new Date());
        this.insert(monthlySettle);
 
        BigDecimal totalInQty = BigDecimal.ZERO;
        BigDecimal totalOutQty = BigDecimal.ZERO;
        int materialCount = 0;
 
        // 创建 Map 存储本期有出入库的物料(用于判断是否需要从上一期继承)
        Map<String, MaterialInOutStatDTO> currentMaterialMap = new HashMap<>();
        for (MaterialInOutStatDTO stat : materialStats) {
            String matnr = stat.getMatnr();
            String batch = stat.getBatch() != null ? stat.getBatch() : "";
            String brand = stat.getBrand() != null ? stat.getBrand() : "";
            String key = matnr + "_" + batch + "_" + brand;
            currentMaterialMap.put(key, stat);
        }
 
        // 收集所有明细记录,用于批量插入
        List<MonthlySettleDetail> detailList = new java.util.ArrayList<>();
 
        // 1. 处理本期有出入库的物料
        for (MaterialInOutStatDTO stat : materialStats) {
            // 1. 提取基础信息
            String matnr = stat.getMatnr();
            String batch = stat.getBatch() != null ? stat.getBatch() : "";
            String brand = stat.getBrand() != null ? stat.getBrand() : "";
            String maktx = stat.getMaktx();
            
            // 2. 提取数量信息
            BigDecimal inQty = stat.getInQty() != null ? stat.getInQty() : BigDecimal.ZERO;
            BigDecimal outQty = stat.getOutQty() != null ? stat.getOutQty() : BigDecimal.ZERO;
            
            // 3. 计算库存相关数量
            BigDecimal beginningQty = getBeginningQty(matnr, batch, brand, previousEndingQtyMap);
            BigDecimal endingQty = calculateEndingQty(beginningQty, inQty, outQty);
            // 差异数量 = 期末库存 - 期初库存(期末大于期初时为正数)
            BigDecimal diffQty = calculateDiffQty(beginningQty, endingQty);
            
            // 4. 创建明细记录(暂不插入)
            MonthlySettleDetail detail = buildMonthlySettleDetail(
                monthlySettle.getId(), settleNo, matnr, batch, maktx, brand,
                beginningQty, inQty, outQty, endingQty, diffQty
            );
            detail.setIsDeleted(0); // 未删除
            detailList.add(detail);
 
            // 5. 累计统计
            totalInQty = totalInQty.add(inQty);
            totalOutQty = totalOutQty.add(outQty);
            materialCount++;
        }
 
        // 2. 处理上一期存在但本期没有出入库的物料(需要承接显示)
        if (previousSettle != null && !previousDetailMap.isEmpty()) {
            for (Map.Entry<String, PreviousSettleEndingQtyDTO> entry : previousDetailMap.entrySet()) {
                String key = entry.getKey();
                // 如果本期没有出入库,但上一期有期末库存(且不为0),则需要创建一条记录
                if (!currentMaterialMap.containsKey(key)) {
                    PreviousSettleEndingQtyDTO previousDetail = entry.getValue();
                    BigDecimal previousEndingQty = previousDetail.getEndingQty() != null ? previousDetail.getEndingQty() : BigDecimal.ZERO;
                    
                    // 如果上一期的期末库存不为0,或者即使为0也要显示(用于对比)
                    // 这里我们总是创建记录,即使上一期期末库存为0,因为用户要求"如果上一期存在过,本期库存归零也要显示一条为0的记录"
                    String matnr = previousDetail.getMatnr();
                    String batch = previousDetail.getBatch() != null ? previousDetail.getBatch() : "";
                    String brand = previousDetail.getBrand() != null ? previousDetail.getBrand() : "";
                    String maktx = previousDetail.getMaktx() != null ? previousDetail.getMaktx() : "";
                    
                    // 本期没有出入库,所以入库和出库数量都为0
                    BigDecimal inQty = BigDecimal.ZERO;
                    BigDecimal outQty = BigDecimal.ZERO;
                    
                    // 期初库存 = 上一期的期末库存
                    BigDecimal beginningQty = previousEndingQty;
                    // 期末库存 = 期初 + 入库 - 出库 = 期初(因为本期没有出入库)
                    BigDecimal endingQty = beginningQty;
                    // 差异数量 = 期初库存 - 期末库存(期初大于期末时为正数)
                    BigDecimal diffQty = calculateDiffQty(beginningQty, endingQty);
                    
                    // 创建明细记录(暂不插入)
                    MonthlySettleDetail detail = buildMonthlySettleDetail(
                        monthlySettle.getId(), settleNo, matnr, batch, maktx, brand,
                        beginningQty, inQty, outQty, endingQty, diffQty
                    );
                    detail.setIsDeleted(0); // 未删除
                    detailList.add(detail);
                    
                    // 累计统计(虽然入库和出库为0,但也要计入物料种类数)
                    materialCount++;
                }
            }
        }
 
        // 分批插入,每批1000条,避免一次性插入过多数据
        if (!detailList.isEmpty()) {
            int batchSize = 1000;
            for (int i = 0; i < detailList.size(); i += batchSize) {
                int end = Math.min(i + batchSize, detailList.size());
                List<MonthlySettleDetail> batch = detailList.subList(i, end);
                for (MonthlySettleDetail detail : batch) {
                    if (monthlySettleDetailMapper.insert(detail) <= 0) {
                        throw new CoolException("插入月结明细失败");
                    }
                }
            }
        }
 
        // 更新月结主记录
        monthlySettle.setTotalInQty(totalInQty);
        monthlySettle.setTotalOutQty(totalOutQty);
        monthlySettle.setTotalMaterials(materialCount);
        monthlySettle.setStatus(1); // 已月结
        monthlySettle.setUpdateBy(userId);
        monthlySettle.setUpdateTime(new Date());
        this.updateById(monthlySettle);
 
        // 更新订单的月结信息(入库和出库都要更新)
        this.baseMapper.updateOrderSettleInfo(monthlySettle.getId(), settleNo, startDateStr, endDateStr);
        this.baseMapper.updateOrderSettleInfoPakout(monthlySettle.getId(), settleNo, startDateStr, endDateStr);
 
        MonthlySettleResultVO result = new MonthlySettleResultVO();
        result.setSettleId(monthlySettle.getId());
        result.setSettleNo(settleNo);
        result.setTotalInQty(totalInQty);
        result.setTotalOutQty(totalOutQty);
        result.setTotalMaterials(materialCount);
        return result;
    }
 
    @Override
    public MonthlySettleStatisticsVO getSettleStatistics(Long settleId) {
        MonthlySettle settle = this.selectById(settleId);
        if (settle == null || (settle.getIsDeleted() != null && settle.getIsDeleted() == 1)) {
            throw new CoolException("月结记录不存在");
        }
 
        // 关联物料表查询明细
        List<MonthlySettleDetail> details = monthlySettleDetailMapper.selectDetailWithMat(settleId);
 
        MonthlySettleStatisticsVO result = new MonthlySettleStatisticsVO();
        result.setSettle(settle);
        result.setDetails(details);
        return result;
    }
 
    @Override
    public Page<MonthlySettle> getPage(Page<MonthlySettle> page) {
        Map<String, Object> condition = page.getCondition();
        EntityWrapper<MonthlySettle> wrapper = new EntityWrapper<>();
        wrapper.eq("is_deleted", 0);
        
        if (condition != null) {
            if (condition.get("settleNo") != null) {
                wrapper.like("settle_no", condition.get("settleNo").toString());
            }
            if (condition.get("status") != null) {
                wrapper.eq("status", condition.get("status"));
            }
            if (condition.get("startDate") != null && condition.get("endDate") != null) {
                wrapper.ge("start_date", condition.get("startDate"));
                wrapper.le("end_date", condition.get("endDate"));
            }
        }
        
        wrapper.orderBy("create_time", false);
        List<MonthlySettle> list = this.selectList(wrapper);
        
        EntityWrapper<MonthlySettle> countWrapper = new EntityWrapper<>();
        countWrapper.eq("is_deleted", 0);
        if (condition != null) {
            if (condition.get("settleNo") != null) {
                countWrapper.like("settle_no", condition.get("settleNo").toString());
            }
            if (condition.get("status") != null) {
                countWrapper.eq("status", condition.get("status"));
            }
            if (condition.get("startDate") != null && condition.get("endDate") != null) {
                countWrapper.ge("start_date", condition.get("startDate"));
                countWrapper.le("end_date", condition.get("endDate"));
            }
        }
        page.setRecords(list);
        page.setTotal(this.selectCount(countWrapper));
        return page;
    }
 
    /**
     * 获取期初库存(上期结余)
     */
    private BigDecimal getBeginningQty(String matnr, String batch, String brand, Map<String, BigDecimal> previousEndingQtyMap) {
        String key = matnr + "_" + batch + "_" + brand;
        return previousEndingQtyMap.getOrDefault(key, BigDecimal.ZERO);
    }
 
    /**
     * 计算期末库存(期初+入库-出库)
     */
    private BigDecimal calculateEndingQty(BigDecimal beginningQty, BigDecimal inQty, BigDecimal outQty) {
        return beginningQty.add(inQty).subtract(outQty);
    }
 
    /**
     * 获取当前实际库存数量(已废弃,改用批量查询)
     * @deprecated 使用批量查询方法替代,避免N+1查询问题
     */
    @Deprecated
    private BigDecimal getCurrentStockQty(String matnr, String batch) {
        Double stockQty = manLocDetlService.queryStockAnfme(matnr, batch);
        return stockQty != null ? BigDecimal.valueOf(stockQty) : BigDecimal.ZERO;
    }
 
    /**
     * 计算差异数量(期末库存-期初库存)
     * 期末大于期初时为正数,表示库存增加
     */
    private BigDecimal calculateDiffQty(BigDecimal beginningQty, BigDecimal endingQty) {
        return endingQty.subtract(beginningQty);
    }
 
    /**
     * 构建月结明细对象
     */
    private MonthlySettleDetail buildMonthlySettleDetail(
            Long settleId, String settleNo, String matnr, String batch, String maktx, String brand,
            BigDecimal beginningQty, BigDecimal inQty, BigDecimal outQty, BigDecimal endingQty,
            BigDecimal diffQty) {
        MonthlySettleDetail detail = new MonthlySettleDetail();
        // 基本信息
        detail.setSettleId(settleId);
        detail.setSettleNo(settleNo);
        detail.setMatnr(matnr);
        detail.setBatch(batch);
        detail.setMaktx(maktx);
        detail.setBrand(brand);
        // 数量信息
        detail.setBeginningQty(beginningQty);
        detail.setInQty(inQty);
        detail.setOutQty(outQty);
        detail.setEndingQty(endingQty);
        detail.setDiffQty(diffQty);
        // 时间信息
        detail.setCreateTime(new Date());
        return detail;
    }
 
    @Override
    @Transactional
    public void deleteSettle(Long settleId) {
        MonthlySettle settle = this.selectById(settleId);
        if (settle == null || (settle.getIsDeleted() != null && settle.getIsDeleted() == 1)) {
            throw new CoolException("月结记录不存在");
        }
 
        // 清除出入库订单的月结信息
        this.baseMapper.clearOrderSettleInfo(settleId);
        this.baseMapper.clearOrderSettleInfoPakout(settleId);
 
        // 逻辑删除月结明细
        EntityWrapper<MonthlySettleDetail> detailWrapper = new EntityWrapper<>();
        detailWrapper.eq("settle_id", settleId);
        detailWrapper.eq("is_deleted", 0);
        List<MonthlySettleDetail> details = monthlySettleDetailMapper.selectList(detailWrapper);
        if (details != null && !details.isEmpty()) {
            for (MonthlySettleDetail detail : details) {
                detail.setIsDeleted(1);
                monthlySettleDetailMapper.updateById(detail);
            }
        }
 
        // 逻辑删除月结主记录
        settle.setIsDeleted(1);
        settle.setUpdateTime(new Date());
        this.updateById(settle);
    }
 
    @Override
    public boolean isOrderTimeInSettledRange(String orderTime) {
        if (Cools.isEmpty(orderTime)) {
            return false;
        }
        try {
            // 解析订单业务时间
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date orderDate = sdf.parse(orderTime);
            
            // 查询所有已月结的记录(status=1且未删除)
            EntityWrapper<MonthlySettle> wrapper = new EntityWrapper<>();
            wrapper.eq("status", 1);
            wrapper.eq("is_deleted", 0);
            List<MonthlySettle> settledList = this.selectList(wrapper);
            
            if (settledList == null || settledList.isEmpty()) {
                return false;
            }
            
            // 检查订单时间是否在任何已月结的区间内
            for (MonthlySettle settle : settledList) {
                Date startDate = settle.getStartDate();
                Date endDate = settle.getEndDate();
                
                // 确保startDate是当天的00:00:00
                Calendar startCal = Calendar.getInstance();
                startCal.setTime(startDate);
                startCal.set(Calendar.HOUR_OF_DAY, 0);
                startCal.set(Calendar.MINUTE, 0);
                startCal.set(Calendar.SECOND, 0);
                startCal.set(Calendar.MILLISECOND, 0);
                
                // 确保endDate是当天的23:59:59.999
                Calendar endCal = Calendar.getInstance();
                endCal.setTime(endDate);
                endCal.set(Calendar.HOUR_OF_DAY, 23);
                endCal.set(Calendar.MINUTE, 59);
                endCal.set(Calendar.SECOND, 59);
                endCal.set(Calendar.MILLISECOND, 999);
                
                // 判断订单时间是否在区间内:[startDate的00:00:00, endDate的23:59:59.999]
                if (!orderDate.before(startCal.getTime()) && !orderDate.after(endCal.getTime())) {
                    return true;
                }
            }
            
            return false;
        } catch (Exception e) {
            log.error("检查订单时间是否在已月结区间内失败", e);
            return false;
        }
    }
}