tuqin
16 小时以前 55011a9cd84676913b62b3c130257939c1df2488
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
package com.ruoyi.production.service.impl;
 
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.production.bean.dto.ProductionCostingQueryDto;
import com.ruoyi.production.bean.vo.ProductionCostingDetailVo;
import com.ruoyi.production.bean.vo.ProductionCostingRawVo;
import com.ruoyi.production.bean.vo.ProductionCostingSummaryVo;
import com.ruoyi.production.mapper.ProductionCostingDeptMapper;
import com.ruoyi.production.mapper.ProductionCostingFactorMapper;
import com.ruoyi.production.mapper.ProductionCostingMapper;
import com.ruoyi.production.mapper.ProductionCostingStandardMapper;
import com.ruoyi.production.mapper.ProductionCostingWorkdayMapper;
import com.ruoyi.production.pojo.ProductionCostingDept;
import com.ruoyi.production.pojo.ProductionCostingFactor;
import com.ruoyi.production.pojo.ProductionCostingStandard;
import com.ruoyi.production.pojo.ProductionCostingWorkday;
import com.ruoyi.production.service.ProductionCostingService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.DayOfWeek;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
 
/**
 * 生产核算服务实现
 * <p>
 * 计算规则(严格按需求):
 * 白盒部门:折算时长 = 月产量 × 标准数量 × 8
 * 成品部门:月产折合小提 = 小提琴 + 大提琴×大提折算率 + 贝斯×贝斯折算率
 * 折算时长 = 月产折合小提 ÷ 标准数量 × 8
 * 月时间合计 = 折算时长求和
 * 折合倍数 = 月时间合计 ÷(当月工作日 × 8)
 */
@Service
@RequiredArgsConstructor
public class ProductionCostingServiceImpl implements ProductionCostingService {
 
    private static final BigDecimal EIGHT = new BigDecimal("8");
    private static final BigDecimal ONE = new BigDecimal("1");
    private static final String VIOLIN = "小提琴";
    private static final String CELLO = "大提琴";
    private static final String BASS = "贝斯";
 
    private final ProductionCostingMapper productionCostingMapper;
    private final ProductionCostingStandardMapper standardMapper;
    private final ProductionCostingFactorMapper factorMapper;
    private final ProductionCostingDeptMapper deptMapper;
    private final ProductionCostingWorkdayMapper workdayMapper;
 
