TransactionHelper.java
/*
* Copyright 2005-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openwms.wms.inventory.impl;
import org.ameba.annotation.Measured;
import org.ameba.http.ctx.CallContext;
import org.ameba.http.ctx.CallContextHolder;
import org.ameba.http.identity.IdentityContextHolder;
import org.ameba.i18n.Translator;
import org.openwms.core.units.api.Measurable;
import org.openwms.transactions.api.TransactionBuilder;
import org.openwms.transactions.api.commands.AsyncTransactionApi;
import org.openwms.transactions.api.commands.TransactionCommand;
import org.openwms.wms.inventory.LoadUnit;
import org.openwms.wms.inventory.PackagingUnit;
import org.openwms.wms.inventory.events.PackagingUnitEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.event.TransactionalEventListener;
import org.springframework.util.Assert;
import static java.lang.String.format;
import static org.openwms.wms.InventoryMessageCodes.MSG_PU_CREATED_IN_LU;
import static org.openwms.wms.InventoryMessageCodes.MSG_PU_DELETED_IN_LU;
import static org.openwms.wms.InventoryMessageCodes.MSG_PU_DELETED_ON_LOC;
import static org.openwms.wms.InventoryMessageCodes.MSG_PU_LOCKED;
import static org.openwms.wms.InventoryMessageCodes.MSG_PU_MOVED_FROM_LOC_TO_LOC;
import static org.openwms.wms.InventoryMessageCodes.MSG_PU_MOVED_FROM_LOC_TO_LU;
import static org.openwms.wms.InventoryMessageCodes.MSG_PU_MOVED_FROM_LU_TO_LOC;
import static org.openwms.wms.InventoryMessageCodes.MSG_PU_MOVED_FROM_LU_TO_LU;
import static org.openwms.wms.InventoryMessageCodes.MSG_PU_QUANTITY_CHANGED;
import static org.openwms.wms.InventoryMessageCodes.MSG_PU_UNLOCKED;
/**
* A TransactionHelper.
*
* @author Heiko Scherrer
*/
@Component
class TransactionHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(TransactionHelper.class);
private final String applicationName;
private final Translator translator;
private final AsyncTransactionApi asyncTransactionApi;
TransactionHelper(@Value("${spring.application.name}") String applicationName, Translator translator,
AsyncTransactionApi asyncTransactionApi) {
this.applicationName = applicationName;
this.translator = translator;
this.asyncTransactionApi = asyncTransactionApi;
}
@Measured
@TransactionalEventListener
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void onPackagingUnitEvent(PackagingUnitEvent event) {
var pu = event.getSource();
switch (event.getType()) {
case CREATED:
if (pu.hasLoadUnit()) {
writeTransactionPUCreateInLU(pu);
} else if (pu.hasActualLocation()) {
writeTransactionPUCreateOnLoc(pu);
} else {
LOGGER.error("Shall throw an exception in the processing logic, because PackagingUnit without Location nor LoadUnit has been created, pKey = [{}]", pu.getPersistentKey());
}
break;
case MOVED:
LOGGER.debug("PU moved event with fromLu [{}], fromLoc [{}], actLu [{}], actLoc [{}]", event.getFromLoadUnit(), event.getFromLocation(), pu.getLoadUnit(), pu.getActualLocation());
if (event.movedToLoadUnit()) {
writeTransactionLocToLU(pu, event.getFromLocation().getErpCode());
} else if (event.movedToLocation()) {
writeTransactionLuToLoc(pu, event.getFromLoadUnit());
} else if (event.movedBetweenLocations()) {
writeTransactionLocToLoc(pu, event.getFromLocation().getErpCode());
} else if (event.movedBetweenLoadUnits()) {
writeTransactionLUToLU(pu, event.getFromLoadUnit());
} else {
throw new IllegalArgumentException(format("Shall not happen: [%s]", event));
}
break;
case QUANTITY_CHANGED:
writeTransactionPUQuantityChange(MSG_PU_QUANTITY_CHANGED, pu, event.getOldQuantity());
break;
case DELETED:
LOGGER.debug("Deleted PackagingUnit [{}]", pu.getPersistentKey());
writeTransactionPUDeleted(event);
break;
case LOCKED:
LOGGER.debug("Locked PackagingUnit [{}]", pu.getPersistentKey());
puLocked(pu);
break;
case UNLOCKED:
LOGGER.debug("Unlocked PackagingUnit [{}]", pu.getPersistentKey());
puUnlocked(pu);
break;
default:
}
}
private void puLocked(PackagingUnit pu) {
asyncTransactionApi.process(TransactionCommand.of(TransactionCommand.Type.CREATE,
createDefaultBuilder().withType(MSG_PU_LOCKED)
.withDescription(translator.translate(MSG_PU_LOCKED, pu.getPersistentKey()))
.withDetail("puPKey", pu.getPersistentKey())
.build()
));
}
private void puUnlocked(PackagingUnit pu) {
asyncTransactionApi.process(TransactionCommand.of(TransactionCommand.Type.CREATE,
createDefaultBuilder().withType(MSG_PU_UNLOCKED)
.withDescription(translator.translate(MSG_PU_UNLOCKED, pu.getPersistentKey()))
.withDetail("puPKey", pu.getPersistentKey())
.build()
));
}
private void writeTransactionLuToLoc(PackagingUnit pu, LoadUnit fromLu) {
LOGGER.debug("Moved PackagingUnit [{}] from LoadUnit [{}] to Location [{}]", pu.getPersistentKey(), fromLu.getPersistentKey(),
pu.getActualLocation().getErpCode());
asyncTransactionApi.process(TransactionCommand.of(TransactionCommand.Type.CREATE,
createDefaultBuilder().withType(MSG_PU_MOVED_FROM_LU_TO_LOC)
.withDescription(translator.translate(MSG_PU_MOVED_FROM_LU_TO_LOC,
pu.getPersistentKey(),
pu.getProduct().getSku(),
pu.getQuantity(),
fromLu.getPhysicalPosition(),
fromLu.getTransportUnit().getTransportUnitBK().getValue(),
fromLu.getTransportUnit().getActualLocation().getErpCode(),
pu.getActualLocation().getErpCode()))
.withDetail("puPKey", pu.getPersistentKey())
.withDetail("sku", pu.getProduct().getSku())
.withDetail("movedQty", "" + pu.getQuantity().getMagnitude().intValue())
.withDetail("fromLoadUnitPosition", fromLu.getPhysicalPosition())
.withDetail("fromTransportUnitBK", fromLu.getTransportUnit().getTransportUnitBK().getValue())
.withDetail("previousLocation", fromLu.getTransportUnit().getActualLocation().getErpCode())
.withDetail("actualLocationErpCode", pu.getActualLocation().getErpCode())
.build()
));
}
private void writeTransactionLUToLU(PackagingUnit pu, LoadUnit fromLu) {
LOGGER.debug("Moved PackagingUnit [{}] from LoadUnit [{}] to LoadUnit [{}]", pu.getPersistentKey(), fromLu.getPersistentKey(),
pu.getLoadUnit().getPersistentKey());
asyncTransactionApi.process(TransactionCommand.of(TransactionCommand.Type.CREATE,
createDefaultBuilder().withType(MSG_PU_MOVED_FROM_LU_TO_LU)
.withDescription(translator.translate(MSG_PU_MOVED_FROM_LU_TO_LU,
pu.getPersistentKey(),
pu.getProduct().getSku(),
pu.getQuantity(),
fromLu.getPhysicalPosition(),
fromLu.getTransportUnit().getTransportUnitBK().getValue(),
fromLu.getTransportUnit().getActualLocation().getErpCode(),
pu.getLoadUnit().getPhysicalPosition(),
pu.getLoadUnit().getTransportUnit().getTransportUnitBK().getValue(),
pu.getLoadUnit().getTransportUnit().getActualLocation().getErpCode()
))
.withDetail("puPKey", pu.getPersistentKey())
.withDetail("sku", pu.getProduct().getSku())
.withDetail("movedQty", "" + pu.getQuantity().getMagnitude().intValue())
.withDetail("fromLoadUnitPosition", fromLu.getPhysicalPosition())
.withDetail("fromTransportUnitBK", fromLu.getTransportUnit().getTransportUnitBK().getValue())
.withDetail("previousLocationErpCode", fromLu.getTransportUnit().getActualLocation().getErpCode())
.withDetail("toLoadUnitPosition", pu.getLoadUnit().getPhysicalPosition())
.withDetail("toTransportUnitBK", pu.getLoadUnit().getTransportUnit().getTransportUnitBK().getValue())
.withDetail("actualLocationErpCode", pu.getLoadUnit().getTransportUnit().getActualLocation().getErpCode())
.build()
));
}
private void writeTransactionLocToLU(PackagingUnit pu, String previousLocation) {
LOGGER.debug("Moved PackagingUnit [{}] from Location [{}] to LoadUnit [{}]", pu.getPersistentKey(), previousLocation,
pu.getLoadUnit().getPersistentKey());
asyncTransactionApi.process(TransactionCommand.of(TransactionCommand.Type.CREATE,
createDefaultBuilder().withType(MSG_PU_MOVED_FROM_LOC_TO_LU)
.withDescription(translator.translate(MSG_PU_MOVED_FROM_LOC_TO_LU,
pu.getPersistentKey(),
pu.getProduct().getSku(),
pu.getQuantity(),
previousLocation,
pu.getLoadUnit().getPhysicalPosition(),
pu.getLoadUnit().getTransportUnit().getTransportUnitBK().getValue(),
pu.getLoadUnit().getTransportUnit().getActualLocation().getErpCode()
))
.withDetail("puPKey", pu.getPersistentKey())
.withDetail("sku", pu.getProduct().getSku())
.withDetail("movedQty", "" + pu.getQuantity().getMagnitude().intValue())
.withDetail("previousLocation", previousLocation)
.withDetail("loadUnitPosition", pu.getLoadUnit().getPhysicalPosition())
.withDetail("toTransportUnitBK", pu.getLoadUnit().getTransportUnit().getTransportUnitBK().getValue())
.withDetail("actualLocationErpCode", pu.getLoadUnit().getTransportUnit().getActualLocation().getErpCode())
.build()
));
}
private void writeTransactionLocToLoc(PackagingUnit pu, String previousLocation) {
LOGGER.debug("Moved PackagingUnit [{}] from Location [{}] to Location [{}]", pu.getPersistentKey(), previousLocation,
pu.getActualLocation().getErpCode());
asyncTransactionApi.process(TransactionCommand.of(TransactionCommand.Type.CREATE,
createDefaultBuilder().withType(MSG_PU_MOVED_FROM_LOC_TO_LOC)
.withDescription(translator.translate(MSG_PU_MOVED_FROM_LOC_TO_LOC,
pu.getPersistentKey(),
pu.getProduct().getSku(),
pu.getQuantity(),
previousLocation,
pu.getActualLocation().getErpCode()
))
.withDetail("puPKey", pu.getPersistentKey())
.withDetail("sku", pu.getProduct().getSku())
.withDetail("movedQty", "" + pu.getQuantity().getMagnitude().intValue())
.withDetail("previousLocation", previousLocation)
.withDetail("actualLocationErpCode", pu.getActualLocation().getErpCode())
.build()
));
}
private void writeTransactionPUCreateInLU(PackagingUnit pu) {
LOGGER.debug("Created PackagingUnit [{}] on LoadUnit [{}]", pu.getPersistentKey(), pu.getLoadUnit().getPersistentKey());
asyncTransactionApi.process(TransactionCommand.of(TransactionCommand.Type.CREATE,
createDefaultBuilder().withType(MSG_PU_CREATED_IN_LU)
.withDescription(translator.translate(MSG_PU_CREATED_IN_LU,
pu.getPersistentKey(),
pu.getProduct().getSku(),
pu.getQuantity(),
pu.getLoadUnit().getPhysicalPosition(),
pu.getLoadUnit().getTransportUnit().getTransportUnitBK().getValue(),
pu.getLoadUnit().getTransportUnit().getActualLocation().getErpCode()
))
.withDetail("puPKey", pu.getPersistentKey())
.withDetail("sku", pu.getProduct().getSku())
.withDetail("qty", "" + pu.getQuantity().getMagnitude().intValue())
.withDetail("loadUnitPosition", pu.getLoadUnit().getPhysicalPosition())
.withDetail("transportUnitBK", pu.getLoadUnit().getTransportUnit().getTransportUnitBK().getValue())
.withDetail("actualLocationErpCode", pu.getLoadUnit().getTransportUnit().getActualLocation().getErpCode())
.build()
));
}
private void writeTransactionPUCreateOnLoc(PackagingUnit pu) {
asyncTransactionApi.process(TransactionCommand.of(TransactionCommand.Type.CREATE,
createDefaultBuilder()
.withType(org.openwms.wms.InventoryMessageCodes.MSG_PU_CREATED_ON_LOC)
.withDescription(translator.translate(org.openwms.wms.InventoryMessageCodes.MSG_PU_CREATED_ON_LOC,
pu.getPersistentKey(),
pu.getProduct().getSku(),
pu.getQuantity(),
pu.getActualLocation().getErpCode()
))
.withDetail("puPKey", pu.getPersistentKey())
.withDetail("sku", pu.getProduct().getSku())
.withDetail("qty", "" + pu.getQuantity().getMagnitude().intValue())
.withDetail("actualLocationErpCode", pu.getActualLocation().getErpCode())
.build()
));
}
private void writeTransactionPUQuantityChange(String msgKey, PackagingUnit pu, Measurable oldQuantity) {
Assert.hasText(msgKey, "msgKey must not be null");
Assert.notNull(pu, "pu must not be null");
Assert.notNull(oldQuantity, "oldQuantity must not be null");
asyncTransactionApi.process(TransactionCommand.of(TransactionCommand.Type.CREATE,
createDefaultBuilder().withType(msgKey)
.withDescription(translator.translate(msgKey,
pu.getPersistentKey(),
pu.getProduct().getSku(),
pu.getQuantity(),
oldQuantity
))
.withDetail("puPKey", pu.getPersistentKey())
.withDetail("sku", pu.getProduct().getSku())
.withDetail("qty", "" + pu.getQuantity().getMagnitude().intValue())
.withDetail("oldQuantity", oldQuantity.asString())
.build()
));
}
private void writeTransactionPUDeleted(PackagingUnitEvent event) {
var pu = event.getSource();
if (event.hasFromLocation()) {
asyncTransactionApi.process(TransactionCommand.of(TransactionCommand.Type.CREATE,
createDefaultBuilder().withType(MSG_PU_DELETED_ON_LOC)
.withDescription(translator.translate(MSG_PU_DELETED_ON_LOC,
pu.getPersistentKey(),
pu.getProduct().getSku(),
pu.getQuantity(),
event.getFromLocation().getErpCode()
))
.withDetail("puPKey", pu.getPersistentKey())
.withDetail("sku", pu.getProduct().getSku())
.withDetail("qty", "" + pu.getQuantity().getMagnitude().intValue())
.withDetail("actualLocationErpCode", event.getFromLocation().getErpCode())
.build()
));
} else if (event.hasFromLoadUnit()) {
asyncTransactionApi.process(TransactionCommand.of(TransactionCommand.Type.CREATE,
createDefaultBuilder().withType(MSG_PU_DELETED_IN_LU)
.withDescription(translator.translate(MSG_PU_DELETED_IN_LU,
pu.getPersistentKey(),
pu.getProduct().getSku(),
pu.getQuantity(),
event.getFromLoadUnit().getPhysicalPosition(),
event.getFromLoadUnit().getTransportUnit().getTransportUnitBK().getValue(),
event.getFromLoadUnit().getTransportUnit().getActualLocation().getErpCode()
))
.withDetail("puPKey", pu.getPersistentKey())
.withDetail("sku", pu.getProduct().getSku())
.withDetail("qty", "" + pu.getQuantity().getMagnitude().intValue())
.withDetail("loadUnitPosition", event.getFromLoadUnit().getPhysicalPosition())
.withDetail("actualLocationErpCode", event.getFromLoadUnit().getTransportUnit().getActualLocation().getErpCode())
.withDetail("transportUnitBK", event.getFromLoadUnit().getTransportUnit().getTransportUnitBK().getValue())
.build()
));
} else {
LOGGER.error(format("A PackagingUnit [%s] was deleted but no information about the last Location or the LoadUnit was provided",
pu.getPersistentKey()));
}
}
private TransactionBuilder createDefaultBuilder() {
return TransactionBuilder.aTransactionVO()
.withCreatedByUser(IdentityContextHolder.getCurrentIdentity())
.withCategory(CallContextHolder.getOptionalCallContext().map(CallContext::getCaller).orElse(""))
.withSender(applicationName);
}
}