完成成品出货单。

This commit is contained in:
c
2026-03-11 16:52:11 +08:00
parent 742d7750f9
commit a9d8c0f7e7
7 changed files with 321 additions and 219 deletions

View File

@@ -4,24 +4,23 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.niuan.erp.common.annotation.ApiLog; import com.niuan.erp.common.annotation.ApiLog;
import com.niuan.erp.common.annotation.ModuleLog; import com.niuan.erp.common.annotation.ModuleLog;
import com.niuan.erp.common.base.BaseApproveAndRejectDto;
import com.niuan.erp.common.base.BaseDeleteBody; import com.niuan.erp.common.base.BaseDeleteBody;
import com.niuan.erp.common.base.BasePageReqParams; import com.niuan.erp.common.base.BasePageReqParams;
import com.niuan.erp.common.base.BaseResult; import com.niuan.erp.common.base.BaseResult;
import com.niuan.erp.common.base.CommonValidateGroup.*; import com.niuan.erp.common.base.CommonValidateGroup.*;
import com.niuan.erp.common.base.OperationType; import com.niuan.erp.common.base.OperationType;
import com.niuan.erp.module.common.entity.Document; import com.niuan.erp.module.common.entity.Document;
import com.niuan.erp.module.production.controller.dto.DeviceShipmentDto;
import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentAddDto; import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentAddDto;
import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentDto; import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentDto;
import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentItemDto;
import com.niuan.erp.module.production.service.FinishedProductShipmentService; import com.niuan.erp.module.production.service.FinishedProductShipmentService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
@@ -65,54 +64,35 @@ public class FinishedProductShipmentController {
@ApiLog(type = OperationType.DELETE, remark = "删除一条FinishedProductShipment记录") @ApiLog(type = OperationType.DELETE, remark = "删除一条FinishedProductShipment记录")
@Operation(summary = "删除FinishedProductShipment", operationId = "deleteFinishedProductShipment") @Operation(summary = "删除FinishedProductShipment", operationId = "deleteFinishedProductShipment")
@PostMapping("/deleteFinishedProductShipment") @PostMapping("/deleteFinishedProductShipment")
@PreAuthorize("hasAuthority('finished_product_shipment:delete')") @PreAuthorize("hasAuthority('finished_product_shipment:remove')")
public BaseResult<?> deleteFinishedProductShipment(@Validated(DeleteOne.class) @RequestBody BaseDeleteBody req) { public BaseResult<?> deleteFinishedProductShipment(@Validated(DeleteOne.class) @RequestBody BaseDeleteBody req) {
finishedProductShipmentService.deleteFinishedProductShipment(req.id()); finishedProductShipmentService.deleteFinishedProductShipment(req.id());
return BaseResult.success(); return BaseResult.success();
} }
@ApiLog(type = OperationType.DELETE, remark = "批量删除FinishedProductShipment记录")
@Operation(summary = "批量删除FinishedProductShipment", operationId = "deleteFinishedProductShipmentBatch")
@PostMapping("/deleteFinishedProductShipmentBatch")
@PreAuthorize("hasAuthority('finished_product_shipment:deleteBatch')")
public BaseResult<?> deleteFinishedProductShipmentBatch(@Validated(DeleteBatch.class) @RequestBody BaseDeleteBody req) {
finishedProductShipmentService.deleteBatch(req.ids());
return BaseResult.success();
}
@ApiLog(type = OperationType.UPDATE, remark = "审核FinishedProductShipment") @ApiLog(type = OperationType.UPDATE, remark = "审核FinishedProductShipment")
@Operation(summary = "审核FinishedProductShipment", operationId = "approveFinishedProductShipment") @Operation(summary = "审核FinishedProductShipment", operationId = "approveFinishedProductShipment")
@PostMapping("/approveFinishedProductShipment") @PostMapping("/approveFinishedProductShipment")
@PreAuthorize("hasAuthority('finished_product_shipment:approve')") @PreAuthorize("hasAuthority('finished_product_shipment:approve')")
public BaseResult<?> approveFinishedProductShipment( public BaseResult<?> approveFinishedProductShipment(@Validated @RequestBody BaseApproveAndRejectDto dto) {
@Parameter(description = "成品出库单ID") @RequestParam Long id) { finishedProductShipmentService.approve(dto.id());
finishedProductShipmentService.approve(id);
return BaseResult.success(); return BaseResult.success();
} }
@ApiLog(type = OperationType.UPDATE, remark = "反审核FinishedProductShipment") @ApiLog(type = OperationType.UPDATE, remark = "反审核FinishedProductShipment")
@Operation(summary = "反审核FinishedProductShipment", operationId = "unapproveFinishedProductShipment") @Operation(summary = "反审核FinishedProductShipment", operationId = "rejectFinishedProductShipment")
@PostMapping("/unapproveFinishedProductShipment") @PostMapping("/rejectFinishedProductShipment")
@PreAuthorize("hasAuthority('finished_product_shipment:unapprove')") @PreAuthorize("hasAuthority('finished_product_shipment:reject')")
public BaseResult<?> unapproveFinishedProductShipment( public BaseResult<?> rejectFinishedProductShipment(@Validated @RequestBody BaseApproveAndRejectDto dto) {
@Parameter(description = "成品出库单ID") @RequestParam Long id) { finishedProductShipmentService.unapprove(dto.id());
finishedProductShipmentService.unapprove(id);
return BaseResult.success(); return BaseResult.success();
} }
@Operation(summary = "获取FinishedProductShipment明细", operationId = "getFinishedProductShipmentDetail") @Operation(summary = "获取FinishedProductShipment明细", operationId = "getFinishedProductShipmentDetail")
@GetMapping("/getFinishedProductShipmentDetail") @GetMapping("/getFinishedProductShipmentDetail")
@PreAuthorize("hasAuthority('finishedproductshipment:index')") @PreAuthorize("hasAuthority('finished_product_shipment:showItem')")
public BaseResult<List<DeviceShipmentDto>> getFinishedProductShipmentDetail( public BaseResult<List<FinishedProductShipmentItemDto>> getFinishedProductShipmentDetail(
@Parameter(description = "成品出库单ID") @RequestParam Long id) { @RequestParam Long id) {
return BaseResult.successWithData(finishedProductShipmentService.getDetail(id)); return BaseResult.successWithData(finishedProductShipmentService.getDetail(id));
} }
@Operation(summary = "导入FinishedProductShipment明细", operationId = "importFinishedProductShipmentItems")
@PostMapping("/importFinishedProductShipmentItems")
@PreAuthorize("hasAuthority('finished_product_shipment:import')")
public BaseResult<List<DeviceShipmentDto>> importFinishedProductShipmentItems(
@Parameter(description = "Excel文件") @RequestParam("file") MultipartFile file) {
return BaseResult.successWithData(finishedProductShipmentService.importItems(file));
}
} }

