10 小时以前 7bf754a7835d06f26240c4ca15bc5f83650de377
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
package com.ruoyi.purchase.service.impl;
 
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.mapper.SupplierManageMapper;
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.enums.PurchaseApplicationStatusEnum;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.purchase.dto.PurchaseApplicationDto;
import com.ruoyi.purchase.dto.PurchaseLedgerDto;
import com.ruoyi.purchase.mapper.PurchaseApplicationMapper;
import com.ruoyi.purchase.mapper.PurchaseApplicationProductMapper;
import com.ruoyi.purchase.pojo.PurchaseApplication;
import com.ruoyi.purchase.pojo.PurchaseApplicationProduct;
import com.ruoyi.purchase.service.IPurchaseLedgerService;
import com.ruoyi.purchase.service.PurchaseApplicationService;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
 
/**
 * 采购申请单 Service 业务层处理
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class PurchaseApplicationServiceImpl extends ServiceImpl<PurchaseApplicationMapper, PurchaseApplication>
        implements PurchaseApplicationService {
 
    private static final String APPLICATION_NO_PREFIX = "CGSQ";
 
    private final PurchaseApplicationMapper purchaseApplicationMapper;
    private final PurchaseApplicationProductMapper purchaseApplicationProductMapper;
    private final IPurchaseLedgerService purchaseLedgerService;
    private final SupplierManageMapper supplierManageMapper;
    private final SysUserMapper sysUserMapper;
    private final ProductMapper productMapper;
    private final ProductModelMapper productModelMapper;
    private final FileUtil fileUtil;
 
    @Override
    public List<PurchaseApplication> selectApplicationList(PurchaseApplicationDto purchaseApplicationDto) {
        LambdaQueryWrapper<PurchaseApplication> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(purchaseApplicationDto.getApplicationNo()),
                        PurchaseApplication::getApplicationNo, purchaseApplicationDto.getApplicationNo())
                .like(StringUtils.isNotBlank(purchaseApplicationDto.getProjectName()),
                        PurchaseApplication::getProjectName, purchaseApplicationDto.getProjectName())
                .eq(purchaseApplicationDto.getSupplierId() != null,
                        PurchaseApplication::getSupplierId, purchaseApplicationDto.getSupplierId())
                .eq(purchaseApplicationDto.getStatus() != null,
                        PurchaseApplication::getStatus, purchaseApplicationDto.getStatus())
                .ge(StringUtils.isNotBlank(purchaseApplicationDto.getStartDate()),
                        PurchaseApplication::getApplyDate, purchaseApplicationDto.getStartDate())
                .le(StringUtils.isNotBlank(purchaseApplicationDto.getEndDate()),
                        PurchaseApplication::getApplyDate, purchaseApplicationDto.getEndDate())
                .orderByDesc(PurchaseApplication::getId);
        return this.list(queryWrapper);
    }
 
    @Override
    public PurchaseApplicationDto getDetail(Long id) {
        PurchaseApplication application = this.getById(id);
        if (application == null) {
            throw new ServiceException("采购申请不存在");
        }
        PurchaseApplicationDto resultDto = new PurchaseApplicationDto();
        BeanUtils.copyProperties(application, resultDto);
        resultDto.setProductData(listProducts(id));
        resultDto.setStorageBlobVOS(listAttachments(id));
        return resultDto;
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long saveApplication(PurchaseApplicationDto purchaseApplicationDto) {
        PurchaseApplication application = new PurchaseApplication();
        BeanUtils.copyProperties(purchaseApplicationDto, application);
        application.setId(null);
        application.setStatus(PurchaseApplicationStatusEnum.DRAFT.getCode());
        application.setPurchaseLedgerId(null);
        application.setConvertTime(null);
        application.setApplicationNo(OrderUtils.countTodayByCreateTime(
                purchaseApplicationMapper, APPLICATION_NO_PREFIX, "application_no", LocalDateTime.now()));
        fillSupplierName(application);
        fillApplicant(application, purchaseApplicationDto);
        this.save(application);
 
        saveProducts(application.getId(), purchaseApplicationDto.getProductData());
        saveAttachments(application.getId(), purchaseApplicationDto.getStorageBlobDTOS());
        return application.getId();
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateApplication(PurchaseApplicationDto purchaseApplicationDto) {
        if (purchaseApplicationDto.getId() == null) {
            throw new ServiceException("采购申请id不能为空");
        }
        PurchaseApplication existing = this.getById(purchaseApplicationDto.getId());
        if (existing == null) {
            throw new ServiceException("采购申请不存在");
        }
        if (!PurchaseApplicationStatusEnum.DRAFT.getCode().equals(existing.getStatus())) {
            throw new ServiceException("已生成订单的采购申请不允许修改");
        }
 
        PurchaseApplication application = new PurchaseApplication();
        BeanUtils.copyProperties(purchaseApplicationDto, application);
        // 单号、状态、订单关联由系统维护,不允许前端改动;置空后 MyBatis-Plus 不会更新这些列
        application.setApplicationNo(null);
        application.setStatus(null);
        application.setPurchaseLedgerId(null);
        application.setConvertTime(null);
        application.setTenantId(null);
        application.setCreateUser(null);
        application.setCreateTime(null);
        fillSupplierName(application);
        boolean updated = this.updateById(application);
 
        if (updated) {
            purchaseApplicationProductMapper.delete(Wrappers.<PurchaseApplicationProduct>lambdaQuery()
                    .eq(PurchaseApplicationProduct::getPurchaseApplicationId, purchaseApplicationDto.getId()));
            saveProducts(purchaseApplicationDto.getId(), purchaseApplicationDto.getProductData());
            saveAttachments(purchaseApplicationDto.getId(), purchaseApplicationDto.getStorageBlobDTOS());
        }
        return updated;
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteApplications(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            throw new ServiceException("请选择要删除的采购申请");
        }
        List<String> orderedNos = this.listByIds(ids).stream()
                .filter(application -> !PurchaseApplicationStatusEnum.DRAFT.getCode().equals(application.getStatus()))
                .map(PurchaseApplication::getApplicationNo)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (!orderedNos.isEmpty()) {
            throw new ServiceException("已生成订单的采购申请不允许删除:" + String.join("、", orderedNos));
        }
 
        purchaseApplicationProductMapper.delete(Wrappers.<PurchaseApplicationProduct>lambdaQuery()
                .in(PurchaseApplicationProduct::getPurchaseApplicationId, ids));
        return this.removeByIds(ids);
    }
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long convertToOrder(Long id) {
        if (id == null) {
            throw new ServiceException("采购申请id不能为空");
        }
        PurchaseApplication application = this.getById(id);
        if (application == null) {
            throw new ServiceException("采购申请不存在");
        }
        if (application.getSupplierId() == null) {
            throw new ServiceException("请先选择供应商再转订单");
        }
        SupplierManage supplierManage = supplierManageMapper.selectById(application.getSupplierId());
        if (supplierManage == null) {
            throw new ServiceException("供应商不存在,请重新选择");
        }
        List<PurchaseApplicationProduct> products = listProducts(id);
        if (products.isEmpty()) {
            throw new ServiceException("采购申请没有产品明细,无法转订单");
        }
 
        // 条件更新抢占草稿状态,避免并发下重复生成订单
        boolean converted = this.update(Wrappers.<PurchaseApplication>lambdaUpdate()
                .eq(PurchaseApplication::getId, id)
                .eq(PurchaseApplication::getStatus, PurchaseApplicationStatusEnum.DRAFT.getCode())
                .set(PurchaseApplication::getStatus, PurchaseApplicationStatusEnum.ORDER_GENERATED.getCode()));
        if (!converted) {
            throw new ServiceException("该采购申请已生成订单,请勿重复转换");
        }
 
        PurchaseLedgerDto purchaseLedgerDto = buildPurchaseLedgerDto(application, supplierManage, products);
        try {
            // 复用采购台账新增逻辑:生成采购合同号、保存台账与产品明细、提交 OA 采购审批
            purchaseLedgerService.addOrEditPurchase(purchaseLedgerDto);
        } catch (Exception e) {
            log.error("采购申请转订单失败, applicationId={}, applicationNo={}",
                    id, application.getApplicationNo(), e);
            throw new ServiceException("转订单失败:" + e.getMessage());
        }
 
        Long purchaseLedgerId = purchaseLedgerDto.getId();
        if (purchaseLedgerId == null) {
            throw new ServiceException("转订单失败:未生成采购订单");
        }
        this.update(Wrappers.<PurchaseApplication>lambdaUpdate()
                .eq(PurchaseApplication::getId, id)
                .set(PurchaseApplication::getPurchaseLedgerId, purchaseLedgerId)
                .set(PurchaseApplication::getConvertTime, LocalDateTime.now()));
        return purchaseLedgerId;
    }
 
    /**
     * 保存附件:先清空该申请单的旧关联,再按前端提交的文件全量重建
     */
    private void saveAttachments(Long applicationId, List<StorageBlobDTO> storageBlobDTOS) {
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.PURCHASE_APPLICATION,
                applicationId, storageBlobDTOS);
    }
 
    private List<StorageBlobVO> listAttachments(Long applicationId) {
        return fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(
                ApplicationTypeEnum.FILE, RecordTypeEnum.PURCHASE_APPLICATION, applicationId);
    }
 
    private List<PurchaseApplicationProduct> listProducts(Long applicationId) {
        return purchaseApplicationProductMapper.selectList(Wrappers.<PurchaseApplicationProduct>lambdaQuery()
                .eq(PurchaseApplicationProduct::getPurchaseApplicationId, applicationId)
                .orderByAsc(PurchaseApplicationProduct::getId));
    }
 
    private void saveProducts(Long applicationId, List<PurchaseApplicationProduct> products) {
        if (products == null || products.isEmpty()) {
            return;
        }
        fillProductNames(products);
        for (PurchaseApplicationProduct product : products) {
            product.setId(null);
            product.setPurchaseApplicationId(applicationId);
            product.setTenantId(null);
            product.setCreateUser(null);
            product.setCreateTime(null);
            purchaseApplicationProductMapper.insert(product);
        }
    }
 
    /**
     * 补全产品大类与规格型号,便于申请单直接展示产品名称
     */
    private void fillProductNames(List<PurchaseApplicationProduct> products) {
        Set<Long> productIds = products.stream()
                .map(PurchaseApplicationProduct::getProductId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Set<Long> modelIds = products.stream()
                .map(PurchaseApplicationProduct::getProductModelId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
 
        Map<Long, String> productMap = new HashMap<>();
        if (!productIds.isEmpty()) {
            for (Product product : productMapper.selectBatchIds(productIds)) {
                productMap.put(product.getId(), product.getProductName());
            }
        }
        Map<Long, String> modelMap = new HashMap<>();
        if (!modelIds.isEmpty()) {
            for (ProductModel model : productModelMapper.selectBatchIds(modelIds)) {
                modelMap.put(model.getId(), model.getModel());
            }
        }
 
        for (PurchaseApplicationProduct product : products) {
            String productName = productMap.get(product.getProductId());
            if (productName != null) {
                product.setProductCategory(productName);
            }
            String model = modelMap.get(product.getProductModelId());
            if (model != null) {
                product.setSpecificationModel(model);
            }
        }
    }
 
    private void fillSupplierName(PurchaseApplication application) {
        if (application.getSupplierId() == null) {
            return;
        }
        SupplierManage supplierManage = supplierManageMapper.selectById(application.getSupplierId());
        if (supplierManage != null) {
            application.setSupplierName(supplierManage.getSupplierName());
        }
    }
 
    private void fillApplicant(PurchaseApplication application, PurchaseApplicationDto purchaseApplicationDto) {
        Long applicantId = purchaseApplicationDto.getApplicantId() != null
                ? purchaseApplicationDto.getApplicantId()
                : SecurityUtils.getUserId();
        application.setApplicantId(applicantId);
 
        if (StringUtils.isBlank(purchaseApplicationDto.getApplicantName())) {
            SysUser applicant = sysUserMapper.selectUserById(applicantId);
            application.setApplicantName(applicant != null ? applicant.getNickName() : SecurityUtils.getUsername());
        }
        if (application.getApplyDate() == null) {
            application.setApplyDate(LocalDate.now());
        }
    }
 
    /**
     * 组装采购台账入参:供应商、销售合同、产品明细等;采购合同号由采购台账侧生成
     */
    private PurchaseLedgerDto buildPurchaseLedgerDto(PurchaseApplication application,
                                                     SupplierManage supplierManage,
                                                     List<PurchaseApplicationProduct> products) {
        PurchaseLedgerDto purchaseLedgerDto = new PurchaseLedgerDto();
        purchaseLedgerDto.setSupplierId(application.getSupplierId());
        purchaseLedgerDto.setSupplierName(supplierManage.getSupplierName());
        purchaseLedgerDto.setRecorderId(application.getRecorderId() != null
                ? application.getRecorderId() : application.getApplicantId());
        purchaseLedgerDto.setRecorderName(application.getApplicantName());
        purchaseLedgerDto.setSalesContractNo(application.getSalesContractNo());
        purchaseLedgerDto.setSalesLedgerId(application.getSalesLedgerId());
        purchaseLedgerDto.setProjectName(application.getProjectName());
        purchaseLedgerDto.setRemarks(application.getRemarks());
        purchaseLedgerDto.setPaymentMethod(application.getPaymentMethod());
        purchaseLedgerDto.setContractAmount(application.getContractAmount());
        purchaseLedgerDto.setPurchaseContractNumber(application.getPurchaseContractNumber());
        purchaseLedgerDto.setExecutionDate(toDateOrNull(application.getExecutionDate()));
        purchaseLedgerDto.setBestArrivalDate(toDateOrNull(application.getBestArrivalDate()));
        purchaseLedgerDto.setApplicationSubmitTime(toDateOrNull(application.getApplicationSubmitTime()));
        purchaseLedgerDto.setPurchaseCategory(application.getPurchaseCategory());
        purchaseLedgerDto.setLogisticsMethod(application.getLogisticsMethod());
        purchaseLedgerDto.setPickupMethod(application.getPickupMethod());
        purchaseLedgerDto.setOilDepotId(application.getOilDepotId());
        purchaseLedgerDto.setCustomerName(application.getCustomerName());
        purchaseLedgerDto.setPurchaseQuantity(application.getPurchaseQuantity());
        purchaseLedgerDto.setPurchaseOrderStatus(application.getPurchaseOrderStatus());
 
        LocalDate entryDate = application.getEntryDate() != null ? application.getEntryDate()
                : (application.getApplyDate() != null ? application.getApplyDate() : LocalDate.now());
        purchaseLedgerDto.setEntryDate(DateUtils.toDate(entryDate));
 
        purchaseLedgerDto.setProductData(products.stream()
                .map(this::toSalesLedgerProduct)
                .collect(Collectors.toList()));
        purchaseLedgerDto.setStorageBlobDTOS(toStorageBlobDtos(listAttachments(application.getId())));
        return purchaseLedgerDto;
    }
 
    private static Date toDateOrNull(LocalDate date) {
        return date == null ? null : DateUtils.toDate(date);
    }
 
    private static Date toDateOrNull(LocalDateTime dateTime) {
        return dateTime == null ? null : DateUtils.toDate(dateTime);
    }
 
    /**
     * 申请单附件原样关联到生成的采购台账:复用同一份 storage_blob,只改关联记录
     */
    private List<StorageBlobDTO> toStorageBlobDtos(List<StorageBlobVO> storageBlobVOS) {
        if (storageBlobVOS == null || storageBlobVOS.isEmpty()) {
            return Collections.emptyList();
        }
        return storageBlobVOS.stream()
                .map(vo -> {
                    StorageBlobDTO dto = new StorageBlobDTO();
                    dto.setId(vo.getId());
                    return dto;
                })
                .collect(Collectors.toList());
    }
 
    private SalesLedgerProduct toSalesLedgerProduct(PurchaseApplicationProduct source) {
        SalesLedgerProduct target = new SalesLedgerProduct();
        target.setProductId(source.getProductId());
        target.setProductModelId(source.getProductModelId());
        target.setProductCategory(source.getProductCategory());
        target.setSpecificationModel(source.getSpecificationModel());
        target.setUnit(source.getUnit());
        target.setQuantity(source.getQuantity());
        target.setWarnNum(source.getWarnNum());
        target.setTaxRate(source.getTaxRate());
        target.setTaxInclusiveUnitPrice(source.getTaxInclusiveUnitPrice());
        target.setTaxInclusiveTotalPrice(source.getTaxInclusiveTotalPrice());
        target.setTaxExclusiveTotalPrice(source.getTaxExclusiveTotalPrice());
        target.setInvoiceType(source.getInvoiceType());
        target.setIsChecked(source.getIsChecked());
        return target;
    }
}