    @Override
    public List<ProductionCostingDetailVo> detailList(ProductionCostingQueryDto dto) {
        ProductionCostingQueryDto query = dto == null ? new ProductionCostingQueryDto() : dto;
        List<ProductionCostingRawVo> rows = productionCostingMapper.selectCostingRows(query);
 
        // 标准数量:成品(1) 按工序;白盒(2) 按 工序+型号
        Map<Long, BigDecimal> standardByProcess = new HashMap<>();
        Map<String, BigDecimal> standardByProcessModel = new HashMap<>();
        List<ProductionCostingStandard> standardList = standardMapper.selectList(null);
        for (ProductionCostingStandard s : standardList) {
            if (s.getProcessId() == null || s.getStandardQuantity() == null) {
                continue;
            }
            if (Integer.valueOf(2).equals(s.getDeptType())) {
                standardByProcessModel.put(key(s.getProcessId(), s.getProductModelId()), s.getStandardQuantity());
            } else {
                standardByProcess.put(s.getProcessId(), s.getStandardQuantity());
            }
        }
 
        // 折算率:工序 + 员工
        Map<String, ProductionCostingFactor> factorMap = new HashMap<>();
        for (ProductionCostingFactor f : factorMapper.selectList(null)) {
            factorMap.put(key(f.getProcessId(), f.getStaffId()), f);
        }
 
        List<ProductionCostingDetailVo> result = new ArrayList<>();
 
        // ---------- 成品部门:按 员工 + 月份 + 工序 聚合 ----------
        Map<String, ProductionCostingDetailVo> finishedGroup = new LinkedHashMap<>();
        // ---------- 白盒部门:按 员工 + 月份 + 工序 + 型号 聚合 ----------
        Map<String, ProductionCostingDetailVo> whiteGroup = new LinkedHashMap<>();
 
        for (ProductionCostingRawVo row : rows) {
            BigDecimal qty = nz(row.getQuantity());
            boolean white = Integer.valueOf(2).equals(row.getDeptType());
            if (white) {
                String k = key(row.getStaffId(), row.getYearMonth(), row.getProcessId(), row.getProductModelId());
                ProductionCostingDetailVo vo = whiteGroup.get(k);
                if (vo == null) {
                    vo = new ProductionCostingDetailVo();
                    vo.setStaffId(row.getStaffId());
                    vo.setStaffName(row.getStaffName());
                    vo.setYearMonth(row.getYearMonth());
                    vo.setProcessId(row.getProcessId());
                    vo.setProcessName(row.getProcessName());
                    vo.setDeptType(row.getDeptType());
                    vo.setProductModelId(row.getProductModelId());
                    vo.setProductName(row.getProductName());
                    vo.setModelName(row.getModelName());
                    vo.setQuantity(BigDecimal.ZERO);
                    whiteGroup.put(k, vo);
                }
                vo.setQuantity(vo.getQuantity().add(qty));
            } else {
                String k = key(row.getStaffId(), row.getYearMonth(), row.getProcessId());
                ProductionCostingDetailVo vo = finishedGroup.get(k);
                if (vo == null) {
                    vo = new ProductionCostingDetailVo();
                    vo.setStaffId(row.getStaffId());
                    vo.setStaffName(row.getStaffName());
                    vo.setYearMonth(row.getYearMonth());
                    vo.setProcessId(row.getProcessId());
                    vo.setProcessName(row.getProcessName());
                    vo.setDeptType(row.getDeptType());
                    vo.setQuantity(BigDecimal.ZERO);
                    vo.setViolinQty(BigDecimal.ZERO);
                    vo.setCelloQty(BigDecimal.ZERO);
                    vo.setBassQty(BigDecimal.ZERO);
                    finishedGroup.put(k, vo);
                }
                vo.setQuantity(vo.getQuantity().add(qty));
                if (VIOLIN.equals(row.getProductName())) {
                    vo.setViolinQty(vo.getViolinQty().add(qty));
                } else if (CELLO.equals(row.getProductName())) {
                    vo.setCelloQty(vo.getCelloQty().add(qty));
                } else if (BASS.equals(row.getProductName())) {
                    vo.setBassQty(vo.getBassQty().add(qty));
                } else {
                    // 非乐器产品默认按小提琴当量 1:1 计入
                    vo.setViolinQty(vo.getViolinQty().add(qty));
                }
            }
        }
 
        // 成品:折算
        for (ProductionCostingDetailVo vo : finishedGroup.values()) {
            ProductionCostingFactor factor = factorMap.get(key(vo.getProcessId(), vo.getStaffId()));
            BigDecimal violinFactor = factor == null ? ONE : nzDefault(factor.getViolinFactor(), ONE);
            BigDecimal celloFactor = factor == null ? ONE : nzDefault(factor.getViolaFactor(), ONE);
            BigDecimal bassFactor = factor == null ? ONE : nzDefault(factor.getBassFactor(), ONE);
            vo.setViolinFactor(violinFactor);
            vo.setCelloFactor(celloFactor);
            vo.setBassFactor(bassFactor);
 
            BigDecimal folded = nz(vo.getViolinQty()).multiply(violinFactor)
                    .add(nz(vo.getCelloQty()).multiply(celloFactor))
                    .add(nz(vo.getBassQty()).multiply(bassFactor))
                    .setScale(2, RoundingMode.HALF_UP);
            vo.setFoldedQuantity(folded);
 
            BigDecimal standard = standardByProcess.get(vo.getProcessId());
            vo.setStandardQuantity(standard);
            vo.setConvertedHours(calcFinishedHours(folded, standard));
        }
 
        // 白盒:折算时长 = 月产量 × 标准数量 × 8
        for (ProductionCostingDetailVo vo : whiteGroup.values()) {
            BigDecimal standard = standardByProcessModel.get(key(vo.getProcessId(), vo.getProductModelId()));
            vo.setStandardQuantity(standard);
            BigDecimal hours = nz(standard).compareTo(BigDecimal.ZERO) <= 0
                    ? BigDecimal.ZERO
                    : nz(vo.getQuantity()).multiply(standard).multiply(EIGHT);
            vo.setConvertedHours(hours.setScale(2, RoundingMode.HALF_UP));
        }
 
        result.addAll(finishedGroup.values());
        result.addAll(whiteGroup.values());
        return result;
    }
 