View File

@@ -1,29 +1,52 @@
package com.niuan.erp.module.production.controller.dto; package com.niuan.erp.module.production.controller.dto;
import com.niuan.erp.module.production.enums.OutStockType;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Schema(description = "成品出库DTO")
public record FinishedProductShipmentDto( public record FinishedProductShipmentDto(
@Schema(description = "ID")
Long id, Long id,
@Schema(description = "状态")
Integer status, Integer status,
@Schema(description = "创建时间")
LocalDateTime createDate, LocalDateTime createDate,
@Schema(description = "创建用户ID")
Long createUserId, Long createUserId,
@Schema(description = "创建用户名")
String createUserName, String createUserName,
@Schema(description = "更新时间")
LocalDateTime updateDate, LocalDateTime updateDate,
@Schema(description = "更新用户ID")
Long updateUserId, Long updateUserId,
@Schema(description = "更新用户名")
String updateUserName, String updateUserName,
@Schema(description = "仓库编号")
Integer storeNo, Integer storeNo,
@Schema(description = "仓库名称")
String storeName, String storeName,
@Schema(description = "单据类型")
Integer formType, Integer formType,
@Schema(description = "单据编号")
String formCode, String formCode,
@Schema(description = "单据名称")
String formName, String formName,
@Schema(description = "单据状态")
Integer formStatus, Integer formStatus,
@Schema(description = "单据备注")
String formMark, String formMark,
Integer reserve1, @Schema(description = "出库类型")
String reserve2, OutStockType outStockType,
Integer vendorNo, @Schema(description = "总价值")
String vendorName,
Double totalValue, Double totalValue,
@Schema(description = "出库仓库编号")
Integer outStoreNo, Integer outStoreNo,
@Schema(description = "出库仓库名称")
String outStoreName, String outStoreName,
@Schema(description = "分组ID")
Integer groupId, Integer groupId,
Integer customerId) {} @Schema(description = "客户ID")
Integer customerId,
@Schema(description = "出库明细列表")
java.util.List<FinishedProductShipmentItemDto> shipmentItems) {}

View File

