自动化立体仓库 - WMS系统
chen.llin
2025-12-24 46b422214d5d422be5dfa0df57560cda678058c9
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
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.SnowflakeIdWorker;
import com.core.exception.CoolException;
import com.zy.asrs.entity.MonthlySettle;
import com.zy.asrs.entity.MonthlySettleDetail;
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();
        
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String startDateStr = dateFormat.format(startDate);
        String endDateStr = dateTimeFormat.format(endDate);
        int count = this.baseMapper.countUnfinishedOrdersInRange(startDateStr, endDateStr);
        return count > 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("月结时间范围内存在未完成的订单,无法进行月结");
        }
 
        // 统计物料出入库数量(合并入库和出库)
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String startDateStr = dateFormat.format(startDate);
        String endDateStr = dateTimeFormat.format(endDate);
        List<MaterialInOutStatDTO> materialStats = this.baseMapper.statisticsMaterialInOut(startDateStr, endDateStr);
        
        // 检查是否有出入库历史订单
        if (materialStats == null || materialStats.isEmpty()) {
            throw new CoolException("月结时间范围内没有出入库历史订单,无法进行月结");
        }
 
        // 获取上一个月结记录(用于计算期初库存)
        MonthlySettle previousSettle = getLatestSettle();
        Map<String, BigDecimal> previousEndingQtyMap = 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);
            }
        }
 
        // 生成月结编号
        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;
 
        // 创建月结明细
        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 stockQtyDecimal = getCurrentStockQty(matnr, batch);
            BigDecimal diffQty = calculateDiffQty(stockQtyDecimal, endingQty);
            
            // 4. 创建并保存明细记录
            MonthlySettleDetail detail = buildMonthlySettleDetail(
                monthlySettle.getId(), settleNo, matnr, batch, maktx, brand,
                beginningQty, inQty, outQty, endingQty, stockQtyDecimal, diffQty
            );
            detail.setIsDeleted(0); // 未删除
            monthlySettleDetailMapper.insert(detail);
 
            // 5. 累计统计
            totalInQty = totalInQty.add(inQty);
            totalOutQty = totalOutQty.add(outQty);
            materialCount++;
        }
 
        // 更新月结主记录
        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);
    }
 
    /**
     * 获取当前实际库存数量
     */
    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 stockQty, BigDecimal endingQty) {
        return stockQty.subtract(endingQty);
    }
 
    /**
     * 构建月结明细对象
     */
    private MonthlySettleDetail buildMonthlySettleDetail(
            Long settleId, String settleNo, String matnr, String batch, String maktx, String brand,
            BigDecimal beginningQty, BigDecimal inQty, BigDecimal outQty, BigDecimal endingQty,
            BigDecimal stockQty, 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.setStockQty(stockQty);
        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);
    }
}