    @Override
    public List<ProductionCostingSummaryVo> summary(ProductionCostingQueryDto dto) {
        ProductionCostingQueryDto query = dto == null ? new ProductionCostingQueryDto() : dto;
        String yearMonth = (query.getYearMonth() == null || query.getYearMonth().isEmpty())
                ? YearMonth.now().toString() : query.getYearMonth();
        query.setYearMonth(yearMonth);
        int workDays = resolveWorkDays(yearMonth);
        BigDecimal fullHours = new BigDecimal(workDays).multiply(EIGHT);
 
        List<ProductionCostingDetailVo> details = detailList(query);
        Map<Long, ProductionCostingSummaryVo> map = new LinkedHashMap<>();
        for (ProductionCostingDetailVo d : details) {
            ProductionCostingSummaryVo vo = map.get(d.getStaffId());
            if (vo == null) {
                vo = new ProductionCostingSummaryVo();
                vo.setStaffId(d.getStaffId());
                vo.setStaffName(d.getStaffName());
                vo.setYearMonth(yearMonth);
                vo.setOutput(BigDecimal.ZERO);
                vo.setMonthTimeTotal(BigDecimal.ZERO);
                vo.setWorkDays(workDays);
                vo.setMonthHours(fullHours);
                map.put(d.getStaffId(), vo);
            }
            vo.setOutput(vo.getOutput().add(nz(d.getQuantity())).setScale(2, RoundingMode.HALF_UP));
            vo.setMonthTimeTotal(vo.getMonthTimeTotal().add(nz(d.getConvertedHours())).setScale(2, RoundingMode.HALF_UP));
        }
        List<ProductionCostingSummaryVo> result = new ArrayList<>(map.values());
        for (ProductionCostingSummaryVo vo : result) {
            BigDecimal divisor = fullHours;
            BigDecimal ratio = divisor.compareTo(BigDecimal.ZERO) == 0
                    ? BigDecimal.ZERO
                    : vo.getMonthTimeTotal().divide(divisor, 2, RoundingMode.HALF_UP);
            vo.setConversionRatio(ratio);
        }
        return result;
    }
 
    @Override
    public Map<String, Object> workdayInfo(String yearMonth) {
        String ym = (yearMonth == null || yearMonth.isEmpty()) ? YearMonth.now().toString() : yearMonth;
        ProductionCostingWorkday cfg = findWorkday(ym);
        int workDays = (cfg != null && cfg.getWorkDays() != null) ? cfg.getWorkDays() : workDays(ym);
        Map<String, Object> map = new HashMap<>();
        map.put("yearMonth", ym);
        map.put("workDays", workDays);
        map.put("dayHours", 8);
        map.put("monthHours", new BigDecimal(workDays).multiply(EIGHT).setScale(2, RoundingMode.HALF_UP));
        map.put("source", (cfg != null && cfg.getWorkDays() != null) ? "CONFIG" : "DEFAULT");
        return map;
    }
 
    /* ================= 配置 CRUD ================= */
 
    @Override
    public List<ProductionCostingDept> listDept() {
        return deptMapper.selectDeptConfigs();
    }
 
    @Override
    public boolean saveDept(ProductionCostingDept dept) {
        return deptMapper.insert(dept) > 0;
    }
 
    @Override
    public boolean updateDept(ProductionCostingDept dept) {
        return deptMapper.updateById(dept) > 0;
    }
 
