fix(prd): reverse 102/262 with original posting date and restore S after 261

Cross-month corrections must reverse on the old BUDAT and post the new date; posted consume repost now writes status S after both SAP calls succeed.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
tsiji 2026-09-11 16:21:59 +08:00
parent 6da7c08e91
commit 069d766d1f
4 changed files with 103 additions and 12 deletions

View File

@ -99,7 +99,7 @@ public class ZLotMaterialCompleteCorrectionServiceImpl implements ZLotMaterialCo
JSONObject sapRequest = new JSONObject();
sapRequest.put("transaction", source);
sapRequest.put("reverseSid", reverseSid);
sapRequest.put("postingDate", request.getPostingDate().toString());
sapRequest.put("postingDate", requireOriginalPostingDate(source.getSyncPostingDate()).toString());
JSONObject response = remoteInvokeService.reverseComplete102(sapRequest, SecurityConstants.FROM_IN);
if (response == null || !"S".equals(response.getString("RESULT"))) {
return result(source.getSid(), "SAP", "FAILED",
@ -197,7 +197,7 @@ public class ZLotMaterialCompleteCorrectionServiceImpl implements ZLotMaterialCo
JSONObject sapRequest = new JSONObject();
sapRequest.put("transaction", source);
sapRequest.put("reverseSid", reverseSid);
sapRequest.put("postingDate", request.getPostingDate().toString());
sapRequest.put("postingDate", requireOriginalPostingDate(source.getSyncPostingDate()).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"));
@ -487,6 +487,13 @@ public class ZLotMaterialCompleteCorrectionServiceImpl implements ZLotMaterialCo
return current != null && next != null && !current.equals(next);
}
private LocalDate requireOriginalPostingDate(LocalDate original) {
if (original == null) {
throw BusinessException.build("原过账日期为空,不能反冲");
}
return original;
}
private boolean zeroQty(CompleteCorrectionRequestDTO request) {
return request.getMainQty() != null && request.getMainQty().compareTo(BigDecimal.ZERO) == 0;
}

View File

@ -128,9 +128,10 @@ public class ZLotMaterialConsumptionRepostServiceImpl implements ZLotMaterialCon
if (newQty.compareTo(BigDecimal.ZERO) < 0) {
throw BusinessException.build("目标数量不能小于0");
}
LocalDate reverseDate = requireOriginalPostingDate(source.getSyncPostingDate());
String cancelSid = StrUtil.uuid();
call262(source, oldQty, request.getPostingDate(), cancelSid);
saveCancel(source, cancelSid, oldQty, request.getPostingDate());
call262(source, oldQty, reverseDate, cancelSid);
saveCancel(source, cancelSid, oldQty, reverseDate);
if (oldQty.compareTo(BigDecimal.ZERO) > 0) {
adjustWms(source, oldQty, BigDecimal.ZERO, source.getSid() + ":rp262:" + oldQty.toPlainString(),
source.getShopOrder() + "已过账消耗262冲销退回线边库", "MATERIAL_CONSUMPTION_REPOST_262");
@ -142,7 +143,7 @@ public class ZLotMaterialConsumptionRepostServiceImpl implements ZLotMaterialCon
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);
persistPosted(source.getSid(), newQty, request.getPostingDate());
}
private void persistUnposted(String transactionSid, BigDecimal newQty) {
@ -229,13 +230,14 @@ public class ZLotMaterialConsumptionRepostServiceImpl implements ZLotMaterialCon
if (StrUtil.isBlank(source.getReservedNo()) || StrUtil.isBlank(source.getReservedLine())) {
throw BusinessException.build("原消耗缺少预留号或预留行号,不能冲销262");
}
LocalDate reverseDate = requireOriginalPostingDate(source.getSyncPostingDate());
String cancelSid = StrUtil.uuid();
call262(source, request.getTransferQty(), request.getPostingDate(), cancelSid);
saveCancel(source, cancelSid, request.getTransferQty(), request.getPostingDate());
call262(source, request.getTransferQty(), reverseDate, cancelSid);
saveCancel(source, cancelSid, request.getTransferQty(), reverseDate);
closeOrReduce(source, remainQty);
ZLotMaterialConsumptionTransaction target = createTarget(source, targetOrder, reserved, request.getTransferQty());
call261(target, request.getTransferQty(), request.getPostingDate(), reserved.getReserved(), reserved.getReservedSeq());
persistQty(target.getSid(), request.getTransferQty());
persistPosted(target.getSid(), request.getTransferQty(), request.getPostingDate());
return;
}
closeOrReduce(source, remainQty);
@ -418,11 +420,26 @@ public class ZLotMaterialConsumptionRepostServiceImpl implements ZLotMaterialCon
}
private void persistQty(String transactionSid, BigDecimal newQty) {
persistQty(transactionSid, newQty, null, null);
}
private void persistPosted(String transactionSid, BigDecimal newQty, LocalDate postingDate) {
persistQty(transactionSid, newQty, "S", postingDate);
}
private void persistQty(String transactionSid, BigDecimal newQty, String syncStatus, LocalDate postingDate) {
ZLotMaterialConsumptionTransaction latest = consumptionService.getById(transactionSid);
if (latest == null) {
throw BusinessException.build("物料消耗事务不存在");
}
latest.setMainQty(newQty);
if (syncStatus != null) {
latest.setSyncStatus(syncStatus);
latest.setErpFlag("Y");
}
if (postingDate != null) {
latest.setSyncPostingDate(postingDate);
}
latest.setUpdater(currentUser());
latest.setDateUpdate(LocalDateTime.now());
consumptionService.updateById(latest);
@ -435,9 +452,21 @@ public class ZLotMaterialConsumptionRepostServiceImpl implements ZLotMaterialCon
}
lotMaterial.setConsumeQty(newQty);
lotMaterial.setQty(newQty);
if ("S".equals(syncStatus)) {
lotMaterial.setErpFlag("Y");
lotMaterial.setRemoved(null);
lotMaterial.setRemovedDate(null);
}
lotMaterialService.updateById(lotMaterial);
}
private LocalDate requireOriginalPostingDate(LocalDate original) {
if (original == null) {
throw BusinessException.build("原过账日期为空,不能反冲");
}
return original;
}
private void adjustWms(ZLotMaterialConsumptionTransaction source, BigDecimal delta, BigDecimal newQty,
String requestId, String comments, String changeType) {
LotMaterial lotMaterial = source.getLotMaterialSid() == null

View File

@ -149,6 +149,7 @@ class ZLotMaterialCompleteCorrectionServiceImplTest {
ZLotMaterialCompleteTransaction source = source();
source.setSite("2031");
source.setSyncStatus("S");
source.setSyncPostingDate(LocalDate.of(2026, 8, 1));
when(completeService.getById("sid-1")).thenReturn(source);
Inventory inventory = new Inventory();
inventory.setHandle("InventoryBO:2031,3601180036A005");
@ -167,13 +168,53 @@ class ZLotMaterialCompleteCorrectionServiceImplTest {
request.setPostingDate(LocalDate.of(2026, 8, 28));
service.correct(request);
verify(remoteInvokeService).reverseComplete102(any(), anyString());
ArgumentCaptor<JSONObject> reverseCaptor = ArgumentCaptor.forClass(JSONObject.class);
verify(remoteInvokeService).reverseComplete102(reverseCaptor.capture(), anyString());
assertEquals("2026-08-01", reverseCaptor.getValue().getString("postingDate"));
verify(remoteInvokeService, never()).repostComplete101(any(), anyString());
assertEquals(0, BigDecimal.ZERO.compareTo(source.getMainQty()));
assertEquals("Y", source.getErpReverse());
assertEquals("C", source.getSyncStatus());
}
@Test
void syncedPostingDateChangeReversesWithOldDateThenRepostsNew() {
ZLotMaterialCompleteTransactionService completeService = mock(ZLotMaterialCompleteTransactionService.class);
RemoteInvokeService remoteInvokeService = mock(RemoteInvokeService.class);
RemoteWmsInventoryService remoteWmsInventoryService = mock(RemoteWmsInventoryService.class);
ZLotMaterialCompleteCorrectionServiceImpl service = new ZLotMaterialCompleteCorrectionServiceImpl(
completeService,
mock(ZLotMaterialCompleteTransactionOffService.class),
remoteInvokeService,
remoteWmsInventoryService,
mock(ItemService.class),
mock(NlvLotStartService.class),
mock(RemoteGetSapBatchNoService.class),
mock(ShopOrderService.class),
mock(LotBasisService.class));
ZLotMaterialCompleteTransaction source = source();
source.setSite("2031");
source.setSyncStatus("S");
source.setSyncPostingDate(LocalDate.of(2026, 8, 1));
when(completeService.getById("sid-1")).thenReturn(source);
JSONObject sapOk = new JSONObject();
sapOk.put("RESULT", "S");
when(remoteInvokeService.reverseComplete102(any(), anyString())).thenReturn(sapOk);
when(remoteInvokeService.repostComplete101(any(), anyString())).thenReturn(sapOk);
CompleteCorrectionRequestDTO request = requestFrom(source);
request.setPostingDate(LocalDate.of(2026, 9, 1));
request.setForceSap(true);
service.correct(request);
ArgumentCaptor<JSONObject> reverseCaptor = ArgumentCaptor.forClass(JSONObject.class);
verify(remoteInvokeService).reverseComplete102(reverseCaptor.capture(), anyString());
assertEquals("2026-08-01", reverseCaptor.getValue().getString("postingDate"));
verify(remoteInvokeService).repostComplete101(any(), anyString());
assertEquals(LocalDate.of(2026, 9, 1), source.getSyncPostingDate());
}
@Test
void zeroQtyOnUnpostedRowSkipsSap101() {
ZLotMaterialCompleteTransactionService completeService = mock(ZLotMaterialCompleteTransactionService.class);

View File

@ -2,6 +2,7 @@ package com.foreverwin.iep.prd.service.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.foreverwin.iep.integration.domain.MaterialQtySapRequest;
import com.foreverwin.iep.integration.feign.RemoteInvokeService;
import com.foreverwin.iep.prd.dto.ConsumptionCancelRequestDTO;
import com.foreverwin.iep.prd.dto.ConsumptionRepostRequestDTO;
@ -137,6 +138,7 @@ class ZLotMaterialConsumptionRepostServiceImplTest {
ZLotMaterialConsumptionTransaction source = unposted("sid-s", "S", new BigDecimal("55"));
source.setReservedNo("90001");
source.setReservedLine("10");
source.setSyncPostingDate(LocalDate.of(2026, 8, 1));
when(consumptionService.getById("sid-s")).thenReturn(source);
when(qtyAdjustmentService.hasSuccessAdjustment("sid-s")).thenReturn(false);
LotMaterial lotMaterial = new LotMaterial();
@ -153,12 +155,21 @@ class ZLotMaterialConsumptionRepostServiceImplTest {
request.setPostingDate(LocalDate.of(2026, 9, 1));
service.repost261(Collections.singletonList(request));
verify(remoteInvokeService).adjustMaterialQty(any(), anyString());
verify(remoteInvokeService).callErpMaterial(any(), anyString());
ArgumentCaptor<MaterialQtySapRequest> reverseCaptor = ArgumentCaptor.forClass(MaterialQtySapRequest.class);
verify(remoteInvokeService).adjustMaterialQty(reverseCaptor.capture(), anyString());
assertEquals("262", reverseCaptor.getValue().getMovementType());
assertEquals("2026-08-01", reverseCaptor.getValue().getPostingDate());
ArgumentCaptor<JSONArray> sapCaptor = ArgumentCaptor.forClass(JSONArray.class);
verify(remoteInvokeService).callErpMaterial(sapCaptor.capture(), anyString());
assertEquals("2026-09-01", sapCaptor.getValue().getJSONObject(0).getString("syncPostingDate"));
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()));
assertEquals("S", source.getSyncStatus());
assertEquals(LocalDate.of(2026, 9, 1), source.getSyncPostingDate());
assertEquals(0, new BigDecimal("50").compareTo(source.getMainQty()));
assertEquals("Y", lotMaterial.getErpFlag());
}
@Test
@ -166,6 +177,7 @@ class ZLotMaterialConsumptionRepostServiceImplTest {
ZLotMaterialConsumptionTransaction source = unposted("sid-0", "S", new BigDecimal("55"));
source.setReservedNo("90001");
source.setReservedLine("10");
source.setSyncPostingDate(LocalDate.of(2026, 8, 1));
when(consumptionService.getById("sid-0")).thenReturn(source);
when(qtyAdjustmentService.hasSuccessAdjustment("sid-0")).thenReturn(false);
LotMaterial lotMaterial = new LotMaterial();
@ -180,7 +192,9 @@ class ZLotMaterialConsumptionRepostServiceImplTest {
request.setPostingDate(LocalDate.of(2026, 9, 1));
service.repost261(Collections.singletonList(request));
verify(remoteInvokeService).adjustMaterialQty(any(), anyString());
ArgumentCaptor<MaterialQtySapRequest> reverseCaptor = ArgumentCaptor.forClass(MaterialQtySapRequest.class);
verify(remoteInvokeService).adjustMaterialQty(reverseCaptor.capture(), anyString());
assertEquals("2026-08-01", reverseCaptor.getValue().getPostingDate());
verify(remoteInvokeService, never()).callErpMaterial(any(), anyString());
assertEquals("N", source.getSyncStatus());
assertEquals(0, BigDecimal.ZERO.compareTo(source.getMainQty()));