@@ -1,12 +1,14 @@
package com.niuan.erp.module.production.converter; package com.niuan.erp.module.production.converter;
import com.niuan.erp.module.common.entity.Document; import com.niuan.erp.module.common.entity.Document;
import com.niuan.erp.module.production.controller.dto.DeviceShipmentDto; import com.niuan.erp.module.common.entity.DocumentMaterial;
import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentAddDto; import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentAddDto;
import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentDto; import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentDto;
import com.niuan.erp.module.sale.entity.Device; import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentItemDto;
import com.niuan.erp.module.sale.controller.dto.DeviceAddDto; import com.niuan.erp.module.production.enums.OutStockType;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.ReportingPolicy; import org.mapstruct.ReportingPolicy;
import java.util.List; import java.util.List;
@@ -14,13 +16,67 @@ import java.util.List;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) @Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface FinishedProductShipmentConverter { public interface FinishedProductShipmentConverter {
Document toEntity(FinishedProductShipmentDto dto); Document toEntity(FinishedProductShipmentDto dto);
@Mapping(target = "reserve1", source = "outStockType", qualifiedByName = "outStockTypeToInteger")
@Mapping(target = "formType", ignore = true)
Document toEntity(FinishedProductShipmentAddDto dto); Document toEntity(FinishedProductShipmentAddDto dto);
@Mapping(target = "outStockType", source = "reserve1", qualifiedByName = "integerToOutStockType")
@Mapping(target = "formType", source = "formType", qualifiedByName = "documentTypeToInteger")
@Mapping(target = "formStatus", source = "formStatus", qualifiedByName = "formStatusToInteger")
FinishedProductShipmentDto toDto(Document entity); FinishedProductShipmentDto toDto(Document entity);
List<FinishedProductShipmentDto> toDtoList(List<Document> entities); List<FinishedProductShipmentDto> toDtoList(List<Document> entities);
Device toEntity(DeviceAddDto dto); @Mapping(target = "partNumber", source = "partNumber")
List<Device> toEntityList(List<DeviceAddDto> dtoList); @Mapping(target = "productCount", source = "productCount")
@Mapping(target = "productMark", source = "productMark")
FinishedProductShipmentItemDto toItemDto(DocumentMaterial entity);
DeviceShipmentDto toDeviceShipmentDto(Device entity); List<FinishedProductShipmentItemDto> toItemDtoList(List<DocumentMaterial> entities);
List<DeviceShipmentDto> toDeviceShipmentDtoList(List<Device> entities);
} @Mapping(target = "partNumber", source = "partNumber")
@Mapping(target = "productCount", source = "productCount")
@Mapping(target = "productMark", source = "productMark")
@Mapping(target = "documentNo", ignore = true)
DocumentMaterial toMaterialEntity(FinishedProductShipmentItemDto dto);
List<DocumentMaterial> toMaterialEntityList(List<FinishedProductShipmentItemDto> dtoList);
@Named("outStockTypeToInteger")
default Integer outStockTypeToInteger(OutStockType outStockType) {
if (outStockType == null) {
return null;
}
return outStockType.getCode();
}
@Named("integerToOutStockType")
default OutStockType integerToOutStockType(Integer code) {
if (code == null) {
return null;
}
for (OutStockType type : OutStockType.values()) {
if (type.getCode() == code) {
return type;
}
}
return null;
}
@Named("documentTypeToInteger")
default Integer documentTypeToInteger(com.niuan.erp.module.common.enums.DocumentType documentType) {
if (documentType == null) {
return null;
}
return documentType.getValue();
}
@Named("formStatusToInteger")
default Integer formStatusToInteger(com.niuan.erp.module.common.enums.FormStatus formStatus) {
if (formStatus == null) {
return null;
}
return formStatus.getValue();
}
}

View File

@@ -4,9 +4,9 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.niuan.erp.common.base.BasePageReqParams; import com.niuan.erp.common.base.BasePageReqParams;
import com.niuan.erp.module.common.entity.Document; import com.niuan.erp.module.common.entity.Document;
import com.niuan.erp.module.production.controller.dto.DeviceShipmentDto;
import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentAddDto; import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentAddDto;
import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentDto; import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentDto;
import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentItemDto;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
@@ -21,13 +21,9 @@ public interface FinishedProductShipmentService {
void deleteFinishedProductShipment(long id); void deleteFinishedProductShipment(long id);
void deleteBatch(List<Long> ids);
void approve(long id); void approve(long id);
void unapprove(long id); void unapprove(long id);
List<DeviceShipmentDto> getDetail(long id); List<FinishedProductShipmentItemDto> getDetail(long id);
List<DeviceShipmentDto> importItems(MultipartFile file);
} }

View File

