|
|
@@ -0,0 +1,529 @@
|
|
|
+package com.trade.purchase.stock.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.util.IdUtil;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.trade.purchase.stock.dto.BusinessStockInventoryCheckDTO;
|
|
|
+import com.trade.purchase.stock.entity.BusinessStockInventoryCheck;
|
|
|
+import com.trade.purchase.stock.entity.BusinessStockInventoryCheckDetail;
|
|
|
+import com.trade.purchase.stock.entity.BusinessStockInventory;
|
|
|
+import com.trade.purchase.stock.excel.BusinessStockInventoryCheckExcel;
|
|
|
+import com.trade.purchase.stock.mapper.BusinessStockInventoryCheckDetailMapper;
|
|
|
+import com.trade.purchase.stock.mapper.BusinessStockInventoryCheckMapper;
|
|
|
+import com.trade.purchase.stock.mapper.BusinessStockInventoryMapper;
|
|
|
+import lombok.Data;
|
|
|
+import com.trade.purchase.stock.service.IBusinessStockInventoryCheckService;
|
|
|
+import com.trade.purchase.stock.utils.BillCodeUtil;
|
|
|
+import com.trade.purchase.stock.vo.BusinessStockInventoryCheckVO;
|
|
|
+import lombok.AllArgsConstructor;
|
|
|
+import org.springblade.core.excel.util.ExcelUtil;
|
|
|
+import org.springblade.core.mp.support.Condition;
|
|
|
+import org.springblade.core.mp.support.Query;
|
|
|
+import org.springblade.core.secure.BladeUser;
|
|
|
+import org.springblade.core.secure.utils.AuthUtil;
|
|
|
+import org.springblade.core.tool.api.R;
|
|
|
+import org.springblade.core.tool.api.ResultCode;
|
|
|
+import org.springblade.core.tool.utils.BeanUtil;
|
|
|
+import org.springblade.core.tool.utils.CollectionUtil;
|
|
|
+import org.springblade.core.tool.utils.StringUtil;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.Optional;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 库存盘点
|
|
|
+ *
|
|
|
+ * @author Rain
|
|
|
+ */
|
|
|
+@Service
|
|
|
+@AllArgsConstructor
|
|
|
+public class BusinessStockInventoryCheckServiceImpl extends ServiceImpl<BusinessStockInventoryCheckMapper, BusinessStockInventoryCheck> implements IBusinessStockInventoryCheckService {
|
|
|
+
|
|
|
+ private final BusinessStockInventoryCheckMapper checkMapper;
|
|
|
+ private final BusinessStockInventoryCheckDetailMapper detailMapper;
|
|
|
+ private final BusinessStockInventoryMapper inventoryMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IPage<BusinessStockInventoryCheckDTO> page(BusinessStockInventoryCheckVO vo, Query query) {
|
|
|
+ if (Objects.isNull(vo)) {
|
|
|
+ vo = new BusinessStockInventoryCheckVO();
|
|
|
+ }
|
|
|
+ BladeUser user = AuthUtil.getUser();
|
|
|
+ LambdaQueryWrapper<BusinessStockInventoryCheck> wrapper = buildListWrapper(user.getTenantId(), vo);
|
|
|
+ IPage<BusinessStockInventoryCheck> entityPage = checkMapper.selectPage(Condition.getPage(query), wrapper);
|
|
|
+ return entityPage.convert(row -> BeanUtil.copy(row, BusinessStockInventoryCheckDTO.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void export(BusinessStockInventoryCheckVO vo, HttpServletResponse response) {
|
|
|
+ if (Objects.isNull(vo)) {
|
|
|
+ vo = new BusinessStockInventoryCheckVO();
|
|
|
+ }
|
|
|
+ BladeUser user = AuthUtil.getUser();
|
|
|
+ List<BusinessStockInventoryCheck> list = checkMapper.selectList(buildListWrapper(user.getTenantId(), vo));
|
|
|
+ List<BusinessStockInventoryCheckExcel> excelRows = new ArrayList<>();
|
|
|
+ if (CollectionUtil.isNotEmpty(list)) {
|
|
|
+ List<Long> checkIdList = list.stream().map(BusinessStockInventoryCheck::getId).filter(Objects::nonNull).collect(Collectors.toList());
|
|
|
+ Map<Long, List<BusinessStockInventoryCheckDetail>> detailMap = new LinkedHashMap<>();
|
|
|
+ if (CollectionUtil.isNotEmpty(checkIdList)) {
|
|
|
+ List<BusinessStockInventoryCheckDetail> details = detailMapper.selectList(new LambdaQueryWrapper<BusinessStockInventoryCheckDetail>()
|
|
|
+ .in(BusinessStockInventoryCheckDetail::getCheckId, checkIdList)
|
|
|
+ .eq(BusinessStockInventoryCheckDetail::getTenantId, user.getTenantId())
|
|
|
+ .eq(BusinessStockInventoryCheckDetail::getIsDeleted, 0)
|
|
|
+ .orderByAsc(BusinessStockInventoryCheckDetail::getSort)
|
|
|
+ .orderByAsc(BusinessStockInventoryCheckDetail::getId));
|
|
|
+ detailMap = details.stream().collect(Collectors.groupingBy(BusinessStockInventoryCheckDetail::getCheckId, LinkedHashMap::new, Collectors.toList()));
|
|
|
+ }
|
|
|
+ for (BusinessStockInventoryCheck row : list) {
|
|
|
+ List<BusinessStockInventoryCheckDetail> detailList = detailMap.get(row.getId());
|
|
|
+ if (CollectionUtil.isEmpty(detailList)) {
|
|
|
+ excelRows.add(buildExportRow(row, null));
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ for (BusinessStockInventoryCheckDetail detail : detailList) {
|
|
|
+ excelRows.add(buildExportRow(row, detail));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ExcelUtil.export(response, "库存盘点", "库存盘点", excelRows, BusinessStockInventoryCheckExcel.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ private BusinessStockInventoryCheckExcel buildExportRow(BusinessStockInventoryCheck head, BusinessStockInventoryCheckDetail detail) {
|
|
|
+ BusinessStockInventoryCheckExcel ex = new BusinessStockInventoryCheckExcel();
|
|
|
+ ex.setFormNumber(head.getFormNumber());
|
|
|
+ ex.setWarehouse(head.getWarehouse());
|
|
|
+ ex.setCheckDate(head.getCheckDate());
|
|
|
+ ex.setStatusLabel(resolveStatusLabel(head.getStatus()));
|
|
|
+ ex.setTotalVarianceQty(head.getTotalVarianceQty());
|
|
|
+ ex.setTotalVarianceAmount(head.getTotalVarianceAmount());
|
|
|
+ ex.setRemarks(head.getRemarks());
|
|
|
+ ex.setCreator(head.getCreator());
|
|
|
+ ex.setCreatedAt(head.getCreatedAt());
|
|
|
+ if (Objects.nonNull(detail)) {
|
|
|
+ ex.setMaterialCode(detail.getMaterialCode());
|
|
|
+ ex.setMaterialName(detail.getMaterialName());
|
|
|
+ ex.setSupplierName(detail.getSupplierName());
|
|
|
+ ex.setStorageArea(detail.getStorageArea());
|
|
|
+ ex.setBookQuantity(detail.getBookQuantity());
|
|
|
+ ex.setActualQuantity(detail.getActualQuantity());
|
|
|
+ ex.setVarianceQuantity(detail.getVarianceQuantity());
|
|
|
+ ex.setVarianceAmount(detail.getVarianceAmount());
|
|
|
+ ex.setNote(detail.getNote());
|
|
|
+ }
|
|
|
+ return ex;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public BusinessStockInventoryCheckDTO detail(Long id) {
|
|
|
+ if (Objects.isNull(id)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ BladeUser user = AuthUtil.getUser();
|
|
|
+ BusinessStockInventoryCheck head = checkMapper.selectOne(new LambdaQueryWrapper<BusinessStockInventoryCheck>()
|
|
|
+ .eq(BusinessStockInventoryCheck::getId, id)
|
|
|
+ .eq(BusinessStockInventoryCheck::getTenantId, user.getTenantId())
|
|
|
+ .eq(BusinessStockInventoryCheck::getIsDeleted, 0)
|
|
|
+ .last("limit 1"));
|
|
|
+ if (Objects.isNull(head)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ BusinessStockInventoryCheckDTO dto = BeanUtil.copy(head, BusinessStockInventoryCheckDTO.class);
|
|
|
+ if (Objects.isNull(dto)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ List<BusinessStockInventoryCheckDetail> details = detailMapper.selectList(new LambdaQueryWrapper<BusinessStockInventoryCheckDetail>()
|
|
|
+ .eq(BusinessStockInventoryCheckDetail::getCheckId, id)
|
|
|
+ .eq(BusinessStockInventoryCheckDetail::getIsDeleted, 0)
|
|
|
+ .orderByAsc(BusinessStockInventoryCheckDetail::getSort)
|
|
|
+ .orderByAsc(BusinessStockInventoryCheckDetail::getId));
|
|
|
+ dto.setDetailList(details);
|
|
|
+ return dto;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public R save(BusinessStockInventoryCheckVO vo) {
|
|
|
+ return persist(vo, 0, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public R submit(BusinessStockInventoryCheckVO vo) {
|
|
|
+ return persist(vo, 1, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public R revoke(BusinessStockInventoryCheckVO vo) {
|
|
|
+ if (Objects.isNull(vo) || Objects.isNull(vo.getId())) {
|
|
|
+ return R.fail("参数错误");
|
|
|
+ }
|
|
|
+ BladeUser user = AuthUtil.getUser();
|
|
|
+ Date now = new Date();
|
|
|
+ BusinessStockInventoryCheck head = checkMapper.selectOne(new LambdaQueryWrapper<BusinessStockInventoryCheck>()
|
|
|
+ .eq(BusinessStockInventoryCheck::getId, vo.getId())
|
|
|
+ .eq(BusinessStockInventoryCheck::getTenantId, user.getTenantId())
|
|
|
+ .eq(BusinessStockInventoryCheck::getIsDeleted, 0)
|
|
|
+ .last("limit 1"));
|
|
|
+ if (Objects.isNull(head)) {
|
|
|
+ return R.fail("单据不存在");
|
|
|
+ }
|
|
|
+ if (!Objects.equals(head.getStatus(), 1)) {
|
|
|
+ return R.fail("仅已提交的单据可撤销为保存");
|
|
|
+ }
|
|
|
+ Date submitTime = Optional.ofNullable(head.getUpdatedAt()).orElse(head.getCreatedAt());
|
|
|
+ if (Objects.isNull(submitTime)) {
|
|
|
+ return R.fail("提交时间异常,无法撤销");
|
|
|
+ }
|
|
|
+ long nowMillis = now.getTime();
|
|
|
+ long submitMillis = submitTime.getTime();
|
|
|
+ if (nowMillis - submitMillis > 86400000L) {
|
|
|
+ return R.fail("提交超过一天,不允许撤销");
|
|
|
+ }
|
|
|
+ BusinessStockInventoryCheck update = new BusinessStockInventoryCheck();
|
|
|
+ update.setId(head.getId());
|
|
|
+ update.setStatus(0);
|
|
|
+ update.setUpdateUserId(user.getUserId());
|
|
|
+ update.setUpdatedAt(now);
|
|
|
+ checkMapper.updateById(update);
|
|
|
+ return R.success("撤销成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public R remove(BusinessStockInventoryCheckVO vo) {
|
|
|
+ if (Objects.isNull(vo) || Objects.isNull(vo.getId())) {
|
|
|
+ return R.fail("参数错误");
|
|
|
+ }
|
|
|
+ BladeUser user = AuthUtil.getUser();
|
|
|
+ Date now = new Date();
|
|
|
+ BusinessStockInventoryCheck head = checkMapper.selectOne(new LambdaQueryWrapper<BusinessStockInventoryCheck>()
|
|
|
+ .eq(BusinessStockInventoryCheck::getId, vo.getId())
|
|
|
+ .eq(BusinessStockInventoryCheck::getTenantId, user.getTenantId())
|
|
|
+ .eq(BusinessStockInventoryCheck::getIsDeleted, 0)
|
|
|
+ .last("limit 1"));
|
|
|
+ if (Objects.isNull(head)) {
|
|
|
+ return R.fail("单据不存在");
|
|
|
+ }
|
|
|
+ if (!Objects.equals(head.getStatus(), 0)) {
|
|
|
+ return R.fail("仅保存状态单据允许删除");
|
|
|
+ }
|
|
|
+ BusinessStockInventoryCheck update = new BusinessStockInventoryCheck();
|
|
|
+ update.setId(head.getId());
|
|
|
+ update.setIsDeleted(1);
|
|
|
+ update.setUpdateUserId(user.getUserId());
|
|
|
+ update.setUpdatedAt(now);
|
|
|
+ checkMapper.updateById(update);
|
|
|
+
|
|
|
+ BusinessStockInventoryCheckDetail removeDetail = new BusinessStockInventoryCheckDetail();
|
|
|
+ removeDetail.setIsDeleted(1);
|
|
|
+ removeDetail.setUpdatedTime(now);
|
|
|
+ detailMapper.update(removeDetail, new LambdaQueryWrapper<BusinessStockInventoryCheckDetail>()
|
|
|
+ .eq(BusinessStockInventoryCheckDetail::getCheckId, head.getId())
|
|
|
+ .eq(BusinessStockInventoryCheckDetail::getTenantId, user.getTenantId())
|
|
|
+ .eq(BusinessStockInventoryCheckDetail::getIsDeleted, 0));
|
|
|
+ return R.success("删除成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ private R persist(BusinessStockInventoryCheckVO vo, int targetStatus, boolean requireDetails) {
|
|
|
+ if (Objects.isNull(vo)) {
|
|
|
+ return R.fail("参数错误");
|
|
|
+ }
|
|
|
+ BladeUser user = AuthUtil.getUser();
|
|
|
+ Date now = new Date();
|
|
|
+ List<BusinessStockInventoryCheckDetail> detailList = Optional.ofNullable(vo.getDetailList()).orElseGet(ArrayList::new);
|
|
|
+ List<BusinessStockInventoryCheckDetail> delDetailList = Optional.ofNullable(vo.getDelDetailList()).orElseGet(ArrayList::new);
|
|
|
+
|
|
|
+ if (requireDetails && CollectionUtil.isEmpty(detailList)) {
|
|
|
+ return R.fail("请添加盘点明细");
|
|
|
+ }
|
|
|
+
|
|
|
+ BusinessStockInventoryCheck head = BeanUtil.copy(vo, BusinessStockInventoryCheck.class);
|
|
|
+ if (Objects.isNull(head)) {
|
|
|
+ return R.fail("参数错误");
|
|
|
+ }
|
|
|
+ head.setStatus(targetStatus);
|
|
|
+ if (Objects.isNull(head.getCheckDate())) {
|
|
|
+ head.setCheckDate(now);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (BusinessStockInventoryCheckDetail d : detailList) {
|
|
|
+ fillDetailLine(d);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Objects.isNull(head.getId())) {
|
|
|
+ applyHeaderTotals(head, detailList);
|
|
|
+ head.setCreator(user.getUserName());
|
|
|
+ head.setCreateUserId(user.getUserId());
|
|
|
+ head.setCreatedAt(now);
|
|
|
+ head.setFormNumber(BillCodeUtil.getBillCodeByType(user.getTenantId(), "PD"));
|
|
|
+ head.setTenantId(user.getTenantId());
|
|
|
+ head.setIsDeleted(0);
|
|
|
+ head.setCreatedDate(now);
|
|
|
+ checkMapper.insert(head);
|
|
|
+ } else {
|
|
|
+ BusinessStockInventoryCheck existed = checkMapper.selectOne(new LambdaQueryWrapper<BusinessStockInventoryCheck>()
|
|
|
+ .eq(BusinessStockInventoryCheck::getId, head.getId())
|
|
|
+ .eq(BusinessStockInventoryCheck::getTenantId, user.getTenantId())
|
|
|
+ .eq(BusinessStockInventoryCheck::getIsDeleted, 0)
|
|
|
+ .last("limit 1"));
|
|
|
+ if (Objects.isNull(existed)) {
|
|
|
+ return R.fail("单据不存在");
|
|
|
+ }
|
|
|
+ if (Objects.equals(existed.getStatus(), 1) && targetStatus == 0) {
|
|
|
+ return R.fail("已提交单据请使用撤销接口回退");
|
|
|
+ }
|
|
|
+ if (Objects.equals(existed.getStatus(), 1) && targetStatus == 1) {
|
|
|
+ return R.fail("单据已提交,请先撤销后再修改");
|
|
|
+ }
|
|
|
+ mergeCheckHead(existed, head);
|
|
|
+ existed.setStatus(targetStatus);
|
|
|
+ applyHeaderTotals(existed, detailList);
|
|
|
+ existed.setUpdateUserId(user.getUserId());
|
|
|
+ existed.setUpdatedAt(now);
|
|
|
+ checkMapper.updateById(existed);
|
|
|
+ head = existed;
|
|
|
+ }
|
|
|
+
|
|
|
+ Long checkId = head.getId();
|
|
|
+ for (BusinessStockInventoryCheckDetail d : detailList) {
|
|
|
+ d.setCheckId(checkId);
|
|
|
+ d.setTenantId(user.getTenantId());
|
|
|
+ d.setIsDeleted(0);
|
|
|
+ d.setUpdatedTime(now);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Long> delIds = delDetailList.stream().map(BusinessStockInventoryCheckDetail::getId).filter(Objects::nonNull).collect(Collectors.toList());
|
|
|
+ if (!delIds.isEmpty()) {
|
|
|
+ BusinessStockInventoryCheckDetail deleteItem = new BusinessStockInventoryCheckDetail();
|
|
|
+ deleteItem.setIsDeleted(1);
|
|
|
+ deleteItem.setUpdatedTime(now);
|
|
|
+ detailMapper.update(deleteItem, new LambdaQueryWrapper<BusinessStockInventoryCheckDetail>()
|
|
|
+ .eq(BusinessStockInventoryCheckDetail::getCheckId, checkId)
|
|
|
+ .in(BusinessStockInventoryCheckDetail::getId, delIds));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Objects.isNull(vo.getId())) {
|
|
|
+ for (BusinessStockInventoryCheckDetail d : detailList) {
|
|
|
+ if (Objects.isNull(d.getId())) {
|
|
|
+ d.setId(IdUtil.getSnowflakeNextId());
|
|
|
+ d.setCreatedTime(now);
|
|
|
+ }
|
|
|
+ detailMapper.insert(d);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ List<BusinessStockInventoryCheckDetail> toUpdate = detailList.stream().filter(d -> Objects.nonNull(d.getId())).collect(Collectors.toList());
|
|
|
+ List<BusinessStockInventoryCheckDetail> toInsert = detailList.stream().filter(d -> Objects.isNull(d.getId())).collect(Collectors.toList());
|
|
|
+ for (BusinessStockInventoryCheckDetail d : toUpdate) {
|
|
|
+ detailMapper.updateById(d);
|
|
|
+ }
|
|
|
+ for (BusinessStockInventoryCheckDetail d : toInsert) {
|
|
|
+ d.setId(IdUtil.getSnowflakeNextId());
|
|
|
+ d.setCreatedTime(now);
|
|
|
+ detailMapper.insert(d);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (targetStatus == 1) {
|
|
|
+ applyInventoryByCheckResult(checkId, user.getTenantId(), now);
|
|
|
+ }
|
|
|
+
|
|
|
+ String msg = targetStatus == 1 ? "提交成功" : "保存成功";
|
|
|
+ return R.data(ResultCode.SUCCESS.getCode(), checkId, msg);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 提交盘点单后,按差异量调整库存数量与金额。
|
|
|
+ */
|
|
|
+ private void applyInventoryByCheckResult(Long checkId, String tenantId, Date now) {
|
|
|
+ List<BusinessStockInventoryCheckDetail> details = detailMapper.selectList(new LambdaQueryWrapper<BusinessStockInventoryCheckDetail>()
|
|
|
+ .eq(BusinessStockInventoryCheckDetail::getCheckId, checkId)
|
|
|
+ .eq(BusinessStockInventoryCheckDetail::getTenantId, tenantId)
|
|
|
+ .eq(BusinessStockInventoryCheckDetail::getIsDeleted, 0));
|
|
|
+ if (CollectionUtil.isEmpty(details)) {
|
|
|
+ throw new RuntimeException("盘点明细不存在,无法提交");
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, InventoryAdjustLine> adjustLineMap = new LinkedHashMap<>();
|
|
|
+ for (BusinessStockInventoryCheckDetail d : details) {
|
|
|
+ if (Objects.isNull(d.getMaterialId())) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ int deltaQty = Optional.ofNullable(d.getVarianceQuantity()).orElse(0);
|
|
|
+ BigDecimal deltaAmount = Optional.ofNullable(d.getVarianceAmount()).orElse(BigDecimal.ZERO);
|
|
|
+ if (deltaQty == 0 && deltaAmount.compareTo(BigDecimal.ZERO) == 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ String key = buildInventoryAdjustKey(d.getMaterialId(), d.getWarehouseId(), d.getStorageAreaId(), d.getSupplierId());
|
|
|
+ InventoryAdjustLine line = adjustLineMap.get(key);
|
|
|
+ if (Objects.isNull(line)) {
|
|
|
+ line = new InventoryAdjustLine();
|
|
|
+ line.setKey(key);
|
|
|
+ line.setMaterialId(d.getMaterialId());
|
|
|
+ line.setWarehouseId(d.getWarehouseId());
|
|
|
+ line.setStorageAreaId(d.getStorageAreaId());
|
|
|
+ line.setSupplierId(d.getSupplierId());
|
|
|
+ line.setMaterialName(d.getMaterialName());
|
|
|
+ line.setDeltaQty(0);
|
|
|
+ line.setDeltaAmount(BigDecimal.ZERO);
|
|
|
+ adjustLineMap.put(key, line);
|
|
|
+ }
|
|
|
+ line.setDeltaQty(line.getDeltaQty() + deltaQty);
|
|
|
+ line.setDeltaAmount(line.getDeltaAmount().add(deltaAmount));
|
|
|
+ }
|
|
|
+ if (CollectionUtil.isEmpty(adjustLineMap)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Long> materialIds = adjustLineMap.values().stream()
|
|
|
+ .map(InventoryAdjustLine::getMaterialId)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ List<BusinessStockInventory> inventoryList = inventoryMapper.selectList(new LambdaQueryWrapper<BusinessStockInventory>()
|
|
|
+ .eq(BusinessStockInventory::getTenantId, tenantId)
|
|
|
+ .eq(BusinessStockInventory::getIsActive, 1)
|
|
|
+ .in(BusinessStockInventory::getMaterialId, materialIds));
|
|
|
+ Map<String, BusinessStockInventory> inventoryMap = inventoryList.stream()
|
|
|
+ .collect(Collectors.toMap(this::buildInventoryAdjustKey, inv -> inv, (a, b) -> a));
|
|
|
+
|
|
|
+ for (InventoryAdjustLine line : adjustLineMap.values()) {
|
|
|
+ BusinessStockInventory inventory = inventoryMap.get(line.getKey());
|
|
|
+ if (Objects.isNull(inventory)) {
|
|
|
+ throw new RuntimeException("商品" + line.getMaterialName() + "未找到对应库存,无法提交");
|
|
|
+ }
|
|
|
+ int stockBefore = Optional.ofNullable(inventory.getCurrentStock()).orElse(0);
|
|
|
+ BigDecimal amountBefore = Optional.ofNullable(inventory.getTotalValue()).orElse(BigDecimal.ZERO);
|
|
|
+ int stockAfter = stockBefore + line.getDeltaQty();
|
|
|
+ if (stockAfter < 0) {
|
|
|
+ throw new RuntimeException("商品" + line.getMaterialName() + "盘点后库存不能小于0");
|
|
|
+ }
|
|
|
+ BigDecimal amountAfter = amountBefore.add(line.getDeltaAmount());
|
|
|
+ if (amountAfter.compareTo(BigDecimal.ZERO) < 0) {
|
|
|
+ amountAfter = BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ BusinessStockInventory update = new BusinessStockInventory();
|
|
|
+ update.setId(inventory.getId());
|
|
|
+ update.setCurrentStock(stockAfter);
|
|
|
+ update.setTotalValue(amountAfter);
|
|
|
+ update.setLastUpdatedDate(now);
|
|
|
+ if (stockAfter > 0) {
|
|
|
+ update.setAverageCostPrice(amountAfter.divide(BigDecimal.valueOf(stockAfter), 2, RoundingMode.DOWN));
|
|
|
+ } else {
|
|
|
+ update.setAverageCostPrice(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+ inventoryMapper.updateById(update);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildInventoryAdjustKey(BusinessStockInventory inventory) {
|
|
|
+ return buildInventoryAdjustKey(inventory.getMaterialId(), inventory.getWarehouseId(), inventory.getStorageAreaId(), inventory.getSupplierId());
|
|
|
+ }
|
|
|
+
|
|
|
+ private String buildInventoryAdjustKey(Long materialId, Long warehouseId, Long storageAreaId, Long supplierId) {
|
|
|
+ return String.valueOf(materialId) + "_" + String.valueOf(warehouseId) + "_" + String.valueOf(storageAreaId) + "_" + String.valueOf(supplierId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Data
|
|
|
+ private static final class InventoryAdjustLine {
|
|
|
+ private String key;
|
|
|
+ private Long materialId;
|
|
|
+ private Long warehouseId;
|
|
|
+ private Long storageAreaId;
|
|
|
+ private Long supplierId;
|
|
|
+ private String materialName;
|
|
|
+ private Integer deltaQty;
|
|
|
+ private BigDecimal deltaAmount;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void fillDetailLine(BusinessStockInventoryCheckDetail d) {
|
|
|
+ Integer book = d.getBookQuantity();
|
|
|
+ Integer actual = d.getActualQuantity();
|
|
|
+ if (book != null && actual != null) {
|
|
|
+ d.setVarianceQuantity(actual - book);
|
|
|
+ }
|
|
|
+ BigDecimal bookAmt = d.getBookAmount();
|
|
|
+ BigDecimal actAmt = d.getActualAmount();
|
|
|
+ if (bookAmt != null && actAmt != null) {
|
|
|
+ d.setVarianceAmount(actAmt.subtract(bookAmt));
|
|
|
+ } else if (d.getVarianceQuantity() != null && d.getAverageCostPrice() != null) {
|
|
|
+ d.setVarianceAmount(d.getAverageCostPrice().multiply(BigDecimal.valueOf(d.getVarianceQuantity())));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void applyHeaderTotals(BusinessStockInventoryCheck head, List<BusinessStockInventoryCheckDetail> details) {
|
|
|
+ if (CollectionUtil.isEmpty(details)) {
|
|
|
+ head.setTotalVarianceQty(0);
|
|
|
+ head.setTotalVarianceAmount(BigDecimal.ZERO);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ int sumQty = 0;
|
|
|
+ BigDecimal sumAmt = BigDecimal.ZERO;
|
|
|
+ for (BusinessStockInventoryCheckDetail d : details) {
|
|
|
+ if (d.getVarianceQuantity() != null) {
|
|
|
+ sumQty += d.getVarianceQuantity();
|
|
|
+ }
|
|
|
+ if (d.getVarianceAmount() != null) {
|
|
|
+ sumAmt = sumAmt.add(d.getVarianceAmount());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ head.setTotalVarianceQty(sumQty);
|
|
|
+ head.setTotalVarianceAmount(sumAmt);
|
|
|
+ }
|
|
|
+
|
|
|
+ private LambdaQueryWrapper<BusinessStockInventoryCheck> buildListWrapper(String tenantId, BusinessStockInventoryCheckVO vo) {
|
|
|
+ LambdaQueryWrapper<BusinessStockInventoryCheck> w = new LambdaQueryWrapper<>();
|
|
|
+ w.eq(BusinessStockInventoryCheck::getTenantId, tenantId);
|
|
|
+ w.eq(BusinessStockInventoryCheck::getIsDeleted, 0);
|
|
|
+ w.like(StringUtil.isNotBlank(vo.getFormNumber()), BusinessStockInventoryCheck::getFormNumber, vo.getFormNumber());
|
|
|
+ w.eq(Objects.nonNull(vo.getWarehouseId()), BusinessStockInventoryCheck::getWarehouseId, vo.getWarehouseId());
|
|
|
+ w.eq(Objects.nonNull(vo.getStatus()), BusinessStockInventoryCheck::getStatus, vo.getStatus());
|
|
|
+ w.ge(StringUtil.isNotBlank(vo.getCheckDateStart()), BusinessStockInventoryCheck::getCheckDate, vo.getCheckDateStart());
|
|
|
+ w.le(StringUtil.isNotBlank(vo.getCheckDateEnd()), BusinessStockInventoryCheck::getCheckDate, vo.getCheckDateEnd());
|
|
|
+ w.orderByDesc(BusinessStockInventoryCheck::getCreatedAt);
|
|
|
+ return w;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String resolveStatusLabel(Integer status) {
|
|
|
+ if (Objects.equals(status, 1)) {
|
|
|
+ return "已提交";
|
|
|
+ }
|
|
|
+ return "保存";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新时合并字段,避免前端未传字段被置空。
|
|
|
+ */
|
|
|
+ private static void mergeCheckHead(BusinessStockInventoryCheck target, BusinessStockInventoryCheck patch) {
|
|
|
+ if (Objects.isNull(patch)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (Objects.nonNull(patch.getWarehouseId())) {
|
|
|
+ target.setWarehouseId(patch.getWarehouseId());
|
|
|
+ }
|
|
|
+ if (Objects.nonNull(patch.getWarehouse())) {
|
|
|
+ target.setWarehouse(patch.getWarehouse());
|
|
|
+ }
|
|
|
+ if (Objects.nonNull(patch.getCheckDate())) {
|
|
|
+ target.setCheckDate(patch.getCheckDate());
|
|
|
+ }
|
|
|
+ if (Objects.nonNull(patch.getRemarks())) {
|
|
|
+ target.setRemarks(patch.getRemarks());
|
|
|
+ }
|
|
|
+ if (Objects.nonNull(patch.getCreatedDate())) {
|
|
|
+ target.setCreatedDate(patch.getCreatedDate());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|