    @Override
    public boolean removeDept(Long id) {
        return deptMapper.deleteById(id) > 0;
    }
 
    @Override
    public List<ProductionCostingStandard> listStandard(ProductionCostingStandard query) {
        return standardMapper.selectStandardList(query == null ? new ProductionCostingStandard() : query);
    }
 
    @Override
    public boolean saveStandard(ProductionCostingStandard standard) {
        return standardMapper.insert(standard) > 0;
    }
 
    @Override
    public boolean updateStandard(ProductionCostingStandard standard) {
        return standardMapper.updateById(standard) > 0;
    }
 
    @Override
    public boolean removeStandard(Long id) {
        return standardMapper.deleteById(id) > 0;
    }
 
    @Override
    public List<ProductionCostingFactor> listFactor(ProductionCostingFactor query) {
        return factorMapper.selectFactorList(query == null ? new ProductionCostingFactor() : query);
    }
 
    @Override
    public boolean saveFactor(ProductionCostingFactor factor) {
        return factorMapper.insert(factor) > 0;
    }
 
    @Override
    public boolean updateFactor(ProductionCostingFactor factor) {
        return factorMapper.updateById(factor) > 0;
    }
 
    @Override
    public boolean removeFactor(Long id) {
        return factorMapper.deleteById(id) > 0;
    }
 
    @Override
    public List<ProductionCostingWorkday> listWorkday(ProductionCostingWorkday query) {
        return workdayMapper.selectWorkdayList(query == null ? new ProductionCostingWorkday() : query);
    }
 
    @Override
    public boolean saveWorkday(ProductionCostingWorkday workday) {
        return workdayMapper.insert(workday) > 0;
    }
 
    @Override
    public boolean updateWorkday(ProductionCostingWorkday workday) {
        return workdayMapper.updateById(workday) > 0;
    }
 
    @Override
    public boolean removeWorkday(Long id) {
        return workdayMapper.deleteById(id) > 0;
    }
 
    /* ================= 工具方法 ================= */
 
    /** 查月度工作日配置 */
    private ProductionCostingWorkday findWorkday(String yearMonth) {
        if (yearMonth == null || yearMonth.isEmpty()) {
            return null;
        }
        return workdayMapper.selectOne(new LambdaQueryWrapper<ProductionCostingWorkday>()
                .eq(ProductionCostingWorkday::getYearMonth, yearMonth)
                .last("limit 1"));
    }
 
    /** 工作日:优先取月度配置,未配置则按周一~周五 */
    private int resolveWorkDays(String yearMonth) {
        ProductionCostingWorkday cfg = findWorkday(yearMonth);
        if (cfg != null && cfg.getWorkDays() != null) {
            return cfg.getWorkDays();
        }
        return workDays(yearMonth);
    }
 
    /** 成品折算:折合小提琴 / 标准数量 * 8;标准数量<=0 返回 0 */
    private BigDecimal calcFinishedHours(BigDecimal folded, BigDecimal standard) {
        if (standard == null || standard.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        return folded.divide(standard, 8, RoundingMode.HALF_UP)
                .multiply(EIGHT)
                .setScale(2, RoundingMode.HALF_UP);
    }
 
    private int workDays(String yearMonth) {
        try {
            YearMonth ym = YearMonth.parse(yearMonth);
            int count = 0;
            for (int d = 1; d <= ym.lengthOfMonth(); d++) {
                DayOfWeek dow = ym.atDay(d).getDayOfWeek();
                if (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY) {
                    count++;
                }
            }
            return count;
        } catch (Exception e) {
            return 0;
        }
    }
 
    private BigDecimal nz(BigDecimal v) {
        return v == null ? BigDecimal.ZERO : v;
    }
 
    private BigDecimal nzDefault(BigDecimal v, BigDecimal def) {
        return v == null ? def : v;
    }
 
    private String key(Object... parts) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parts.length; i++) {
            if (i > 0) {
                sb.append(':');
            }
            sb.append(parts[i] == null ? "null" : parts[i]);
        }
        return sb.toString();
    }
}