@@ -9,24 +9,26 @@ import com.niuan.erp.common.base.BasePageReqParams;
import com.niuan.erp.common.exception.BusinessException; import com.niuan.erp.common.exception.BusinessException;
import com.niuan.erp.common.utils.SecurityUtils; import com.niuan.erp.common.utils.SecurityUtils;
import com.niuan.erp.module.common.entity.Document; import com.niuan.erp.module.common.entity.Document;
import com.niuan.erp.module.common.entity.DocumentMaterial;
import com.niuan.erp.module.common.enums.DocumentType; import com.niuan.erp.module.common.enums.DocumentType;
import com.niuan.erp.module.common.enums.FormStatus; import com.niuan.erp.module.common.enums.FormStatus;
import com.niuan.erp.module.common.mapper.DocumentMapper; import com.niuan.erp.module.common.mapper.DocumentMapper;
import com.niuan.erp.module.production.controller.dto.DeviceShipmentDto; import com.niuan.erp.module.common.mapper.DocumentMaterialMapper;
import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentAddDto; import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentAddDto;
import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentDto; import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentDto;
import com.niuan.erp.module.production.controller.dto.FinishedProductShipmentItemDto;
import com.niuan.erp.module.production.converter.FinishedProductShipmentConverter; import com.niuan.erp.module.production.converter.FinishedProductShipmentConverter;
import com.niuan.erp.module.production.enums.OutStockType;
import com.niuan.erp.module.production.service.FinishedProductShipmentService; import com.niuan.erp.module.production.service.FinishedProductShipmentService;
import com.niuan.erp.module.sale.entity.Device; import com.niuan.erp.module.warehouse.entity.Stock;
import com.niuan.erp.module.sale.mapper.DeviceMapper; import com.niuan.erp.module.warehouse.entity.WarehouseItem;
import com.niuan.erp.module.warehouse.mapper.StockMapper;
import com.niuan.erp.module.warehouse.mapper.WarehouseItemMapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.*;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -40,60 +42,86 @@ public class FinishedProductShipmentServiceImpl extends ServiceImpl<DocumentMapp
private final FinishedProductShipmentConverter finishedProductShipmentConverter; private final FinishedProductShipmentConverter finishedProductShipmentConverter;
private final DeviceMapper deviceMapper; private final DocumentMaterialMapper documentMaterialMapper;
private final StockMapper stockMapper;
private final WarehouseItemMapper warehouseItemMapper;
@Override @Override
public IPage<FinishedProductShipmentDto> getFinishedProductShipmentPage(BasePageReqParams pageParams, LambdaQueryWrapper<Document> wrapper) { public IPage<FinishedProductShipmentDto> getFinishedProductShipmentPage(BasePageReqParams pageParams, LambdaQueryWrapper<Document> wrapper) {
wrapper.eq(Document::getFormType, DocumentType.FINISHED_PRODUCT_SHIPMENT); wrapper.eq(Document::getFormType, DocumentType.FINISHED_PRODUCT_SHIPMENT)
.or()
.eq(Document::getFormType, DocumentType.WAREHOUSE_ISSUE);
// 按创建时间倒序排列
wrapper.orderByDesc(Document::getCreateDate);
IPage<Document> result = this.baseMapper.selectPage(new Page<>(pageParams.page(), pageParams.pageSize()), wrapper); IPage<Document> result = this.baseMapper.selectPage(new Page<>(pageParams.page(), pageParams.pageSize()), wrapper);
return result.convert(finishedProductShipmentConverter::toDto); return result.convert(finishedProductShipmentConverter::toDto);
} }
@Override @Override
public void addFinishedProductShipment(FinishedProductShipmentAddDto dto) { public void addFinishedProductShipment(FinishedProductShipmentAddDto dto) {
List<com.niuan.erp.module.sale.controller.dto.DeviceAddDto> deviceItems = dto.deviceItems(); // 基础校验
if (dto == null) {
if (deviceItems == null || deviceItems.isEmpty()) { throw new BusinessException("production.finished_product_shipment.validate.dto.not_null");
throw new BusinessException("production.finished_product_shipment.exception.no_device_items"); }
if (dto.outStockType() == null) {
throw new BusinessException("production.finished_product_shipment.validate.out_stock_type.not_null");
}
if (!StringUtils.hasText(dto.formCode())) {
throw new BusinessException("production.finished_product_shipment.validate.form_code.not_null");
}
if (!StringUtils.hasText(dto.formName())) {
throw new BusinessException("production.finished_product_shipment.validate.form_name.not_null");
}
if (dto.storeNo() == null) {
throw new BusinessException("production.finished_product_shipment.validate.store_no.not_null");
} }
List<String> snList = deviceItems.stream() List<FinishedProductShipmentItemDto> shipmentItems = dto.shipmentItems();
.map(com.niuan.erp.module.sale.controller.dto.DeviceAddDto::productSn)
if (shipmentItems == null || shipmentItems.isEmpty()) {
throw new BusinessException("production.finished_product_shipment.exception.no_shipment_items");
}
// 校验明细项
for (FinishedProductShipmentItemDto item : shipmentItems) {
if (!StringUtils.hasText(item.partNumber())) {
throw new BusinessException("production.finished_product_shipment.validate.part_number.not_blank");
}
if (item.productCount() == null || item.productCount() <= 0) {
throw new BusinessException("production.finished_product_shipment.validate.product_count.positive");
}
}
List<String> partNumberList = shipmentItems.stream()
.map(FinishedProductShipmentItemDto::partNumber)
.filter(StringUtils::hasText) .filter(StringUtils::hasText)
.toList(); .toList();
if (snList.isEmpty()) { if (partNumberList.isEmpty()) {
throw new BusinessException("production.finished_product_shipment.exception.no_device_items"); throw new BusinessException("production.finished_product_shipment.exception.no_shipment_items");
} }
Set<String> uniqueSnSet = new HashSet<>(snList); Set<String> uniquePartNumberSet = new HashSet<>(partNumberList);
if (uniqueSnSet.size() != snList.size()) { if (uniquePartNumberSet.size() != partNumberList.size()) {
throw new BusinessException("production.finished_product_shipment.exception.duplicate_sn_in_request"); throw new BusinessException("production.finished_product_shipment.exception.duplicate_part_number_in_request");
} }
LambdaQueryWrapper<Device> snWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<WarehouseItem> itemWrapper = new LambdaQueryWrapper<>();
snWrapper.in(Device::getProductSn, snList); itemWrapper.in(WarehouseItem::getPartNumber, partNumberList);
List<Device> existingSnList = deviceMapper.selectList(snWrapper); List<WarehouseItem> existingItems = warehouseItemMapper.selectList(itemWrapper);
Set<String> existingSnSet = existingSnList.stream() Set<String> existingPartNumberSet = existingItems.stream()
.map(Device::getProductSn) .map(WarehouseItem::getPartNumber)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
List<String> notFoundSn = snList.stream() List<String> notFoundPartNumbers = partNumberList.stream()
.filter(sn -> !existingSnSet.contains(sn)) .filter(pn -> !existingPartNumberSet.contains(pn))
.map(sn -> "SN号:" + sn) .map(pn -> "物料编号:" + pn)
.toList(); .toList();
if (!notFoundSn.isEmpty()) { if (!notFoundPartNumbers.isEmpty()) {
throw new BusinessException("production.finished_product_shipment.exception.sn_not_found"); throw new BusinessException("production.finished_product_shipment.exception.part_number_not_found");
}
List<String> alreadyShippedSn = existingSnList.stream()
.filter(item -> item.getOutStatus() != null && item.getOutStatus())
.map(item -> "SN号" + item.getProductSn())
.toList();
if (!alreadyShippedSn.isEmpty()) {
throw new BusinessException("production.finished_product_shipment.exception.sn_already_shipped");
} }
Document entity = finishedProductShipmentConverter.toEntity(dto); Document entity = finishedProductShipmentConverter.toEntity(dto);
@@ -101,25 +129,31 @@ public class FinishedProductShipmentServiceImpl extends ServiceImpl<DocumentMapp
entity.setCreateUserName(SecurityUtils.getUserName()); entity.setCreateUserName(SecurityUtils.getUserName());
entity.setCreateDate(LocalDateTime.now()); entity.setCreateDate(LocalDateTime.now());
entity.setStatus(0); entity.setStatus(0);
entity.setFormType(DocumentType.FINISHED_PRODUCT_SHIPMENT); // 根据出库类型设置不同的单据类型:物料出库使用 WAREHOUSE_ISSUE(4),成品出库使用 FINISHED_PRODUCT_SHIPMENT(7)
// 注意Spring 默认使用枚举的 ordinal 值转换,所以 1 对应 FINISHED_PRODUCT (ordinal=1),需要用 code 比较
if (dto.outStockType() != null && dto.outStockType().getCode() == 1) {
entity.setFormType(DocumentType.WAREHOUSE_ISSUE);
} else {
entity.setFormType(DocumentType.FINISHED_PRODUCT_SHIPMENT);
}
entity.setFormStatus(FormStatus.NO_APPROVE); entity.setFormStatus(FormStatus.NO_APPROVE);
entity.setTotalValue(Double.valueOf(deviceItems.size())); entity.setTotalValue(Double.valueOf(shipmentItems.size()));
entity.setCustomerId(SecurityUtils.getCustomerId()); entity.setCustomerId(SecurityUtils.getCustomerId());
entity.setReserve1(dto.outStockType() != null ? dto.outStockType().getValue() : null);
this.baseMapper.insert(entity); this.baseMapper.insert(entity);
List<Device> deviceEntities = finishedProductShipmentConverter.toEntityList(deviceItems); List<DocumentMaterial> materialEntities = finishedProductShipmentConverter.toMaterialEntityList(shipmentItems);
for (Device device : deviceEntities) { for (DocumentMaterial material : materialEntities) {
device.setDocumentNo(entity.getId().intValue()); material.setDocumentNo(entity.getId().intValue());
device.setCreateDate(LocalDateTime.now()); material.setCreateDate(LocalDateTime.now());
device.setCreateUserId(SecurityUtils.getUserId()); material.setCreateUserId(SecurityUtils.getUserId());
device.setCreateUserName(SecurityUtils.getUserName()); material.setCreateUserName(SecurityUtils.getUserName());
device.setStatus(0); material.setStatus(0);
device.setCustomerId(SecurityUtils.getCustomerId()); material.setStoreNo(dto.storeNo());
device.setOutStatus(false);
} }
deviceMapper.insert(deviceEntities); documentMaterialMapper.insert(materialEntities);
} }
@Override @Override
@@ -151,30 +185,9 @@ public class FinishedProductShipmentServiceImpl extends ServiceImpl<DocumentMapp
this.baseMapper.deleteById(id); this.baseMapper.deleteById(id);
LambdaQueryWrapper<Device> itemWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<DocumentMaterial> itemWrapper = new LambdaQueryWrapper<>();
itemWrapper.eq(Device::getDocumentNo, id); itemWrapper.eq(DocumentMaterial::getDocumentNo, id);
deviceMapper.delete(itemWrapper); documentMaterialMapper.delete(itemWrapper);
}
@Override
public void deleteBatch(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
throw new BusinessException("production.finished_product_shipment.exception.ids_empty");
}
LambdaQueryWrapper<Document> wrapper = new LambdaQueryWrapper<>();
wrapper.in(Document::getId, ids);
wrapper.eq(Document::getFormStatus, FormStatus.APPROVE);
Long approvedCount = this.baseMapper.selectCount(wrapper);
if (approvedCount > 0) {
throw new BusinessException("production.finished_product_shipment.exception.cannot_delete_approved_batch");
}
this.baseMapper.deleteBatchIds(ids);
LambdaQueryWrapper<Device> itemWrapper = new LambdaQueryWrapper<>();
itemWrapper.in(Device::getDocumentNo, ids);
deviceMapper.delete(itemWrapper);
} }
@Override @Override
@@ -187,20 +200,40 @@ public class FinishedProductShipmentServiceImpl extends ServiceImpl<DocumentMapp
throw new BusinessException("production.finished_product_shipment.exception.already_approved"); throw new BusinessException("production.finished_product_shipment.exception.already_approved");
} }
LambdaQueryWrapper<Device> itemWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<DocumentMaterial> itemWrapper = new LambdaQueryWrapper<>();
itemWrapper.eq(Device::getDocumentNo, id); itemWrapper.eq(DocumentMaterial::getDocumentNo, id);
List<Device> items = deviceMapper.selectList(itemWrapper); List<DocumentMaterial> items = documentMaterialMapper.selectList(itemWrapper);
if (items == null || items.isEmpty()) { if (items == null || items.isEmpty()) {
throw new BusinessException("production.finished_product_shipment.exception.no_device_items"); throw new BusinessException("production.finished_product_shipment.exception.no_shipment_items");
} }
List<String> alreadyShippedSn = items.stream() Integer storeNo = entity.getStoreNo();
.filter(item -> item.getOutStatus() != null && item.getOutStatus()) List<String> partNumbers = items.stream()
.map(item -> "SN号" + item.getProductSn()) .map(DocumentMaterial::getPartNumber)
.distinct()
.toList(); .toList();
if (!alreadyShippedSn.isEmpty()) { LambdaQueryWrapper<Stock> stockWrapper = new LambdaQueryWrapper<>();
throw new BusinessException("production.finished_product_shipment.exception.sn_already_shipped"); stockWrapper.eq(Stock::getStoreNo, storeNo)
.in(Stock::getPartNumber, partNumbers);
List<Stock> stockList = stockMapper.selectList(stockWrapper);
Map<String, Stock> stockMap = stockList.stream()
.collect(Collectors.toMap(Stock::getPartNumber, s -> s));
for (DocumentMaterial item : items) {
Stock stock = stockMap.get(item.getPartNumber());
if (stock == null || stock.getProductCount() < item.getProductCount()) {
throw new BusinessException("production.finished_product_shipment.exception.insufficient_stock");
}
}
for (DocumentMaterial item : items) {
Stock stock = stockMap.get(item.getPartNumber());
stock.setProductCount(stock.getProductCount() - item.getProductCount());
stock.setUpdateDate(LocalDateTime.now());
stock.setUpdateUserId(SecurityUtils.getUserId());
stock.setUpdateUserName(SecurityUtils.getUserName());
stockMapper.updateById(stock);
} }
entity.setFormStatus(FormStatus.APPROVE); entity.setFormStatus(FormStatus.APPROVE);
@@ -208,15 +241,6 @@ public class FinishedProductShipmentServiceImpl extends ServiceImpl<DocumentMapp
entity.setUpdateUserId(SecurityUtils.getUserId()); entity.setUpdateUserId(SecurityUtils.getUserId());
entity.setUpdateUserName(SecurityUtils.getUserName()); entity.setUpdateUserName(SecurityUtils.getUserName());
this.baseMapper.updateById(entity); this.baseMapper.updateById(entity);
LambdaUpdateWrapper<Device> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Device::getDocumentNo, id)
.set(Device::getOutStatus, true)
.set(Device::getOutProductDate, LocalDateTime.now())
.set(Device::getUpdateDate, LocalDateTime.now())
.set(Device::getUpdateUserId, SecurityUtils.getUserId())
.set(Device::getUpdateUserName, SecurityUtils.getUserName());
deviceMapper.update(null, updateWrapper);
} }
@Override @Override
@@ -229,84 +253,84 @@ public class FinishedProductShipmentServiceImpl extends ServiceImpl<DocumentMapp
throw new BusinessException("production.finished_product_shipment.exception.not_approved"); throw new BusinessException("production.finished_product_shipment.exception.not_approved");
} }
LambdaQueryWrapper<Device> itemWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<DocumentMaterial> itemWrapper = new LambdaQueryWrapper<>();
itemWrapper.eq(Device::getDocumentNo, id); itemWrapper.eq(DocumentMaterial::getDocumentNo, id);
List<Device> items = deviceMapper.selectList(itemWrapper); List<DocumentMaterial> items = documentMaterialMapper.selectList(itemWrapper);
Integer storeNo = entity.getStoreNo();
List<String> partNumbers = items.stream()
.map(DocumentMaterial::getPartNumber)
.distinct()
.toList();
LambdaQueryWrapper<Stock> stockWrapper = new LambdaQueryWrapper<>();
stockWrapper.eq(Stock::getStoreNo, storeNo)
.in(Stock::getPartNumber, partNumbers);
List<Stock> stockList = stockMapper.selectList(stockWrapper);
Map<String, Stock> stockMap = stockList.stream()
.collect(Collectors.toMap(Stock::getPartNumber, s -> s));
for (DocumentMaterial item : items) {
Stock stock = stockMap.get(item.getPartNumber());
if (stock != null) {
stock.setProductCount(stock.getProductCount() + item.getProductCount());
stock.setUpdateDate(LocalDateTime.now());
stock.setUpdateUserId(SecurityUtils.getUserId());
stock.setUpdateUserName(SecurityUtils.getUserName());
stockMapper.updateById(stock);
} else {
Stock newStock = new Stock();
newStock.setPartNumber(item.getPartNumber());
newStock.setProductCount(item.getProductCount());
newStock.setStoreNo(storeNo);
newStock.setCreateDate(LocalDateTime.now());
newStock.setCreateUserId(SecurityUtils.getUserId());
newStock.setCreateUserName(SecurityUtils.getUserName());
newStock.setStatus(0);
stockMapper.insert(newStock);
}
}
entity.setFormStatus(FormStatus.NO_APPROVE); entity.setFormStatus(FormStatus.NO_APPROVE);
entity.setUpdateDate(LocalDateTime.now()); entity.setUpdateDate(LocalDateTime.now());
entity.setUpdateUserId(SecurityUtils.getUserId()); entity.setUpdateUserId(SecurityUtils.getUserId());
entity.setUpdateUserName(SecurityUtils.getUserName()); entity.setUpdateUserName(SecurityUtils.getUserName());
this.baseMapper.updateById(entity); this.baseMapper.updateById(entity);
LambdaUpdateWrapper<Device> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(Device::getDocumentNo, id)
.set(Device::getOutStatus, false)
.set(Device::getOutProductDate, (LocalDateTime) null)
.set(Device::getUpdateDate, LocalDateTime.now())
.set(Device::getUpdateUserId, SecurityUtils.getUserId())
.set(Device::getUpdateUserName, SecurityUtils.getUserName());
deviceMapper.update(null, updateWrapper);
} }
@Override @Override
public List<DeviceShipmentDto> getDetail(long id) { public List<FinishedProductShipmentItemDto> getDetail(long id) {
LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<DocumentMaterial> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Device::getDocumentNo, id); wrapper.eq(DocumentMaterial::getDocumentNo, id);
List<Device> items = deviceMapper.selectList(wrapper); List<DocumentMaterial> items = documentMaterialMapper.selectList(wrapper);
return finishedProductShipmentConverter.toDeviceShipmentDtoList(items);
}
@Override List<String> partNumbers = items.stream()
public List<DeviceShipmentDto> importItems(MultipartFile file) { .map(DocumentMaterial::getPartNumber)
if (file == null || file.isEmpty()) { .filter(StringUtils::hasText)
throw new BusinessException("production.finished_product_shipment.exception.file_empty"); .distinct()
.toList();
if (!partNumbers.isEmpty()) {
LambdaQueryWrapper<WarehouseItem> itemWrapper = new LambdaQueryWrapper<>();
itemWrapper.in(WarehouseItem::getPartNumber, partNumbers);
List<WarehouseItem> warehouseItems = warehouseItemMapper.selectList(itemWrapper);
Map<String, String> specsMap = warehouseItems.stream()
.collect(Collectors.toMap(WarehouseItem::getPartNumber, WarehouseItem::getProductSpecs, (a, b) -> a));
return items.stream()
.map(item -> {
FinishedProductShipmentItemDto dto = finishedProductShipmentConverter.toItemDto(item);
return new FinishedProductShipmentItemDto(
dto.id(),
dto.partNumber(),
specsMap.getOrDefault(dto.partNumber(), ""),
dto.productCount(),
dto.productMark()
);
})
.toList();
} }
try (Workbook workbook = WorkbookFactory.create(file.getInputStream())) { return finishedProductShipmentConverter.toItemDtoList(items);
Sheet sheet = workbook.getSheetAt(0);
List<DeviceShipmentDto> items = new ArrayList<>();
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
String productType = getCellValueAsString(row.getCell(0));
String productSn = getCellValueAsString(row.getCell(1));
String mac = getCellValueAsString(row.getCell(2));
String serialNum = getCellValueAsString(row.getCell(3));
String softVersion = getCellValueAsString(row.getCell(4));
String alVersion = getCellValueAsString(row.getCell(5));
String alNum = getCellValueAsString(row.getCell(6));
String alTxt = getCellValueAsString(row.getCell(7));
String manufacturingDate = getCellValueAsString(row.getCell(8));
String mark = getCellValueAsString(row.getCell(9));
if (!StringUtils.hasText(productSn)) continue;
DeviceShipmentDto item = new DeviceShipmentDto(
null, null, null, null, null, null, null, null,
null, productType, productSn, mac, serialNum, softVersion,
alVersion, alNum, null, null, mark, null, null, null, null, null
);
items.add(item);
}
return items;
} catch (IOException e) {
throw new BusinessException("production.finished_product_shipment.exception.import_failed");
}
} }
}
private String getCellValueAsString(Cell cell) {
if (cell == null) {
return "";
}
return switch (cell.getCellType()) {
case STRING -> cell.getStringCellValue().trim();
case NUMERIC -> String.valueOf((long) cell.getNumericCellValue());
case BOOLEAN -> String.valueOf(cell.getBooleanCellValue());
default -> "";
};
}
}

