001// -------------------------------------------------------------------------------- 002// Copyright 2002-2026 Echo Three, LLC 003// 004// Licensed under the Apache License, Version 2.0 (the "License"); 005// you may not use this file except in compliance with the License. 006// You may obtain a copy of the License at 007// 008// http://www.apache.org/licenses/LICENSE-2.0 009// 010// Unless required by applicable law or agreed to in writing, software 011// distributed under the License is distributed on an "AS IS" BASIS, 012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013// See the License for the specific language governing permissions and 014// limitations under the License. 015// -------------------------------------------------------------------------------- 016 017package com.echothree.model.control.sales.server.logic; 018 019import com.echothree.control.user.sales.common.spec.SalesOrderBatchUniversalSpec; 020import com.echothree.model.control.batch.common.BatchTypes; 021import com.echothree.model.control.batch.common.exception.InvalidBatchTypeException; 022import com.echothree.model.control.batch.server.control.BatchControl; 023import com.echothree.model.control.batch.server.logic.BatchLogic; 024import com.echothree.model.control.core.common.ComponentVendors; 025import com.echothree.model.control.core.common.EntityTypes; 026import com.echothree.model.control.core.common.exception.InvalidParameterCountException; 027import com.echothree.model.control.core.server.control.EntityInstanceControl; 028import com.echothree.model.control.core.server.logic.EntityInstanceLogic; 029import com.echothree.model.control.order.server.control.OrderBatchControl; 030import com.echothree.model.control.order.server.control.OrderControl; 031import com.echothree.model.control.sales.common.choice.SalesOrderBatchStatusChoicesBean; 032import com.echothree.model.control.sales.common.exception.CannotDeleteSalesOrderBatchInUseException; 033import com.echothree.model.control.sales.common.exception.IncorrectSalesOrderBatchAmountException; 034import com.echothree.model.control.sales.common.exception.IncorrectSalesOrderBatchCountException; 035import com.echothree.model.control.sales.common.exception.InvalidSalesOrderBatchStatusException; 036import com.echothree.model.control.sales.common.exception.InvalidSalesOrderStatusException; 037import com.echothree.model.control.sales.common.exception.UnknownSalesOrderBatchStatusChoiceException; 038import com.echothree.model.control.sales.common.workflow.SalesOrderBatchStatusConstants; 039import com.echothree.model.control.sales.common.workflow.SalesOrderStatusConstants; 040import com.echothree.model.control.sales.server.control.SalesOrderBatchControl; 041import com.echothree.model.control.workflow.server.control.WorkflowControl; 042import com.echothree.model.control.workflow.server.logic.WorkflowDestinationLogic; 043import com.echothree.model.control.workflow.server.logic.WorkflowLogic; 044import com.echothree.model.control.workflow.server.logic.WorkflowStepLogic; 045import com.echothree.model.data.accounting.server.entity.Currency; 046import com.echothree.model.data.batch.server.entity.Batch; 047import com.echothree.model.data.batch.server.entity.BatchEntity; 048import com.echothree.model.data.order.server.entity.Order; 049import com.echothree.model.data.party.common.pk.PartyPK; 050import com.echothree.model.data.party.server.entity.Language; 051import com.echothree.model.data.payment.server.entity.PaymentMethod; 052import com.echothree.util.common.message.ExecutionErrors; 053import com.echothree.util.common.persistence.BasePK; 054import com.echothree.util.server.control.BaseLogic; 055import com.echothree.util.server.message.ExecutionErrorAccumulator; 056import com.echothree.util.server.persistence.EntityPermission; 057import com.echothree.util.server.persistence.Session; 058import com.echothree.util.server.string.AmountUtils; 059import java.util.ArrayList; 060import java.util.List; 061import javax.enterprise.context.ApplicationScoped; 062import javax.enterprise.inject.spi.CDI; 063import javax.inject.Inject; 064 065@ApplicationScoped 066public class SalesOrderBatchLogic 067 extends BaseLogic { 068 069 @Inject 070 BatchControl batchControl; 071 072 @Inject 073 EntityInstanceControl entityInstanceControl; 074 075 @Inject 076 OrderBatchControl orderBatchControl; 077 078 @Inject 079 OrderControl orderControl; 080 081 @Inject 082 SalesOrderBatchControl salesOrderBatchControl; 083 084 @Inject 085 WorkflowControl workflowControl; 086 087 @Inject 088 BatchLogic batchLogic; 089 090 @Inject 091 EntityInstanceLogic entityInstanceLogic; 092 093 @Inject 094 SalesOrderLineLogic salesOrderLineLogic; 095 096 @Inject 097 WorkflowDestinationLogic workflowDestinationLogic; 098 099 @Inject 100 WorkflowLogic workflowLogic; 101 102 @Inject 103 WorkflowStepLogic workflowStepLogic; 104 105 protected SalesOrderBatchLogic() { 106 super(); 107 } 108 109 public static SalesOrderBatchLogic getInstance() { 110 return CDI.current().select(SalesOrderBatchLogic.class).get(); 111 } 112 113 public Batch createBatch(final ExecutionErrorAccumulator eea, final Currency currency, final PaymentMethod paymentMethod, final Long count, 114 final Long amount, final BasePK createdBy) { 115 var batch = batchLogic.createBatch(eea, BatchTypes.SALES_ORDER.name(), createdBy); 116 117 if(eea == null || !eea.hasExecutionErrors()) { 118 orderBatchControl.createOrderBatch(batch, currency, count, amount, createdBy); 119 salesOrderBatchControl.createSalesOrderBatch(batch, paymentMethod, createdBy); 120 } 121 122 return batch; 123 } 124 125 public boolean checkBatchInWorkflowSteps(final ExecutionErrorAccumulator eea, final Batch batch, final String... workflowStepNames) { 126 return !workflowStepLogic.isEntityInWorkflowSteps(eea, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, 127 getEntityInstanceByBaseEntity(batch), workflowStepNames).isEmpty(); 128 } 129 130 public boolean checkBatchAvailableForEntry(final ExecutionErrorAccumulator eea, final Batch batch) { 131 return checkBatchInWorkflowSteps(eea, batch, SalesOrderBatchStatusConstants.WorkflowStep_ENTRY); 132 } 133 134 public BatchEntity createBatchEntity(final ExecutionErrorAccumulator eea, final Order order, final Batch batch, final BasePK createdBy) { 135 return batchLogic.createBatchEntity(eea, getEntityInstanceByBaseEntity(order), batch, createdBy); 136 } 137 138 public boolean batchEntryExists(final ExecutionErrorAccumulator eea, final Order order, final Batch batch, final String... workflowStepNames) { 139 var result = false; 140 141 if(workflowStepNames.length == 0) { 142 result = batchLogic.batchEntityExists(order, batch); 143 } else { 144 if(!workflowStepLogic.isEntityInWorkflowSteps(eea, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, 145 getEntityInstanceByBaseEntity(order), workflowStepNames).isEmpty()) { 146 result = true; 147 } else { 148 handleExecutionError(InvalidSalesOrderBatchStatusException.class, eea, ExecutionErrors.InvalidSalesOrderBatchStatus.name(), 149 batch.getLastDetail().getBatchName()); 150 } 151 } 152 153 return result; 154 } 155 156 public void deleteBatch(final ExecutionErrorAccumulator eea, final Batch batch, final BasePK deletedBy) { 157 if(batchControl.countBatchEntitiesByBatch(batch) == 0) { 158 batchLogic.deleteBatch(eea, batch, deletedBy); 159 160 if(eea == null || !eea.hasExecutionErrors()) { 161 orderBatchControl.deleteOrderBatch(batch, deletedBy); 162 salesOrderBatchControl.deleteSalesOrderBatch(batch, deletedBy); 163 } 164 } else { 165 handleExecutionError(CannotDeleteSalesOrderBatchInUseException.class, eea, ExecutionErrors.CannotDeleteSalesOrderBatchInUse.name(), 166 batch.getLastDetail().getBatchName()); 167 } 168 } 169 170 public Batch getBatchByName(final ExecutionErrorAccumulator eea, final String batchName, final EntityPermission entityPermission) { 171 return batchLogic.getBatchByName(eea, BatchTypes.SALES_ORDER.name(), batchName, entityPermission); 172 } 173 174 public Batch getBatchByName(final ExecutionErrorAccumulator eea, final String batchName) { 175 return getBatchByName(eea, batchName, EntityPermission.READ_ONLY); 176 } 177 178 public Batch getBatchByNameForUpdate(final ExecutionErrorAccumulator eea, final String batchName) { 179 return getBatchByName(eea, batchName, EntityPermission.READ_WRITE); 180 } 181 182 public Batch getBatchByUniversalSpec(final ExecutionErrorAccumulator eea, 183 final SalesOrderBatchUniversalSpec universalSpec, final EntityPermission entityPermission) { 184 Batch batch = null; 185 var batchName = universalSpec.getBatchName(); 186 var parameterCount = (batchName == null ? 0 : 1) + entityInstanceLogic.countPossibleEntitySpecs(universalSpec); 187 188 switch(parameterCount) { 189 case 1 -> { 190 if(batchName == null) { 191 var entityInstance = entityInstanceLogic.getEntityInstance(eea, universalSpec, 192 ComponentVendors.ECHO_THREE.name(), EntityTypes.Batch.name()); 193 194 if(eea == null || !eea.hasExecutionErrors()) { 195 batch = batchControl.getBatchByEntityInstance(entityInstance, entityPermission); 196 } 197 } else { 198 batch = getBatchByName(eea, batchName, entityPermission); 199 } 200 201 if(!hasExecutionErrors(eea)) { 202 var batchTypeName = batch.getLastDetail().getBatchType().getLastDetail().getBatchTypeName(); 203 204 if(!batchTypeName.equals(BatchTypes.SALES_ORDER.name())) { 205 batch = null; 206 // Purposefully disclose nothing about the BatchType of the Batch that was found. 207 handleExecutionError(InvalidBatchTypeException.class, eea, ExecutionErrors.InvalidBatchType.name()); 208 } 209 } 210 } 211 default -> 212 handleExecutionError(InvalidParameterCountException.class, eea, ExecutionErrors.InvalidParameterCount.name()); 213 } 214 215 return batch; 216 } 217 218 public Batch getBatchByUniversalSpec(final ExecutionErrorAccumulator eea, 219 final SalesOrderBatchUniversalSpec universalSpec) { 220 return getBatchByUniversalSpec(eea, universalSpec, EntityPermission.READ_ONLY); 221 } 222 223 public Batch getBatchByUniversalSpecForUpdate(final ExecutionErrorAccumulator eea, 224 final SalesOrderBatchUniversalSpec universalSpec) { 225 return getBatchByUniversalSpec(eea, universalSpec, EntityPermission.READ_WRITE); 226 } 227 228 public SalesOrderBatchStatusChoicesBean getSalesOrderBatchStatusChoices(String defaultSalesOrderBatchStatusChoice, Language language, boolean allowNullChoice, 229 Batch batch, PartyPK partyPK) { 230 var salesOrderBatchStatusChoicesBean = new SalesOrderBatchStatusChoicesBean(); 231 232 if(batch == null) { 233 workflowControl.getWorkflowEntranceChoices(salesOrderBatchStatusChoicesBean, defaultSalesOrderBatchStatusChoice, language, allowNullChoice, 234 workflowControl.getWorkflowByName(SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS), partyPK); 235 } else { 236 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(batch.getPrimaryKey()); 237 var workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceUsingNames(SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, 238 entityInstance); 239 240 workflowControl.getWorkflowDestinationChoices(salesOrderBatchStatusChoicesBean, defaultSalesOrderBatchStatusChoice, language, allowNullChoice, 241 workflowEntityStatus.getWorkflowStep(), partyPK); 242 } 243 244 return salesOrderBatchStatusChoicesBean; 245 } 246 247 public void setSalesOrderBatchStatus(final Session session, ExecutionErrorAccumulator eea, Batch batch, String salesOrderBatchStatusChoice, PartyPK modifiedBy) { 248 var workflow = workflowLogic.getWorkflowByName(eea, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS); 249 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(batch.getPrimaryKey()); 250 var workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceForUpdate(workflow, entityInstance); 251 var workflowDestination = salesOrderBatchStatusChoice == null ? null : workflowControl.getWorkflowDestinationByName(workflowEntityStatus.getWorkflowStep(), salesOrderBatchStatusChoice); 252 253 if(workflowDestination != null || salesOrderBatchStatusChoice == null) { 254 var currentWorkflowStepName = workflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName(); 255 var map = workflowDestinationLogic.getWorkflowDestinationsAsMap(workflowDestination); 256 Long triggerTime = null; 257 258 if(currentWorkflowStepName.equals(SalesOrderBatchStatusConstants.WorkflowStep_ENTRY)) { 259 if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, SalesOrderBatchStatusConstants.WorkflowStep_AUDIT)) { 260 var batchEntities = batchControl.getBatchEntitiesByBatch(batch); 261 262 // Verify all orders are in BATCH_AUDIT. 263 batchEntities.stream().map((batchEntity) -> workflowControl.getWorkflowEntityStatusByEntityInstanceUsingNames(SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, batchEntity.getEntityInstance())).forEach((orderWorkflowEntityStatus) -> { 264 var orderEntityInstance = orderWorkflowEntityStatus.getEntityInstance(); 265 var orderWorkflowStepName = orderWorkflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName(); 266 if (!orderWorkflowStepName.equals(SalesOrderStatusConstants.WorkflowStep_BATCH_AUDIT)) { 267 var order = orderControl.getOrderByEntityInstance(orderEntityInstance); 268 269 handleExecutionError(InvalidSalesOrderStatusException.class, eea, ExecutionErrors.InvalidSalesOrderStatus.name(), 270 order.getLastDetail().getOrderName(), orderWorkflowStepName); 271 } 272 }); 273 } 274 } else if(currentWorkflowStepName.equals(SalesOrderBatchStatusConstants.WorkflowStep_AUDIT)) { 275 if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, SalesOrderBatchStatusConstants.WorkflowStep_COMPLETE)) { 276 var orderBatch = orderBatchControl.getOrderBatch(batch); 277 var count = orderBatch.getCount(); 278 var amount = orderBatch.getAmount(); 279 280 if(count != null) { 281 Long batchCount = batchControl.countBatchEntitiesByBatch(batch); 282 283 if(!count.equals(batchCount)) { 284 handleExecutionError(IncorrectSalesOrderBatchCountException.class, eea, ExecutionErrors.IncorrectSalesOrderBatchCount.name(), 285 count.toString(), batchCount.toString()); 286 } 287 } 288 289 if(amount != null) { 290 var batchAmount = getBatchOrderTotalsWithAdjustments(batch); 291 292 if(!amount.equals(batchAmount)) { 293 var currency = orderBatch.getCurrency(); 294 295 handleExecutionError(IncorrectSalesOrderBatchAmountException.class, eea, ExecutionErrors.IncorrectSalesOrderBatchAmount.name(), 296 AmountUtils.getInstance().formatPriceUnit(currency, amount), 297 AmountUtils.getInstance().formatPriceUnit(currency, batchAmount)); 298 } 299 } 300 } 301 } 302 303 if(eea == null || !eea.hasExecutionErrors()) { 304 workflowControl.transitionEntityInWorkflow(eea, workflowEntityStatus, workflowDestination, triggerTime, modifiedBy); 305 } 306 } else { 307 handleExecutionError(UnknownSalesOrderBatchStatusChoiceException.class, eea, ExecutionErrors.UnknownSalesOrderBatchStatusChoice.name(), salesOrderBatchStatusChoice); 308 } 309 } 310 311 public List<Order> getBatchOrders(final Batch batch) { 312 var batchEntities = batchControl.getBatchEntitiesByBatch(batch); 313 List<Order> orders = new ArrayList<>(batchEntities.size()); 314 315 batchEntities.forEach((batchEntity) -> { 316 orders.add(orderControl.getOrderByEntityInstance(batchEntity.getEntityInstance())); 317 }); 318 319 return orders; 320 } 321 322 public Long getBatchOrderTotalsWithAdjustments(Batch batch) { 323 var orders = getBatchOrders(batch); 324 long total = 0; 325 326 total = orders.stream().map((order) -> salesOrderLineLogic.getOrderTotalWithAdjustments(order)).reduce(total, (accumulator, _item) -> accumulator + _item); 327 328 return total; 329 } 330 331}