feat(prd): transfer finished goods and repost posted 261 rows

Add 102+101 shop-order transfer for 101 completions, allow posting-date driven reverse/repost, and reset posted consume qty through 262 then 261.
This commit is contained in:
tsiji 2026-09-09 17:39:11 +08:00
parent 778142b570
commit 25b3c216b0
9 changed files with 399 additions and 11 deletions

View File

@ -22,4 +22,5 @@ public class CompleteCorrectionRequestDTO {
private String storageLocation;
private LocalDate postingDate;
private String reason;
private Boolean forceSap;
}

View File

@ -0,0 +1,12 @@
package com.foreverwin.iep.prd.dto;
import lombok.Data;
import java.time.LocalDate;
@Data
public class CompleteTransferRequestDTO {
private String transactionSid;
private String targetShopOrder;
private LocalDate postingDate;
}

View File

@ -11,6 +11,7 @@ import com.foreverwin.iep.prd.dto.ZLotMaterialTransactionResponseDTO;
import com.foreverwin.iep.prd.dto.ZLotMaterialTransactionResponseExportDTO;
import com.foreverwin.iep.prd.dto.ZLotMaterialTransactionResponseExportDetailDTO;
import com.foreverwin.iep.prd.dto.CompleteCorrectionRequestDTO;
import com.foreverwin.iep.prd.dto.CompleteTransferRequestDTO;
import com.foreverwin.iep.prd.entity.ZLotMaterialCompleteTransaction;
import com.foreverwin.iep.prd.service.ZLotMaterialCompleteCorrectionService;
import com.foreverwin.iep.prd.service.ZLotMaterialCompleteTransactionService;
@ -109,6 +110,15 @@ public class ZLotMaterialCompleteTransactionController {
return R.ok(zLotMaterialCompleteCorrectionService.repost101(sid));
}
@ApiOperation(value = "成品调整至其他工单", notes = "S走102+101,N/E只转MES流水")
@SysLog("成品调整至其他工单")
@PostMapping("/transfer-shop-order")
@PreAuthorize("@pms.hasPermission('bg_finished_warehousing_off_btn')")
public R transferShopOrder(@RequestBody List<CompleteTransferRequestDTO> requests) {
zLotMaterialCompleteCorrectionService.transferShopOrder(requests);
return R.ok();
}
/**
* 通过id查询完工入库事务表
*

View File

@ -99,7 +99,7 @@ public class ZLotMaterialConsumptionTransactionController {
return R.ok(result);
}
@ApiOperation(value = "待过账或失败的261重新过账", notes = "可改数量,成功后按差额回写WMS库存")
@ApiOperation(value = "261重新过账", notes = "N/E直接261;S先262再按目标数量261,目标为0则只262并改为N")
@SysLog("物料消耗重新过账261")
@PostMapping("/repost-261")
public R repost261(@RequestBody List<ConsumptionRepostRequestDTO> requests) {

View File

@ -2,8 +2,12 @@ package com.foreverwin.iep.prd.service;
import com.foreverwin.iep.prd.dto.CompleteCorrectionRequestDTO;
import com.foreverwin.iep.prd.dto.CompleteCorrectionResultDTO;
import com.foreverwin.iep.prd.dto.CompleteTransferRequestDTO;
import java.util.List;
public interface ZLotMaterialCompleteCorrectionService {
CompleteCorrectionResultDTO correct(CompleteCorrectionRequestDTO request);
CompleteCorrectionResultDTO repost101(String sid);
void transferShopOrder(List<CompleteTransferRequestDTO> requests);
}

View File

@ -6,14 +6,19 @@ import com.foreverwin.iep.integration.feign.RemoteGetSapBatchNoService;
import com.foreverwin.iep.integration.feign.RemoteInvokeService;
import com.foreverwin.iep.prd.dto.CompleteCorrectionRequestDTO;
import com.foreverwin.iep.prd.dto.CompleteCorrectionResultDTO;
import com.foreverwin.iep.prd.dto.CompleteTransferRequestDTO;
import com.foreverwin.iep.prd.entity.Item;
import com.foreverwin.iep.prd.entity.ShopOrder;
import com.foreverwin.iep.prd.entity.ZLotMaterialCompleteTransaction;
import com.foreverwin.iep.prd.entity.ZLotMaterialCompleteTransactionOff;
import com.foreverwin.iep.prd.service.ItemService;
import com.foreverwin.iep.prd.service.ShopOrderService;
import com.foreverwin.iep.prd.service.ZLotMaterialCompleteCorrectionService;
import com.foreverwin.iep.prd.service.ZLotMaterialCompleteTransactionOffService;
import com.foreverwin.iep.prd.service.ZLotMaterialCompleteTransactionService;
import com.foreverwin.iep.wip.domain.ShopOrderReleaseItemInfo;
import com.foreverwin.iep.wip.entity.LotBasis;
import com.foreverwin.iep.wip.service.LotBasisService;
import com.foreverwin.iep.wip.service.NlvLotStartService;
import com.foreverwin.mom.common.core.constant.SecurityConstants;
import com.foreverwin.mom.common.core.exception.BusinessException;
@ -27,6 +32,7 @@ import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
@ -43,6 +49,8 @@ public class ZLotMaterialCompleteCorrectionServiceImpl implements ZLotMaterialCo
private final ItemService itemService;
private final NlvLotStartService nlvLotStartService;
private final RemoteGetSapBatchNoService remoteGetSapBatchNoService;
private final ShopOrderService shopOrderService;
private final LotBasisService lotBasisService;
@Override
public CompleteCorrectionResultDTO correct(CompleteCorrectionRequestDTO request) {
@ -62,9 +70,12 @@ public class ZLotMaterialCompleteCorrectionServiceImpl implements ZLotMaterialCo
prepareOutFields(source, request);
boolean sapChanged = sapChanged(source, request);
boolean localChanged = localChanged(source, request);
if (!sapChanged && !localChanged) {
if (!sapChanged && !localChanged && !Boolean.TRUE.equals(request.getForceSap())) {
throw BusinessException.build("没有可保存的变更");
}
if (Boolean.TRUE.equals(request.getForceSap())) {
sapChanged = true;
}
ZLotMaterialCompleteTransaction before = new ZLotMaterialCompleteTransaction();
BeanUtils.copyProperties(source, before);
if (!sapChanged) {
@ -110,6 +121,7 @@ public class ZLotMaterialCompleteCorrectionServiceImpl implements ZLotMaterialCo
source.setDateUpdate(LocalDateTime.now());
if (zeroQty(request)) {
source.setErpReverse("Y");
source.setSyncStatus("C");
completeService.updateById(source);
return result(source.getSid(), "SAP", "SUCCESS",
reversed102 ? "数量为0,已发102冲销,不再报101" : "数量为0,已同步MES库存,不再报101");
@ -140,6 +152,137 @@ public class ZLotMaterialCompleteCorrectionServiceImpl implements ZLotMaterialCo
return result(sid, "SAP", "SUCCESS", "已重报101并同步WMS库存");
}
@Override
public void transferShopOrder(List<CompleteTransferRequestDTO> requests) {
if (requests == null || requests.isEmpty()) {
throw BusinessException.build("请选择要调整到其他工单的成品行");
}
for (CompleteTransferRequestDTO request : requests) {
transferOne(request);
}
}
private void transferOne(CompleteTransferRequestDTO request) {
if (request == null || isBlank(request.getTransactionSid()) || isBlank(request.getTargetShopOrder())) {
throw BusinessException.build("成品事务和目标工单不能为空");
}
ZLotMaterialCompleteTransaction source = completeService.getById(request.getTransactionSid());
if (source == null) {
throw BusinessException.build("成品事务不存在");
}
if (!"101".equals(source.getProductType())) {
throw BusinessException.build("仅支持101成品调整到其他工单");
}
if ("Y".equals(source.getErpReverse()) || "C".equals(source.getSyncStatus())) {
throw BusinessException.build("已冲销或已结案的记录不能调整到其他工单");
}
if (!"S".equals(source.getSyncStatus()) && !"N".equals(source.getSyncStatus()) && !"E".equals(source.getSyncStatus())) {
throw BusinessException.build("只支持成功、待过账或失败的成品行调整到其他工单");
}
if (request.getTargetShopOrder().equals(source.getShopOrder())) {
throw BusinessException.build("目标工单不能与原工单相同");
}
ShopOrder targetOrder = shopOrderService.getOne(Wrappers.<ShopOrder>lambdaQuery()
.eq(ShopOrder::getSite, source.getSite())
.eq(ShopOrder::getShopOrder, request.getTargetShopOrder()));
if (targetOrder == null) {
throw BusinessException.build("目标工单不存在");
}
assertItemAllowedOnOrder(source.getSite(), targetOrder.getShopOrder(), source.getItem());
if ("S".equals(source.getSyncStatus())) {
if (request.getPostingDate() == null) {
throw BusinessException.build("已过账记录调整到其他工单时请选择过账日期");
}
String reverseSid = UUID.randomUUID().toString();
JSONObject sapRequest = new JSONObject();
sapRequest.put("transaction", source);
sapRequest.put("reverseSid", reverseSid);
sapRequest.put("postingDate", request.getPostingDate().toString());
JSONObject response = remoteInvokeService.reverseComplete102(sapRequest, SecurityConstants.FROM_IN);
if (response == null || !"S".equals(response.getString("RESULT"))) {
throw BusinessException.build(response == null ? "SAP未返回结果" : response.getString("MESSAGE"));
}
saveOff(source, reverseSid, response);
closeComplete(source);
ZLotMaterialCompleteTransaction target = createTargetComplete(source, targetOrder, request.getPostingDate());
moveLotBasis(source, targetOrder);
CompleteCorrectionResultDTO posted = repost101(target.getSid());
if (!"SUCCESS".equals(posted.getStatus())) {
throw BusinessException.build(posted.getMessage() == null ? "目标工单101过账失败" : posted.getMessage());
}
return;
}
closeComplete(source);
createTargetComplete(source, targetOrder, request.getPostingDate());
moveLotBasis(source, targetOrder);
}
private void assertItemAllowedOnOrder(String site, String shopOrder, String item) {
List<ShopOrderReleaseItemInfo> allowed = nlvLotStartService.firstOpReleaseItemList(
site, null, Collections.singletonList(shopOrder));
boolean ok = allowed != null && allowed.stream().anyMatch(info -> eq(item, info.getItem()));
if (!ok) {
throw BusinessException.build("物料" + item + "不是工单" + shopOrder + "允许的产出物料");
}
}
private ZLotMaterialCompleteTransaction createTargetComplete(ZLotMaterialCompleteTransaction source,
ShopOrder targetOrder, LocalDate postingDate) {
String newSid = UUID.randomUUID().toString();
LocalDateTime now = LocalDateTime.now();
String user = currentUser();
ZLotMaterialCompleteTransaction target = new ZLotMaterialCompleteTransaction();
BeanUtils.copyProperties(source, target);
target.setSid(newSid);
target.setShopOrder(targetOrder.getShopOrder());
target.setSapBatchNo(nextSapBatchNo(source.getItem(), targetOrder.getShopOrder(), source.getQcGrade()));
target.setSyncStatus("N");
target.setSyncVoucherNo(null);
target.setSyncMsgNo(null);
target.setSyncMsgText(null);
target.setSyncMsgId(null);
target.setSyncTransBatch(null);
target.setSyncTransDate(null);
target.setSyncPostingDate(postingDate);
target.setErpFlag("N");
target.setErpReverse(null);
target.setCreator(user);
target.setDateCreate(now);
target.setUpdater(user);
target.setDateUpdate(now);
completeService.save(target);
return target;
}
private void closeComplete(ZLotMaterialCompleteTransaction source) {
ZLotMaterialCompleteTransaction latest = completeService.getById(source.getSid());
latest.setSyncStatus("C");
if ("S".equals(source.getSyncStatus())) {
latest.setErpReverse("Y");
}
latest.setUpdater(currentUser());
latest.setDateUpdate(LocalDateTime.now());
completeService.updateById(latest);
}
private void moveLotBasis(ZLotMaterialCompleteTransaction source, ShopOrder targetOrder) {
if (isBlank(source.getLotNo())) {
return;
}
LotBasis lot = lotBasisService.getById(LotBasis.genHandle(source.getSite(), source.getLotNo()));
if (lot == null) {
return;
}
lot.setShopOrderBo(targetOrder.getHandle());
lot.setUpdater(currentUser());
lot.setDateUpdate(LocalDateTime.now());
lotBasisService.updateById(lot);
}
private boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
private void prepareOutFields(ZLotMaterialCompleteTransaction source, CompleteCorrectionRequestDTO request) {
request.setShopOrder(source.getShopOrder());
request.setLotNo(source.getLotNo());
@ -215,7 +358,7 @@ public class ZLotMaterialCompleteCorrectionServiceImpl implements ZLotMaterialCo
return;
}
InventoryQtyAdjustRequest inventoryRequest = new InventoryQtyAdjustRequest();
inventoryRequest.setRequestId(before.getSid() + ":fgq:" + oldQty.toPlainString() + ":" + newQty.toPlainString());
inventoryRequest.setRequestId(before.getSid() + ":" + System.currentTimeMillis());
inventoryRequest.setInventoryBo(inventory.getHandle());
inventoryRequest.setDeltaQty(delta);
inventoryRequest.setExpectedMaterialLot(inventory.getInventoryId());
@ -280,7 +423,8 @@ public class ZLotMaterialCompleteCorrectionServiceImpl implements ZLotMaterialCo
return !eq(source.getItem(), request.getItem())
|| !qtyEq(source.getMainQty(), request.getMainQty())
|| !eq(source.getQcGrade(), request.getQcGrade())
|| !eq(source.getSapBatchNo(), request.getSapBatchNo());
|| !eq(source.getSapBatchNo(), request.getSapBatchNo())
|| postingDateChanged(source.getSyncPostingDate(), request.getPostingDate());
}
private boolean localChanged(ZLotMaterialCompleteTransaction source, CompleteCorrectionRequestDTO request) {
@ -339,6 +483,10 @@ public class ZLotMaterialCompleteCorrectionServiceImpl implements ZLotMaterialCo
return a.equals(b);
}
private boolean postingDateChanged(LocalDate current, LocalDate next) {
return current != null && next != null && !current.equals(next);
}
private boolean zeroQty(CompleteCorrectionRequestDTO request) {
return request.getMainQty() != null && request.getMainQty().compareTo(BigDecimal.ZERO) == 0;
}

View File

@ -16,6 +16,7 @@ import com.foreverwin.iep.prd.service.ShopOrderService;
import com.foreverwin.iep.prd.service.ZLotMaterialCancelService;
import com.foreverwin.iep.prd.service.ZLotMaterialConsumptionRepostService;
import com.foreverwin.iep.prd.service.ZLotMaterialConsumptionTransactionService;
import com.foreverwin.iep.prd.service.ZLotMaterialQtyAdjustmentService;
import com.foreverwin.iep.wip.dto.OperationOutBomComponentDTO;
import com.foreverwin.iep.wip.entity.LotMaterial;
import com.foreverwin.iep.wip.mapper.OperationOutMapper;
@ -48,6 +49,7 @@ public class ZLotMaterialConsumptionRepostServiceImpl implements ZLotMaterialCon
private final ShopOrderService shopOrderService;
private final OperationOutMapper operationOutMapper;
private final ZLotMaterialCancelService cancelService;
private final ZLotMaterialQtyAdjustmentService qtyAdjustmentService;
@Override
public void repost261(List<ConsumptionRepostRequestDTO> requests) {
@ -86,7 +88,20 @@ public class ZLotMaterialConsumptionRepostServiceImpl implements ZLotMaterialCon
if (request.getPostingDate() == null) {
throw BusinessException.build("请选择过账日期");
}
ZLotMaterialConsumptionTransaction source = requireUnposted(request.getTransactionSid());
ZLotMaterialConsumptionTransaction source = consumptionService.getById(request.getTransactionSid());
if (source == null) {
throw BusinessException.build("物料消耗事务不存在");
}
if ("Y".equals(source.getErpReverse()) || "C".equals(source.getSyncStatus())) {
throw BusinessException.build("已冲销或已结案的记录不能重新过账");
}
if ("S".equals(source.getSyncStatus())) {
repostPosted(source, request);
return;
}
if (!"N".equals(source.getSyncStatus()) && !"E".equals(source.getSyncStatus())) {
throw BusinessException.build("只能对成功、待过账或失败的记录重新过账");
}
BigDecimal oldQty = value(source.getMainQty());
BigDecimal newQty = request.getTargetQty() == null ? oldQty : request.getTargetQty();
if (newQty.compareTo(BigDecimal.ZERO) < 0) {
@ -101,6 +116,67 @@ public class ZLotMaterialConsumptionRepostServiceImpl implements ZLotMaterialCon
persistQty(source.getSid(), newQty);
}
private void repostPosted(ZLotMaterialConsumptionTransaction source, ConsumptionRepostRequestDTO request) {
if (qtyAdjustmentService.hasSuccessAdjustment(source.getSid())) {
throw BusinessException.build("已数量调整的行请使用调整261/262");
}
if (StrUtil.isBlank(source.getReservedNo()) || StrUtil.isBlank(source.getReservedLine())) {
throw BusinessException.build("原消耗缺少预留号或预留行号,不能冲销262");
}
BigDecimal oldQty = value(source.getMainQty());
BigDecimal newQty = request.getTargetQty() == null ? oldQty : request.getTargetQty();
if (newQty.compareTo(BigDecimal.ZERO) < 0) {
throw BusinessException.build("目标数量不能小于0");
}
String cancelSid = StrUtil.uuid();
call262(source, oldQty, request.getPostingDate(), cancelSid);
saveCancel(source, cancelSid, oldQty, request.getPostingDate());
if (oldQty.compareTo(BigDecimal.ZERO) > 0) {
adjustWms(source, oldQty, BigDecimal.ZERO, source.getSid() + ":rp262:" + oldQty.toPlainString(),
source.getShopOrder() + "已过账消耗262冲销退回线边库", "MATERIAL_CONSUMPTION_REPOST_262");
}
persistUnposted(source.getSid(), newQty);
if (newQty.compareTo(BigDecimal.ZERO) == 0) {
return;
}
adjustWms(source, newQty.negate(), newQty, source.getSid() + ":rp261:" + newQty.toPlainString(),
source.getShopOrder() + "已过账消耗按新数量261", "MATERIAL_CONSUMPTION_REPOST_261");
call261(source, newQty, request.getPostingDate(), source.getReservedNo(), source.getReservedLine());
persistQty(source.getSid(), newQty);
}
private void persistUnposted(String transactionSid, BigDecimal newQty) {
ZLotMaterialConsumptionTransaction latest = consumptionService.getById(transactionSid);
if (latest == null) {
throw BusinessException.build("物料消耗事务不存在");
}
latest.setMainQty(newQty);
latest.setSyncStatus("N");
latest.setSyncVoucherNo(null);
latest.setErpReverse(null);
latest.setUpdater(currentUser());
latest.setDateUpdate(LocalDateTime.now());
consumptionService.updateById(latest);
if (latest.getLotMaterialSid() == null) {
return;
}
LotMaterial lotMaterial = lotMaterialService.getById(latest.getLotMaterialSid());
if (lotMaterial == null) {
return;
}
lotMaterial.setConsumeQty(newQty);
lotMaterial.setQty(newQty);
lotMaterial.setErpFlag("N");
if (newQty.compareTo(BigDecimal.ZERO) == 0) {
lotMaterial.setRemoved("Y");
lotMaterial.setRemovedDate(LocalDateTime.now());
} else {
lotMaterial.setRemoved(null);
lotMaterial.setRemovedDate(null);
}
lotMaterialService.updateById(lotMaterial);
}
private void cancelOne(ConsumptionCancelRequestDTO request) {
if (request == null || request.getTransactionSid() == null || request.getTransactionSid().isEmpty()) {
throw BusinessException.build("消耗事务不能为空");

View File

@ -3,11 +3,16 @@ package com.foreverwin.iep.prd.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.foreverwin.iep.integration.feign.RemoteInvokeService;
import com.foreverwin.iep.prd.dto.CompleteCorrectionRequestDTO;
import com.foreverwin.iep.prd.dto.CompleteTransferRequestDTO;
import com.foreverwin.iep.prd.entity.ShopOrder;
import com.foreverwin.iep.prd.entity.ZLotMaterialCompleteTransaction;
import com.foreverwin.iep.integration.feign.RemoteGetSapBatchNoService;
import com.foreverwin.iep.prd.service.ItemService;
import com.foreverwin.iep.prd.service.ShopOrderService;
import com.foreverwin.iep.prd.service.ZLotMaterialCompleteTransactionOffService;
import com.foreverwin.iep.prd.service.ZLotMaterialCompleteTransactionService;
import com.foreverwin.iep.wip.entity.LotBasis;
import com.foreverwin.iep.wip.service.LotBasisService;
import com.foreverwin.iep.wip.service.NlvLotStartService;
import com.foreverwin.mom.common.core.util.R;
import com.foreverwin.mom.wms.api.feign.RemoteWmsInventoryService;
@ -61,7 +66,8 @@ class ZLotMaterialCompleteCorrectionServiceImplTest {
ZLotMaterialCompleteCorrectionServiceImpl service = new ZLotMaterialCompleteCorrectionServiceImpl(
mock(ZLotMaterialCompleteTransactionService.class),
mock(ZLotMaterialCompleteTransactionOffService.class),
null, null, mock(ItemService.class), nlvLotStartService, mock(RemoteGetSapBatchNoService.class));
null, null, mock(ItemService.class), nlvLotStartService, mock(RemoteGetSapBatchNoService.class),
mock(ShopOrderService.class), mock(LotBasisService.class));
ZLotMaterialCompleteTransaction source = source();
source.setSite("2031");
CompleteCorrectionRequestDTO request = requestFrom(source);
@ -90,7 +96,9 @@ class ZLotMaterialCompleteCorrectionServiceImplTest {
remoteWmsInventoryService,
mock(ItemService.class),
mock(NlvLotStartService.class),
mock(RemoteGetSapBatchNoService.class));
mock(RemoteGetSapBatchNoService.class),
mock(ShopOrderService.class),
mock(LotBasisService.class));
ZLotMaterialCompleteTransaction source = source();
source.setSite("2031");
@ -116,6 +124,8 @@ class ZLotMaterialCompleteCorrectionServiceImplTest {
ArgumentCaptor<InventoryQtyAdjustRequest> captor = ArgumentCaptor.forClass(InventoryQtyAdjustRequest.class);
verify(remoteWmsInventoryService).adjustQty(captor.capture());
assertEquals(0, new BigDecimal("-2").compareTo(captor.getValue().getDeltaQty()));
assertTrue(captor.getValue().getRequestId().startsWith("sid-1:"));
assertTrue(captor.getValue().getRequestId().length() <= 64);
assertEquals(0, new BigDecimal("8").compareTo(source.getMainQty()));
verify(remoteInvokeService).repostComplete101(any(), anyString());
}
@ -132,7 +142,9 @@ class ZLotMaterialCompleteCorrectionServiceImplTest {
remoteWmsInventoryService,
mock(ItemService.class),
mock(NlvLotStartService.class),
mock(RemoteGetSapBatchNoService.class));
mock(RemoteGetSapBatchNoService.class),
mock(ShopOrderService.class),
mock(LotBasisService.class));
ZLotMaterialCompleteTransaction source = source();
source.setSite("2031");
@ -159,6 +171,7 @@ class ZLotMaterialCompleteCorrectionServiceImplTest {
verify(remoteInvokeService, never()).repostComplete101(any(), anyString());
assertEquals(0, BigDecimal.ZERO.compareTo(source.getMainQty()));
assertEquals("Y", source.getErpReverse());
assertEquals("C", source.getSyncStatus());
}
@Test
@ -173,7 +186,9 @@ class ZLotMaterialCompleteCorrectionServiceImplTest {
remoteWmsInventoryService,
mock(ItemService.class),
mock(NlvLotStartService.class),
mock(RemoteGetSapBatchNoService.class));
mock(RemoteGetSapBatchNoService.class),
mock(ShopOrderService.class),
mock(LotBasisService.class));
ZLotMaterialCompleteTransaction source = source();
source.setSite("2031");
@ -196,13 +211,73 @@ class ZLotMaterialCompleteCorrectionServiceImplTest {
verify(remoteInvokeService, never()).reverseComplete102(any(), anyString());
verify(remoteInvokeService, never()).repostComplete101(any(), anyString());
assertEquals("Y", source.getErpReverse());
assertEquals("C", source.getSyncStatus());
}
@Test
void postingDateChangeTouchesSap() throws Exception {
ZLotMaterialCompleteCorrectionServiceImpl service = newService();
ZLotMaterialCompleteTransaction source = source();
source.setSyncPostingDate(LocalDate.of(2026, 8, 1));
CompleteCorrectionRequestDTO request = requestFrom(source);
request.setPostingDate(LocalDate.of(2026, 9, 1));
assertTrue((Boolean) invoke(service, "sapChanged", source, request));
}
@Test
void transferUnpostedMovesLotWithoutSap() {
ZLotMaterialCompleteTransactionService completeService = mock(ZLotMaterialCompleteTransactionService.class);
ShopOrderService shopOrderService = mock(ShopOrderService.class);
LotBasisService lotBasisService = mock(LotBasisService.class);
NlvLotStartService nlvLotStartService = mock(NlvLotStartService.class);
RemoteGetSapBatchNoService sapBatchNoService = mock(RemoteGetSapBatchNoService.class);
RemoteInvokeService remoteInvokeService = mock(RemoteInvokeService.class);
ZLotMaterialCompleteCorrectionServiceImpl service = new ZLotMaterialCompleteCorrectionServiceImpl(
completeService,
mock(ZLotMaterialCompleteTransactionOffService.class),
remoteInvokeService,
mock(RemoteWmsInventoryService.class),
mock(ItemService.class),
nlvLotStartService,
sapBatchNoService,
shopOrderService,
lotBasisService);
ZLotMaterialCompleteTransaction source = source();
source.setSite("2031");
source.setSyncStatus("N");
when(completeService.getById("sid-1")).thenReturn(source);
ShopOrder target = new ShopOrder();
target.setHandle("ShopOrderBO:2031,2100014999");
target.setShopOrder("2100014999");
when(shopOrderService.getOne(any())).thenReturn(target);
com.foreverwin.iep.wip.domain.ShopOrderReleaseItemInfo itemInfo =
new com.foreverwin.iep.wip.domain.ShopOrderReleaseItemInfo();
itemInfo.setItem("2300227");
when(nlvLotStartService.firstOpReleaseItemList(any(), any(), any()))
.thenReturn(Collections.singletonList(itemInfo));
when(sapBatchNoService.getSapBatchNoNew(any())).thenReturn(R.ok("2609010001"));
LotBasis lot = new LotBasis();
lot.setHandle(LotBasis.genHandle("2031", "3601180036A005"));
when(lotBasisService.getById(anyString())).thenReturn(lot);
CompleteTransferRequestDTO request = new CompleteTransferRequestDTO();
request.setTransactionSid("sid-1");
request.setTargetShopOrder("2100014999");
service.transferShopOrder(Collections.singletonList(request));
verify(remoteInvokeService, never()).reverseComplete102(any(), anyString());
verify(remoteInvokeService, never()).repostComplete101(any(), anyString());
verify(completeService).save(any(ZLotMaterialCompleteTransaction.class));
assertEquals("C", source.getSyncStatus());
assertEquals("ShopOrderBO:2031,2100014999", lot.getShopOrderBo());
}
private ZLotMaterialCompleteCorrectionServiceImpl newService() {
return new ZLotMaterialCompleteCorrectionServiceImpl(
mock(ZLotMaterialCompleteTransactionService.class),
mock(ZLotMaterialCompleteTransactionOffService.class),
null, null, null, null, null);
null, null, null, null, null, null, null);
}
private Object invoke(ZLotMaterialCompleteCorrectionServiceImpl service, String name,

View File

@ -11,6 +11,7 @@ import com.foreverwin.iep.prd.entity.ZLotMaterialConsumptionTransaction;
import com.foreverwin.iep.prd.service.ShopOrderService;
import com.foreverwin.iep.prd.service.ZLotMaterialCancelService;
import com.foreverwin.iep.prd.service.ZLotMaterialConsumptionTransactionService;
import com.foreverwin.iep.prd.service.ZLotMaterialQtyAdjustmentService;
import com.foreverwin.iep.wip.dto.OperationOutBomComponentDTO;
import com.foreverwin.iep.wip.entity.LotMaterial;
import com.foreverwin.iep.wip.mapper.OperationOutMapper;
@ -44,9 +45,10 @@ class ZLotMaterialConsumptionRepostServiceImplTest {
private final ShopOrderService shopOrderService = mock(ShopOrderService.class);
private final OperationOutMapper operationOutMapper = mock(OperationOutMapper.class);
private final ZLotMaterialCancelService cancelService = mock(ZLotMaterialCancelService.class);
private final ZLotMaterialQtyAdjustmentService qtyAdjustmentService = mock(ZLotMaterialQtyAdjustmentService.class);
private final ZLotMaterialConsumptionRepostServiceImpl service = new ZLotMaterialConsumptionRepostServiceImpl(
consumptionService, lotMaterialService, remoteInvokeService, remoteWmsInventoryService,
shopOrderService, operationOutMapper, cancelService);
shopOrderService, operationOutMapper, cancelService, qtyAdjustmentService);
@Test
void decreasingQtyReturnsDifferenceToWms() {
@ -130,6 +132,60 @@ class ZLotMaterialConsumptionRepostServiceImplTest {
assertEquals("C", source.getSyncStatus());
}
@Test
void postedRepostDecreasesQtyWith262Then261() {
ZLotMaterialConsumptionTransaction source = unposted("sid-s", "S", new BigDecimal("55"));
source.setReservedNo("90001");
source.setReservedLine("10");
when(consumptionService.getById("sid-s")).thenReturn(source);
when(qtyAdjustmentService.hasSuccessAdjustment("sid-s")).thenReturn(false);
LotMaterial lotMaterial = new LotMaterial();
lotMaterial.setSid("lm-1");
lotMaterial.setItemBo("ItemBO:2031,A");
when(lotMaterialService.getById("lm-1")).thenReturn(lotMaterial);
sap261Ok();
sap262Ok();
when(remoteWmsInventoryService.adjustQty(any())).thenReturn(R.ok(new Inventory()));
ConsumptionRepostRequestDTO request = new ConsumptionRepostRequestDTO();
request.setTransactionSid("sid-s");
request.setTargetQty(new BigDecimal("50"));
request.setPostingDate(LocalDate.of(2026, 9, 1));
service.repost261(Collections.singletonList(request));
verify(remoteInvokeService).adjustMaterialQty(any(), anyString());
verify(remoteInvokeService).callErpMaterial(any(), anyString());
ArgumentCaptor<InventoryQtyAdjustRequest> captor = ArgumentCaptor.forClass(InventoryQtyAdjustRequest.class);
verify(remoteWmsInventoryService, org.mockito.Mockito.times(2)).adjustQty(captor.capture());
assertEquals(0, new BigDecimal("55").compareTo(captor.getAllValues().get(0).getDeltaQty()));
assertEquals(0, new BigDecimal("-50").compareTo(captor.getAllValues().get(1).getDeltaQty()));
}
@Test
void postedRepostZeroQtyOnly262AndStaysUnposted() {
ZLotMaterialConsumptionTransaction source = unposted("sid-0", "S", new BigDecimal("55"));
source.setReservedNo("90001");
source.setReservedLine("10");
when(consumptionService.getById("sid-0")).thenReturn(source);
when(qtyAdjustmentService.hasSuccessAdjustment("sid-0")).thenReturn(false);
LotMaterial lotMaterial = new LotMaterial();
lotMaterial.setSid("lm-1");
when(lotMaterialService.getById("lm-1")).thenReturn(lotMaterial);
sap262Ok();
when(remoteWmsInventoryService.adjustQty(any())).thenReturn(R.ok(new Inventory()));
ConsumptionRepostRequestDTO request = new ConsumptionRepostRequestDTO();
request.setTransactionSid("sid-0");
request.setTargetQty(BigDecimal.ZERO);
request.setPostingDate(LocalDate.of(2026, 9, 1));
service.repost261(Collections.singletonList(request));
verify(remoteInvokeService).adjustMaterialQty(any(), anyString());
verify(remoteInvokeService, never()).callErpMaterial(any(), anyString());
assertEquals("N", source.getSyncStatus());
assertEquals(0, BigDecimal.ZERO.compareTo(source.getMainQty()));
}
private ZLotMaterialConsumptionTransaction unposted(String sid, String status, BigDecimal qty) {
ZLotMaterialConsumptionTransaction source = new ZLotMaterialConsumptionTransaction();
source.setSid(sid);
@ -148,4 +204,10 @@ class ZLotMaterialConsumptionRepostServiceImplTest {
sapOk.put("code", 0);
when(remoteInvokeService.callErpMaterial(any(), anyString())).thenReturn(sapOk);
}
private void sap262Ok() {
JSONObject sapOk = new JSONObject();
sapOk.put("RESULT", "S");
when(remoteInvokeService.adjustMaterialQty(any(), anyString())).thenReturn(sapOk);
}
}