View File

@@ -85,6 +85,28 @@ production.production_plan.validate.demand_count.min=需求数量不能小于 1
production.production_plan.exception.must_no_complete=选中的行其中有不是未完成的状态 production.production_plan.exception.must_no_complete=选中的行其中有不是未完成的状态
production.production_plan.exception.more_than_one_warehouse=选中的行不能有多个仓库 production.production_plan.exception.more_than_one_warehouse=选中的行不能有多个仓库
production.finished_product_shipment.validate.form_code.not_null=单据编号不能为空
production.finished_product_shipment.validate.form_name.not_null=单据名称不能为空
production.finished_product_shipment.validate.store_no.not_null=仓库不能为空
production.finished_product_shipment.validate.store_name.not_null=仓库名称不能为空
production.finished_product_shipment.validate.out_stock_type.not_null=出库类型不能为空
production.finished_product_shipment.validate.shipment_items.not_null=出库明细不能为空
production.finished_product_shipment.validate.part_number.not_blank=物料编号不能为空
production.finished_product_shipment.validate.product_count.not_null=数量不能为空
production.finished_product_shipment.exception.not_found=成品出库单不存在
production.finished_product_shipment.exception.cannot_update_approved=已审核的成品出库单不能修改
production.finished_product_shipment.exception.cannot_delete_approved=已审核的成品出库单不能删除
production.finished_product_shipment.exception.cannot_delete_approved_batch=选中的数据中存在已审核的成品出库单,不能删除
production.finished_product_shipment.exception.ids_empty=请选择要删除的成品出库单
production.finished_product_shipment.exception.already_approved=成品出库单已经审核
production.finished_product_shipment.exception.not_approved=成品出库单不是已审核状态,不能反审
production.finished_product_shipment.exception.no_shipment_items=成品出库单没有明细
production.finished_product_shipment.exception.duplicate_part_number_in_request=导入数据中存在重复物料编号
production.finished_product_shipment.exception.part_number_not_found=系统中不存在的物料编号
production.finished_product_shipment.exception.insufficient_stock=库存不足
production.finished_product_shipment.exception.file_empty=请选择要上传的文件
production.finished_product_shipment.exception.import_failed=导入文件失败
sys.operationType.codeNotExists=编号不存在 sys.operationType.codeNotExists=编号不存在
# ==========>> 采购管理 # ==========>> 采购管理

