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.inject.Inject; 063 064@ApplicationScoped 065public class SalesOrderBatchLogic 066 extends BaseLogic { 067 068 @Inject 069 BatchControl batchControl; 070 071 @Inject 072 EntityInstanceControl entityInstanceControl; 073 074 @Inject 075 OrderBatchControl orderBatchControl; 076 077 @Inject 078 OrderControl orderControl; 079 080 @Inject 081 SalesOrderBatchControl salesOrderBatchControl; 082 083 @Inject 084 WorkflowControl workflowControl; 085 086 @Inject 087 BatchLogic batchLogic; 088 089 @Inject 090 EntityInstanceLogic entityInstanceLogic; 091 092 @Inject 093 SalesOrderLineLogic salesOrderLineLogic; 094 095 @Inject 096 WorkflowDestinationLogic workflowDestinationLogic; 097 098 @Inject 099 WorkflowLogic workflowLogic; 100 101 @Inject 102 WorkflowStepLogic workflowStepLogic; 103 104 protected SalesOrderBatchLogic() { 105 super(); 106 } 107 108 public Batch createBatch(final ExecutionErrorAccumulator eea, final Currency currency, final PaymentMethod paymentMethod, final Long count, 109 final Long amount, final BasePK createdBy) { 110 var batch = batchLogic.createBatch(eea, BatchTypes.SALES_ORDER.name(), createdBy); 111 112 if(eea == null || !eea.hasExecutionErrors()) { 113 orderBatchControl.createOrderBatch(batch, currency, count, amount, createdBy); 114 salesOrderBatchControl.createSalesOrderBatch(batch, paymentMethod, createdBy); 115 } 116 117 return batch; 118 } 119 120 public boolean checkBatchInWorkflowSteps(final ExecutionErrorAccumulator eea, final Batch batch, final String... workflowStepNames) { 121 return !workflowStepLogic.isEntityInWorkflowSteps(eea, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, 122 getEntityInstanceByBaseEntity(batch), workflowStepNames).isEmpty(); 123 } 124 125 public boolean checkBatchAvailableForEntry(final ExecutionErrorAccumulator eea, final Batch batch) { 126 return checkBatchInWorkflowSteps(eea, batch, SalesOrderBatchStatusConstants.WorkflowStep_ENTRY); 127 } 128 129 public BatchEntity createBatchEntity(final ExecutionErrorAccumulator eea, final Order order, final Batch batch, final BasePK createdBy) { 130 return batchLogic.createBatchEntity(eea, getEntityInstanceByBaseEntity(order), batch, createdBy); 131 } 132 133 public boolean batchEntryExists(final ExecutionErrorAccumulator eea, final Order order, final Batch batch, final String... workflowStepNames) { 134 var result = false; 135 136 if(workflowStepNames.length == 0) { 137 result = batchLogic.batchEntityExists(order, batch); 138 } else { 139 if(!workflowStepLogic.isEntityInWorkflowSteps(eea, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, 140 getEntityInstanceByBaseEntity(order), workflowStepNames).isEmpty()) { 141 result = true; 142 } else { 143 handleExecutionError(InvalidSalesOrderBatchStatusException.class, eea, ExecutionErrors.InvalidSalesOrderBatchStatus.name(), 144 batch.getLastDetail().getBatchName()); 145 } 146 } 147 148 return result; 149 } 150 151 public void deleteBatch(final ExecutionErrorAccumulator eea, final Batch batch, final BasePK deletedBy) { 152 if(batchControl.countBatchEntitiesByBatch(batch) == 0) { 153 batchLogic.deleteBatch(eea, batch, deletedBy); 154 155 if(eea == null || !eea.hasExecutionErrors()) { 156 orderBatchControl.deleteOrderBatch(batch, deletedBy); 157 salesOrderBatchControl.deleteSalesOrderBatch(batch, deletedBy); 158 } 159 } else { 160 handleExecutionError(CannotDeleteSalesOrderBatchInUseException.class, eea, ExecutionErrors.CannotDeleteSalesOrderBatchInUse.name(), 161 batch.getLastDetail().getBatchName()); 162 } 163 } 164 165 public Batch getBatchByName(final ExecutionErrorAccumulator eea, final String batchName, final EntityPermission entityPermission) { 166 return batchLogic.getBatchByName(eea, BatchTypes.SALES_ORDER.name(), batchName, entityPermission); 167 } 168 169 public Batch getBatchByName(final ExecutionErrorAccumulator eea, final String batchName) { 170 return getBatchByName(eea, batchName, EntityPermission.READ_ONLY); 171 } 172 173 public Batch getBatchByNameForUpdate(final ExecutionErrorAccumulator eea, final String batchName) { 174 return getBatchByName(eea, batchName, EntityPermission.READ_WRITE); 175 } 176 177 public Batch getBatchByUniversalSpec(final ExecutionErrorAccumulator eea, 178 final SalesOrderBatchUniversalSpec universalSpec, final EntityPermission entityPermission) { 179 Batch batch = null; 180 var batchName = universalSpec.getBatchName(); 181 var parameterCount = (batchName == null ? 0 : 1) + entityInstanceLogic.countPossibleEntitySpecs(universalSpec); 182 183 switch(parameterCount) { 184 case 1 -> { 185 if(batchName == null) { 186 var entityInstance = entityInstanceLogic.getEntityInstance(eea, universalSpec, 187 ComponentVendors.ECHO_THREE.name(), EntityTypes.Batch.name()); 188 189 if(eea == null || !eea.hasExecutionErrors()) { 190 batch = batchControl.getBatchByEntityInstance(entityInstance, entityPermission); 191 } 192 } else { 193 batch = getBatchByName(eea, batchName, entityPermission); 194 } 195 196 if(!hasExecutionErrors(eea)) { 197 var batchTypeName = batch.getLastDetail().getBatchType().getLastDetail().getBatchTypeName(); 198 199 if(!batchTypeName.equals(BatchTypes.SALES_ORDER.name())) { 200 batch = null; 201 // Purposefully disclose nothing about the BatchType of the Batch that was found. 202 handleExecutionError(InvalidBatchTypeException.class, eea, ExecutionErrors.InvalidBatchType.name()); 203 } 204 } 205 } 206 default -> 207 handleExecutionError(InvalidParameterCountException.class, eea, ExecutionErrors.InvalidParameterCount.name()); 208 } 209 210 return batch; 211 } 212 213 public Batch getBatchByUniversalSpec(final ExecutionErrorAccumulator eea, 214 final SalesOrderBatchUniversalSpec universalSpec) { 215 return getBatchByUniversalSpec(eea, universalSpec, EntityPermission.READ_ONLY); 216 } 217 218 public Batch getBatchByUniversalSpecForUpdate(final ExecutionErrorAccumulator eea, 219 final SalesOrderBatchUniversalSpec universalSpec) { 220 return getBatchByUniversalSpec(eea, universalSpec, EntityPermission.READ_WRITE); 221 } 222 223 public SalesOrderBatchStatusChoicesBean getSalesOrderBatchStatusChoices(String defaultSalesOrderBatchStatusChoice, Language language, boolean allowNullChoice, 224 Batch batch, PartyPK partyPK) { 225 var salesOrderBatchStatusChoicesBean = new SalesOrderBatchStatusChoicesBean(); 226 227 if(batch == null) { 228 workflowControl.getWorkflowEntranceChoices(salesOrderBatchStatusChoicesBean, defaultSalesOrderBatchStatusChoice, language, allowNullChoice, 229 workflowControl.getWorkflowByName(SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS), partyPK); 230 } else { 231 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(batch.getPrimaryKey()); 232 var workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceUsingNames(SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, 233 entityInstance); 234 235 workflowControl.getWorkflowDestinationChoices(salesOrderBatchStatusChoicesBean, defaultSalesOrderBatchStatusChoice, language, allowNullChoice, 236 workflowEntityStatus.getWorkflowStep(), partyPK); 237 } 238 239 return salesOrderBatchStatusChoicesBean; 240 } 241 242 public void setSalesOrderBatchStatus(final Session session, ExecutionErrorAccumulator eea, Batch batch, String salesOrderBatchStatusChoice, PartyPK modifiedBy) { 243 var workflow = workflowLogic.getWorkflowByName(eea, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS); 244 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(batch.getPrimaryKey()); 245 var workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceForUpdate(workflow, entityInstance); 246 var workflowDestination = salesOrderBatchStatusChoice == null ? null : workflowControl.getWorkflowDestinationByName(workflowEntityStatus.getWorkflowStep(), salesOrderBatchStatusChoice); 247 248 if(workflowDestination != null || salesOrderBatchStatusChoice == null) { 249 var currentWorkflowStepName = workflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName(); 250 var map = workflowDestinationLogic.getWorkflowDestinationsAsMap(workflowDestination); 251 Long triggerTime = null; 252 253 if(currentWorkflowStepName.equals(SalesOrderBatchStatusConstants.WorkflowStep_ENTRY)) { 254 if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, SalesOrderBatchStatusConstants.WorkflowStep_AUDIT)) { 255 var batchEntities = batchControl.getBatchEntitiesByBatch(batch); 256 257 // Verify all orders are in BATCH_AUDIT. 258 batchEntities.stream().map((batchEntity) -> workflowControl.getWorkflowEntityStatusByEntityInstanceUsingNames(SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, batchEntity.getEntityInstance())).forEach((orderWorkflowEntityStatus) -> { 259 var orderEntityInstance = orderWorkflowEntityStatus.getEntityInstance(); 260 var orderWorkflowStepName = orderWorkflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName(); 261 if (!orderWorkflowStepName.equals(SalesOrderStatusConstants.WorkflowStep_BATCH_AUDIT)) { 262 var order = orderControl.getOrderByEntityInstance(orderEntityInstance); 263 264 handleExecutionError(InvalidSalesOrderStatusException.class, eea, ExecutionErrors.InvalidSalesOrderStatus.name(), 265 order.getLastDetail().getOrderName(), orderWorkflowStepName); 266 } 267 }); 268 } 269 } else if(currentWorkflowStepName.equals(SalesOrderBatchStatusConstants.WorkflowStep_AUDIT)) { 270 if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, SalesOrderBatchStatusConstants.WorkflowStep_COMPLETE)) { 271 var orderBatch = orderBatchControl.getOrderBatch(batch); 272 var count = orderBatch.getCount(); 273 var amount = orderBatch.getAmount(); 274 275 if(count != null) { 276 Long batchCount = batchControl.countBatchEntitiesByBatch(batch); 277 278 if(!count.equals(batchCount)) { 279 handleExecutionError(IncorrectSalesOrderBatchCountException.class, eea, ExecutionErrors.IncorrectSalesOrderBatchCount.name(), 280 count.toString(), batchCount.toString()); 281 } 282 } 283 284 if(amount != null) { 285 var batchAmount = getBatchOrderTotalsWithAdjustments(batch); 286 287 if(!amount.equals(batchAmount)) { 288 var currency = orderBatch.getCurrency(); 289 290 handleExecutionError(IncorrectSalesOrderBatchAmountException.class, eea, ExecutionErrors.IncorrectSalesOrderBatchAmount.name(), 291 AmountUtils.getInstance().formatPriceUnit(currency, amount), 292 AmountUtils.getInstance().formatPriceUnit(currency, batchAmount)); 293 } 294 } 295 } 296 } 297 298 if(eea == null || !eea.hasExecutionErrors()) { 299 workflowControl.transitionEntityInWorkflow(eea, workflowEntityStatus, workflowDestination, triggerTime, modifiedBy); 300 } 301 } else { 302 handleExecutionError(UnknownSalesOrderBatchStatusChoiceException.class, eea, ExecutionErrors.UnknownSalesOrderBatchStatusChoice.name(), salesOrderBatchStatusChoice); 303 } 304 } 305 306 public List<Order> getBatchOrders(final Batch batch) { 307 var batchEntities = batchControl.getBatchEntitiesByBatch(batch); 308 List<Order> orders = new ArrayList<>(batchEntities.size()); 309 310 batchEntities.forEach((batchEntity) -> { 311 orders.add(orderControl.getOrderByEntityInstance(batchEntity.getEntityInstance())); 312 }); 313 314 return orders; 315 } 316 317 public Long getBatchOrderTotalsWithAdjustments(Batch batch) { 318 var orders = getBatchOrders(batch); 319 long total = 0; 320 321 total = orders.stream().map((order) -> salesOrderLineLogic.getOrderTotalWithAdjustments(order)).reduce(total, (accumulator, _item) -> accumulator + _item); 322 323 return total; 324 } 325 326}