View File

@@ -124,9 +124,10 @@ production.finished_product_shipment.validate.form_code.not_null=单据编号不
production.finished_product_shipment.validate.form_name.not_null=单据名称不能为空 production.finished_product_shipment.validate.form_name.not_null=单据名称不能为空
production.finished_product_shipment.validate.store_no.not_null=仓库不能为空 production.finished_product_shipment.validate.store_no.not_null=仓库不能为空
production.finished_product_shipment.validate.store_name.not_null=仓库名称不能为空 production.finished_product_shipment.validate.store_name.not_null=仓库名称不能为空
production.finished_product_shipment.validate.module_sn_items.not_null=成品明细不能为空 production.finished_product_shipment.validate.out_stock_type.not_null=出库类型不能为空
production.finished_product_shipment.validate.product_type.not_null=型号不能为空 production.finished_product_shipment.validate.shipment_items.not_null=出库明细不能为空
production.finished_product_shipment.validate.product_sn.not_null=SN号不能为空 production.finished_product_shipment.validate.part_number.not_blank=物料编号不能为空
production.finished_product_shipment.validate.product_count.not_null=数量不能为空
production.finished_product_shipment.exception.not_found=成品出库单不存在 production.finished_product_shipment.exception.not_found=成品出库单不存在
production.finished_product_shipment.exception.cannot_update_approved=已审核的成品出库单不能修改 production.finished_product_shipment.exception.cannot_update_approved=已审核的成品出库单不能修改
production.finished_product_shipment.exception.cannot_delete_approved=已审核的成品出库单不能删除 production.finished_product_shipment.exception.cannot_delete_approved=已审核的成品出库单不能删除
@@ -134,10 +135,10 @@ production.finished_product_shipment.exception.cannot_delete_approved_batch=选
production.finished_product_shipment.exception.ids_empty=请选择要删除的成品出库单 production.finished_product_shipment.exception.ids_empty=请选择要删除的成品出库单
production.finished_product_shipment.exception.already_approved=成品出库单已经审核 production.finished_product_shipment.exception.already_approved=成品出库单已经审核
production.finished_product_shipment.exception.not_approved=成品出库单不是已审核状态,不能反审 production.finished_product_shipment.exception.not_approved=成品出库单不是已审核状态,不能反审
production.finished_product_shipment.exception.no_module_sn_items=成品出库单没有明细 production.finished_product_shipment.exception.no_shipment_items=成品出库单没有明细
production.finished_product_shipment.exception.duplicate_sn_in_request=导入数据中存在重复SN production.finished_product_shipment.exception.duplicate_part_number_in_request=导入数据中存在重复物料编
production.finished_product_shipment.exception.sn_not_found=系统中不存在的SN号{0} production.finished_product_shipment.exception.part_number_not_found=系统中不存在的物料编号
production.finished_product_shipment.exception.sn_already_shipped=已经出货的SN号{0} production.finished_product_shipment.exception.insufficient_stock=库存不足
production.finished_product_shipment.exception.file_empty=请选择要上传的文件 production.finished_product_shipment.exception.file_empty=请选择要上传的文件
production.finished_product_shipment.exception.import_failed=导入文件失败 production.finished_product_shipment.exception.import_failed=导入文件失败
sale.sale_order.validate.form_code.not_null=单据编号不能为空 sale.sale_order.validate.form_code.not_null=单据编号不能为空