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.model.control.accounting.common.exception.InvalidCurrencyException; 020import com.echothree.model.control.accounting.server.logic.CurrencyLogic; 021import com.echothree.model.control.associate.server.logic.AssociateReferralLogic; 022import com.echothree.model.control.batch.server.logic.BatchLogic; 023import com.echothree.model.control.cancellationpolicy.common.CancellationKinds; 024import com.echothree.model.control.cancellationpolicy.server.logic.CancellationPolicyLogic; 025import com.echothree.model.control.core.server.control.EntityInstanceControl; 026import com.echothree.model.control.customer.common.exception.MissingDefaultCustomerTypeException; 027import com.echothree.model.control.customer.server.control.CustomerControl; 028import com.echothree.model.control.offer.common.exception.MissingDefaultSourceException; 029import com.echothree.model.control.offer.server.control.OfferControl; 030import com.echothree.model.control.offer.server.control.SourceControl; 031import com.echothree.model.control.offer.server.logic.SourceLogic; 032import com.echothree.model.control.order.common.OrderRoleTypes; 033import com.echothree.model.control.order.common.OrderTypes; 034import com.echothree.model.control.order.common.exception.MissingDefaultOrderPriorityException; 035import com.echothree.model.control.order.common.exception.MissingRequiredBillToPartyException; 036import com.echothree.model.control.order.server.control.OrderBatchControl; 037import com.echothree.model.control.order.server.control.OrderControl; 038import com.echothree.model.control.order.server.control.OrderPriorityControl; 039import com.echothree.model.control.order.server.control.OrderRoleControl; 040import com.echothree.model.control.order.server.logic.BaseOrderLogic; 041import com.echothree.model.control.party.common.PartyTypes; 042import com.echothree.model.control.party.server.logic.PartyLogic; 043import com.echothree.model.control.returnpolicy.common.ReturnKinds; 044import com.echothree.model.control.returnpolicy.server.logic.ReturnPolicyLogic; 045import com.echothree.model.control.sales.common.choice.SalesOrderStatusChoicesBean; 046import com.echothree.model.control.sales.common.exception.InvalidSalesOrderBatchStatusException; 047import com.echothree.model.control.sales.common.exception.InvalidSalesOrderReferenceException; 048import com.echothree.model.control.sales.common.exception.InvalidSalesOrderStatusException; 049import com.echothree.model.control.sales.common.exception.SalesOrderDuplicateReferenceException; 050import com.echothree.model.control.sales.common.exception.SalesOrderReferenceRequiredException; 051import com.echothree.model.control.sales.common.exception.UnknownSalesOrderStatusChoiceException; 052import com.echothree.model.control.sales.common.workflow.SalesOrderStatusConstants; 053import com.echothree.model.control.sales.server.control.SalesOrderControl; 054import com.echothree.model.control.shipment.server.control.PartyFreeOnBoardControl; 055import com.echothree.model.control.shipment.server.logic.FreeOnBoardLogic; 056import com.echothree.model.control.term.server.control.TermControl; 057import com.echothree.model.control.term.server.logic.TermLogic; 058import com.echothree.model.control.user.server.control.UserControl; 059import com.echothree.model.control.workflow.server.control.WorkflowControl; 060import com.echothree.model.control.workflow.server.logic.WorkflowDestinationLogic; 061import com.echothree.model.control.workflow.server.logic.WorkflowLogic; 062import com.echothree.model.control.workflow.server.logic.WorkflowStepLogic; 063import com.echothree.model.data.accounting.server.entity.Currency; 064import com.echothree.model.data.batch.server.entity.Batch; 065import com.echothree.model.data.cancellationpolicy.server.entity.CancellationPolicy; 066import com.echothree.model.data.core.server.entity.EntityInstance; 067import com.echothree.model.data.customer.server.entity.Customer; 068import com.echothree.model.data.customer.server.entity.CustomerType; 069import com.echothree.model.data.offer.server.entity.Offer; 070import com.echothree.model.data.offer.server.entity.Source; 071import com.echothree.model.data.order.server.entity.Order; 072import com.echothree.model.data.order.server.entity.OrderPriority; 073import com.echothree.model.data.party.common.pk.PartyPK; 074import com.echothree.model.data.party.server.entity.Language; 075import com.echothree.model.data.party.server.entity.Party; 076import com.echothree.model.data.returnpolicy.server.entity.ReturnPolicy; 077import com.echothree.model.data.shipment.server.entity.FreeOnBoard; 078import com.echothree.model.data.term.server.entity.Term; 079import com.echothree.model.data.user.server.entity.UserVisit; 080import com.echothree.util.common.message.ExecutionErrors; 081import com.echothree.util.common.persistence.BasePK; 082import com.echothree.util.server.message.ExecutionErrorAccumulator; 083import com.echothree.util.server.persistence.Session; 084import javax.enterprise.context.ApplicationScoped; 085import javax.enterprise.inject.spi.CDI; 086import javax.inject.Inject; 087 088@ApplicationScoped 089public class SalesOrderLogic 090 extends BaseOrderLogic { 091 092 @Inject 093 CustomerControl customerControl; 094 095 @Inject 096 EntityInstanceControl entityInstanceControl; 097 098 @Inject 099 OfferControl offerControl; 100 101 @Inject 102 OrderBatchControl orderBatchControl; 103 104 @Inject 105 OrderControl orderControl; 106 107 @Inject 108 OrderPriorityControl orderPriorityControl; 109 110 @Inject 111 OrderRoleControl orderRoleControl; 112 113 @Inject 114 PartyFreeOnBoardControl partyFreeOnBoardControl; 115 116 @Inject 117 SalesOrderControl salesOrderControl; 118 119 @Inject 120 SourceControl sourceControl; 121 122 @Inject 123 TermControl termControl; 124 125 @Inject 126 UserControl userControl; 127 128 @Inject 129 WorkflowControl workflowControl; 130 131 @Inject 132 AssociateReferralLogic associateReferralLogic; 133 134 @Inject 135 BatchLogic batchLogic; 136 137 @Inject 138 CancellationPolicyLogic cancellationPolicyLogic; 139 140 @Inject 141 CurrencyLogic currencyLogic; 142 143 @Inject 144 FreeOnBoardLogic freeOnBoardLogic; 145 146 @Inject 147 PartyLogic partyLogic; 148 149 @Inject 150 ReturnPolicyLogic returnPolicyLogic; 151 152 @Inject 153 SalesOrderBatchLogic salesOrderBatchLogic; 154 155 @Inject 156 SalesOrderPriorityLogic salesOrderPriorityLogic; 157 158 @Inject 159 SourceLogic sourceLogic; 160 161 @Inject 162 TermLogic termLogic; 163 164 @Inject 165 WorkflowDestinationLogic workflowDestinationLogic; 166 167 @Inject 168 WorkflowLogic workflowLogic; 169 170 @Inject 171 WorkflowStepLogic workflowStepLogic; 172 173 protected SalesOrderLogic() { 174 super(); 175 } 176 177 public static SalesOrderLogic getInstance() { 178 return CDI.current().select(SalesOrderLogic.class).get(); 179 } 180 181 final static long AllocatedInventoryTimeout = 5 * 60 * 1000; // 5 Minutes 182 183 public CustomerType getCustomerType(final ExecutionErrorAccumulator eea, final Offer offer, final Customer customer) { 184 CustomerType customerType = null; 185 186 // 1) Try to get it from the customer, if one was supplied. 187 if(customer != null) { 188 customerType = customer.getCustomerType(); 189 } 190 191 // 2) Try to get it from the offer, if one was supplied. 192 if(customerType == null && offer != null) { 193 var offerCustomerType = offerControl.getDefaultOfferCustomerType(offer); 194 195 if(offerCustomerType != null) { 196 customerType = offerCustomerType.getCustomerType(); 197 } 198 } 199 200 // 3) Try to get the default CustomerType, error if it isn't available. 201 if(customerType == null) { 202 customerType = customerControl.getDefaultCustomerType(); 203 204 if(customerType == null) { 205 handleExecutionError(MissingDefaultCustomerTypeException.class, eea, ExecutionErrors.MissingDefaultCustomerType.name()); 206 } 207 } 208 209 return customerType; 210 } 211 212 public void validateSalesOrderReference(final ExecutionErrorAccumulator eea, final String reference, final CustomerType customerType, final Customer billToCustomer) { 213 Boolean requireReference = null; 214 Boolean allowReferenceDuplicates = null; 215 String referenceValidationPattern = null; 216 217 if(billToCustomer != null) { 218 requireReference = billToCustomer.getRequireReference(); 219 allowReferenceDuplicates = billToCustomer.getAllowReferenceDuplicates(); 220 referenceValidationPattern = billToCustomer.getReferenceValidationPattern(); 221 } else if(customerType != null) { 222 var customerTypeDetail = customerType.getLastDetail(); 223 224 requireReference = customerTypeDetail.getDefaultRequireReference(); 225 allowReferenceDuplicates = customerTypeDetail.getDefaultAllowReferenceDuplicates(); 226 referenceValidationPattern = customerTypeDetail.getDefaultReferenceValidationPattern(); 227 } 228 229 if(requireReference != null) { 230 if(requireReference && reference == null) { 231 handleExecutionError(SalesOrderReferenceRequiredException.class, eea, ExecutionErrors.SalesOrderReferenceRequired.name()); 232 } else if(reference != null) { 233 if(!allowReferenceDuplicates) { 234 if(billToCustomer == null) { 235 handleExecutionError(MissingRequiredBillToPartyException.class, eea, ExecutionErrors.MissingRequiredBillToParty.name()); 236 } else if(orderControl.countOrdersByBillToAndReference(billToCustomer.getParty(), reference) != 0) { 237 handleExecutionError(SalesOrderDuplicateReferenceException.class, eea, ExecutionErrors.SalesOrderDuplicateReference.name()); 238 } 239 } 240 241 if(referenceValidationPattern != null && !reference.matches(referenceValidationPattern)) { 242 handleExecutionError(InvalidSalesOrderReferenceException.class, eea, ExecutionErrors.InvalidSalesOrderReference.name()); 243 } 244 } 245 } 246 } 247 248 public CancellationPolicy getCancellationPolicy(final ExecutionErrorAccumulator eea, final CustomerType customerType, final Customer billToCustomer) { 249 return cancellationPolicyLogic.getDefaultCancellationPolicyByKind(eea, CancellationKinds.CUSTOMER_CANCELLATION.name(), 250 new CancellationPolicy[]{ 251 billToCustomer == null ? null : billToCustomer.getCancellationPolicy(), 252 customerType.getLastDetail().getDefaultCancellationPolicy() 253 }); 254 } 255 256 public ReturnPolicy getReturnPolicy(final ExecutionErrorAccumulator eea, final CustomerType customerType, final Customer billToCustomer) { 257 return returnPolicyLogic.getDefaultReturnPolicyByKind(eea, ReturnKinds.CUSTOMER_RETURN.name(), 258 new ReturnPolicy[]{ 259 billToCustomer == null ? null : billToCustomer.getReturnPolicy(), 260 customerType.getLastDetail().getDefaultReturnPolicy() 261 }); 262 } 263 264 /** 265 * Create a new Sales Order given a set of parameters. Default values will be determined as best as possible when 266 * a parameter is marked as Optional. If a previous preference has not been set then it may not be possible to 267 * to determine appropriate defaults. 268 * 269 * @param session Required. 270 * @param eea Required. 271 * @param userVisit Required. 272 * @param source Optional. 273 * @param billToParty Optional. 274 * @param orderPriority Optional. 275 * @param currency Optional. 276 * @param holdUntilComplete Optional. 277 * @param allowBackorders Optional. 278 * @param allowSubstitutions Optional. 279 * @param allowCombiningShipments Optional. 280 * @param reference Optional. 281 * @param term Optional. 282 * @param taxable Optional. 283 * @param workflowEntranceName Optional. 284 * @param createdByParty Required. 285 * @return The newly created Order, or null if there was an error. 286 */ 287 public Order createSalesOrder(final Session session, final ExecutionErrorAccumulator eea, final UserVisit userVisit, final Batch batch, Source source, 288 final Party billToParty, OrderPriority orderPriority, Currency currency, Boolean holdUntilComplete, Boolean allowBackorders, Boolean allowSubstitutions, 289 Boolean allowCombiningShipments, final String reference, Term term, FreeOnBoard freeOnBoard, Boolean taxable, final String workflowEntranceName, final Party createdByParty) { 290 var orderType = getOrderTypeByName(eea, OrderTypes.SALES_ORDER.name()); 291 var billToOrderRoleType = getOrderRoleTypeByName(eea, OrderRoleTypes.BILL_TO.name()); 292 var placingOrderRoleType = getOrderRoleTypeByName(eea, OrderRoleTypes.PLACING.name()); 293 Order order = null; 294 295 if(batch != null) { 296 if(salesOrderBatchLogic.checkBatchAvailableForEntry(eea, batch)) { 297 var orderBatchCurrency = orderBatchControl.getOrderBatch(batch).getCurrency(); 298 299 if(currency == null) { 300 currency = orderBatchCurrency; 301 } else { 302 if(!currency.equals(orderBatchCurrency)) { 303 handleExecutionError(InvalidCurrencyException.class, eea, ExecutionErrors.InvalidCurrency.name(), currency.getCurrencyIsoName(), 304 orderBatchCurrency.getCurrencyIsoName()); 305 } 306 } 307 } else { 308 handleExecutionError(InvalidSalesOrderBatchStatusException.class, eea, ExecutionErrors.InvalidSalesOrderBatchStatus.name(), 309 batch.getLastDetail().getBatchName()); 310 } 311 } 312 313 if(eea == null || !eea.hasExecutionErrors()) { 314 if(source == null) { 315 source = sourceControl.getDefaultSource(); 316 317 if(source == null) { 318 handleExecutionError(MissingDefaultSourceException.class, eea, ExecutionErrors.MissingDefaultSource.name()); 319 } 320 } 321 322 if(orderPriority == null) { 323 orderPriority = orderPriorityControl.getDefaultOrderPriority(orderType); 324 325 if(orderPriority == null) { 326 handleExecutionError(MissingDefaultOrderPriorityException.class, eea, ExecutionErrors.MissingDefaultOrderPriority.name(), OrderTypes.SALES_ORDER.name()); 327 } 328 } 329 330 if(currency == null) { 331 if(billToParty != null) { 332 currency = userControl.getPreferredCurrencyFromParty(billToParty); 333 } 334 335 if(currency == null && userVisit != null) { 336 currency = userControl.getPreferredCurrencyFromUserVisit(userVisit); 337 } 338 339 if(currency == null) { 340 currency = currencyLogic.getDefaultCurrency(eea); 341 } 342 } 343 344 if(billToParty != null) { 345 partyLogic.checkPartyType(eea, billToParty, PartyTypes.CUSTOMER.name()); 346 } 347 348 if(eea == null || !eea.hasExecutionErrors()) { 349 var billToCustomer = billToParty == null ? null : customerControl.getCustomer(billToParty); 350 var offerUse = source.getLastDetail().getOfferUse(); 351 var customerType = getCustomerType(eea, offerUse.getLastDetail().getOffer(), billToCustomer); 352 353 if(eea == null || !eea.hasExecutionErrors()) { 354 var cancellationPolicy = getCancellationPolicy(eea, customerType, billToCustomer); 355 var returnPolicy = getReturnPolicy(eea, customerType, billToCustomer); 356 357 if(billToCustomer != null) { 358 validateSalesOrderReference(eea, reference, customerType, billToCustomer); 359 } 360 361 if(eea == null || !eea.hasExecutionErrors()) { 362 var customerTypeDetail = customerType.getLastDetail(); 363 var sequence = offerUse.getLastDetail().getSalesOrderSequence(); 364 var createdBy = createdByParty == null ? null : createdByParty.getPrimaryKey(); 365 366 // If term or taxable were not set, then try to come up with sensible defaults, first from a PartyTerm if it 367 // was available, and then falling back on the CustomerType. 368 if(term == null || taxable == null) { 369 var partyTerm = billToParty == null ? null : termControl.getPartyTerm(billToParty); 370 371 if(partyTerm == null) { 372 if(term == null) { 373 term = customerTypeDetail.getDefaultTerm(); 374 } 375 376 if(taxable == null) { 377 taxable = customerTypeDetail.getDefaultTaxable(); 378 } 379 } else { 380 if(term == null) { 381 term = partyTerm.getTerm(); 382 } 383 384 if(taxable == null) { 385 taxable = partyTerm.getTaxable(); 386 } 387 } 388 389 // If no better answer was found, use the default Term. 390 if(term == null) { 391 termControl.getDefaultTerm(); 392 } 393 394 // If no better answer was found, the order is taxable. 395 if(taxable == null) { 396 taxable = true; 397 } 398 } 399 400 // If FreeOnBoard wasn't specified, using the bill to Party, fir look for a preference for the Party, 401 // and then check the CustomerType. 402 if(freeOnBoard == null) { 403 var partFreeOnBoardControl = partyFreeOnBoardControl; 404 var partyFreeOnBoard = billToParty == null ? null : partFreeOnBoardControl.getPartyFreeOnBoard(billToParty); 405 406 if(partyFreeOnBoard == null) { 407 if(freeOnBoard == null) { 408 freeOnBoard = customerTypeDetail.getDefaultFreeOnBoard(); 409 } 410 } else { 411 if(freeOnBoard == null) { 412 freeOnBoard = partyFreeOnBoard.getFreeOnBoard(); 413 } 414 } 415 416 // If no better answer was found, use the default FreeOnBoard. 417 if(freeOnBoard == null) { 418 freeOnBoard = freeOnBoardLogic.getDefaultFreeOnBoard(eea); 419 } 420 } 421 422 // If any of these flags were not set, try to get them from either the Customer, or the CustomerType. 423 if(holdUntilComplete == null || allowBackorders == null || allowSubstitutions == null || allowCombiningShipments == null) { 424 if(billToCustomer == null) { 425 if(holdUntilComplete == null) { 426 holdUntilComplete = customerTypeDetail.getDefaultHoldUntilComplete(); 427 } 428 429 if(allowBackorders == null) { 430 allowBackorders = customerTypeDetail.getDefaultAllowBackorders(); 431 } 432 433 if(allowSubstitutions == null) { 434 allowSubstitutions = customerTypeDetail.getDefaultAllowSubstitutions(); 435 } 436 437 if(allowCombiningShipments == null) { 438 allowCombiningShipments = customerTypeDetail.getDefaultAllowCombiningShipments(); 439 } 440 } else { 441 if(holdUntilComplete == null) { 442 holdUntilComplete = billToCustomer.getHoldUntilComplete(); 443 } 444 445 if(allowBackorders == null) { 446 allowBackorders = billToCustomer.getAllowBackorders(); 447 } 448 449 if(allowSubstitutions == null) { 450 allowSubstitutions = billToCustomer.getAllowSubstitutions(); 451 } 452 453 if(allowCombiningShipments == null) { 454 allowCombiningShipments = billToCustomer.getAllowCombiningShipments(); 455 } 456 } 457 } 458 459 order = createOrder(eea, orderType, sequence, orderPriority, currency, holdUntilComplete, allowBackorders, 460 allowSubstitutions, allowCombiningShipments, term, freeOnBoard, reference, null, 461 cancellationPolicy, returnPolicy, taxable, createdBy); 462 463 if(eea == null || !eea.hasExecutionErrors()) { 464 var associateReferral = associateReferralLogic.getAssociateReferral(session, userVisit); 465 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(order.getPrimaryKey()); 466 467 salesOrderControl.createSalesOrder(order, offerUse, associateReferral, createdBy); 468 469 orderControl.createOrderUserVisit(order, userVisit); 470 471 workflowControl.addEntityToWorkflowUsingNames(null, SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, workflowEntranceName, 472 entityInstance, null, session.getStartTime() + AllocatedInventoryTimeout, createdBy); 473 474 if(billToParty != null) { 475 orderRoleControl.createOrderRole(order, billToParty, billToOrderRoleType, createdBy); 476 } 477 478 if(createdByParty != null) { 479 orderRoleControl.createOrderRole(order, createdByParty, placingOrderRoleType, createdBy); 480 } 481 482 if(batch != null) { 483 // eea is passed in as null to createBatchEntity(...) so that an Exception will be thrown is something 484 // goes wrong. No real way to back out at this point if it does, except by Exception. 485 batchLogic.createBatchEntity(null, entityInstance, batch, createdBy); 486 } 487 } 488 } 489 } 490 } 491 } 492 493 return order; 494 } 495 496 public Order createSalesOrder(final Session session, final ExecutionErrorAccumulator eea, final UserVisit userVisit, 497 final String batchName, final String sourceName, final String billToPartyName, final String orderPriorityName, 498 final String currencyIsoName, final String termName, final String strHoldUntilComplete, final String strAllowBackorders, 499 final String strAllowSubstitutions, final String strAllowCombiningShipments, final String reference, final String freeOnBoardName, 500 final String strTaxable, final String workflowEntranceName, final Party createdByParty) { 501 var batch = batchName == null ? null : salesOrderBatchLogic.getBatchByName(eea, batchName); 502 var source = sourceName == null ? null : sourceLogic.getSourceByName(eea, sourceName); 503 var billToParty = billToPartyName == null ? null : partyLogic.getPartyByName(eea, billToPartyName, PartyTypes.CUSTOMER.name()); 504 var orderPriority = orderPriorityName == null ? null : this.getOrderPriorityByName(eea, orderPriorityName); 505 var currency = currencyIsoName == null ? null : currencyLogic.getCurrencyByName(eea, currencyIsoName); 506 var term = termName == null ? null : termLogic.getTermByName(eea, termName); 507 var freeOnBoard = freeOnBoardName == null ? null : freeOnBoardLogic.getFreeOnBoardByName(eea, freeOnBoardName); 508 Order order = null; 509 510 if(eea == null || !eea.hasExecutionErrors()) { 511 var holdUntilComplete = strHoldUntilComplete == null ? null : Boolean.valueOf(strHoldUntilComplete); 512 var allowBackorders = strAllowBackorders == null ? null : Boolean.valueOf(strAllowBackorders); 513 var allowSubstitutions = strAllowSubstitutions == null ? null : Boolean.valueOf(strAllowSubstitutions); 514 var allowCombiningShipments = strAllowCombiningShipments == null ? null : Boolean.valueOf(strAllowCombiningShipments); 515 var taxable = strTaxable == null ? null : Boolean.valueOf(strTaxable); 516 517 order = createSalesOrder(session, eea, userVisit, batch, source, billToParty, orderPriority, currency, 518 holdUntilComplete, allowBackorders, allowSubstitutions, allowCombiningShipments, reference, term, 519 freeOnBoard, taxable, workflowEntranceName, createdByParty); 520 521 } 522 523 return order; 524 } 525 526 public boolean isOrderInWorkflowSteps(final ExecutionErrorAccumulator eea, final Order order, final String... workflowStepNames) { 527 return isOrderInWorkflowSteps(eea, getEntityInstanceByBaseEntity(order), workflowStepNames); 528 } 529 530 public boolean isOrderInWorkflowSteps(final ExecutionErrorAccumulator eea, final EntityInstance entityInstance, final String... workflowStepNames) { 531 return !workflowStepLogic.isEntityInWorkflowSteps(eea, SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, entityInstance, 532 workflowStepNames).isEmpty(); 533 } 534 535 public Order getOrderByName(final ExecutionErrorAccumulator eea, final String orderName) { 536 return getOrderByName(eea, OrderTypes.SALES_ORDER.name(), orderName); 537 } 538 539 public Order getOrderByNameForUpdate(final ExecutionErrorAccumulator eea, final String orderName) { 540 return getOrderByNameForUpdate(eea, OrderTypes.SALES_ORDER.name(), orderName); 541 } 542 543 public OrderPriority getOrderPriorityByName(final ExecutionErrorAccumulator eea, final String orderPriorityName) { 544 return salesOrderPriorityLogic.getOrderPriorityByName(eea, OrderTypes.SALES_ORDER.name(), orderPriorityName); 545 } 546 547 public OrderPriority getOrderPriorityByNameForUpdate(final ExecutionErrorAccumulator eea, final String orderPriorityName) { 548 return salesOrderPriorityLogic.getOrderPriorityByNameForUpdate(eea, OrderTypes.SALES_ORDER.name(), orderPriorityName); 549 } 550 551 public SalesOrderStatusChoicesBean getSalesOrderStatusChoices(final String defaultOrderStatusChoice, final Language language, final boolean allowNullChoice, 552 final Order order, final PartyPK partyPK) { 553 var salesOrderStatusChoicesBean = new SalesOrderStatusChoicesBean(); 554 555 if(order == null) { 556 workflowControl.getWorkflowEntranceChoices(salesOrderStatusChoicesBean, defaultOrderStatusChoice, language, allowNullChoice, 557 workflowControl.getWorkflowByName(SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS), partyPK); 558 } else { 559 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(order.getPrimaryKey()); 560 var workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceUsingNames(SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, entityInstance); 561 562 workflowControl.getWorkflowDestinationChoices(salesOrderStatusChoicesBean, defaultOrderStatusChoice, language, allowNullChoice, workflowEntityStatus.getWorkflowStep(), partyPK); 563 } 564 565 return salesOrderStatusChoicesBean; 566 } 567 568 public void setSalesOrderStatus(final Session session, final ExecutionErrorAccumulator eea, final Order order, final String orderStatusChoice, final PartyPK modifiedBy) { 569 var workflow = workflowLogic.getWorkflowByName(eea, SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS); 570 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(order.getPrimaryKey()); 571 var workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceForUpdate(workflow, entityInstance); 572 var workflowDestination = orderStatusChoice == null? null: workflowControl.getWorkflowDestinationByName(workflowEntityStatus.getWorkflowStep(), orderStatusChoice); 573 574 if(workflowDestination != null || orderStatusChoice == null) { 575 var currentWorkflowStepName = workflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName(); 576 var map = workflowDestinationLogic.getWorkflowDestinationsAsMap(workflowDestination); 577 var handled = false; 578 Long triggerTime = null; 579 580 if(currentWorkflowStepName.equals(SalesOrderStatusConstants.WorkflowStep_ENTRY_ALLOCATED)) { 581 if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, SalesOrderStatusConstants.WorkflowStep_ENTRY_UNALLOCATED)) { 582 // TODO: Unallocate inventory. 583 handled = true; 584 } else if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, SalesOrderStatusConstants.WorkflowStep_ENTRY_COMPLETE)) { 585 // TODO: Verify the order is not part of a batch. 586 // TODO: Verify all aspects of the order are valid. 587 handled = true; 588 } else if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, SalesOrderStatusConstants.WorkflowStep_BATCH_AUDIT)) { 589 // TODO: Verify the order is part of a batch. 590 // TODO: Verify all aspects of the order are valid. 591 handled = true; 592 } 593 } else if(currentWorkflowStepName.equals(SalesOrderStatusConstants.WorkflowStep_ENTRY_UNALLOCATED)) { 594 if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, SalesOrderStatusConstants.WorkflowStep_ENTRY_ALLOCATED)) { 595 // TODO: Allocate inventory. 596 597 triggerTime = session.getStartTime() + AllocatedInventoryTimeout; 598 handled = true; 599 } 600 } else if(currentWorkflowStepName.equals(SalesOrderStatusConstants.WorkflowStep_BATCH_AUDIT)) { 601 if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, SalesOrderStatusConstants.WorkflowStep_ENTRY_COMPLETE)) { 602 handled = true; 603 } 604 } 605 606 if(eea == null || !eea.hasExecutionErrors()) { 607 if(handled) { 608 workflowControl.transitionEntityInWorkflow(eea, workflowEntityStatus, workflowDestination, triggerTime, modifiedBy); 609 } else { 610 // TODO: An error of some sort. 611 } 612 } 613 } else { 614 handleExecutionError(UnknownSalesOrderStatusChoiceException.class, eea, ExecutionErrors.UnknownSalesOrderStatusChoice.name(), orderStatusChoice); 615 } 616 } 617 618 /** Check to see if an Order is available for modification, and if it isn't, send back an error. 619 * 620 * @param session Required. 621 * @param eea Required. 622 * @param order Required. 623 * @param modifiedBy Required. 624 */ 625 public void checkOrderAvailableForModification(final Session session, final ExecutionErrorAccumulator eea, final Order order, final PartyPK modifiedBy) { 626 var workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceForUpdateUsingNames(SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, getEntityInstanceByBaseEntity(order)); 627 var workflowStepName = workflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName(); 628 629 if(workflowStepName.equals(SalesOrderStatusConstants.WorkflowEntrance_ENTRY_ALLOCATED)) { 630 var workflowTrigger = workflowControl.getWorkflowTriggerForUpdate(workflowEntityStatus); 631 632 workflowTrigger.setTriggerTime(session.getStartTime() + AllocatedInventoryTimeout); 633 } else if(workflowStepName.equals(SalesOrderStatusConstants.WorkflowEntrance_ENTRY_UNALLOCATED)) { 634 setSalesOrderStatus(session, eea, order, SalesOrderStatusConstants.WorkflowDestination_ENTRY_UNALLOCATED_TO_ALLOCATED, modifiedBy); 635 } else { 636 handleExecutionError(InvalidSalesOrderStatusException.class, eea, ExecutionErrors.InvalidSalesOrderStatus.name(), order.getLastDetail().getOrderName(), workflowStepName); 637 } 638 } 639 640 /** Find the BILL_TO Party for a given Order. 641 * 642 * @param order Required. 643 * @return The Party used for the BILL_TO OrderRoleType. May be null. 644 */ 645 public Party getOrderBillToParty(final Order order) { 646 var billToOrderRole = orderRoleControl.getOrderRoleByOrderAndOrderRoleTypeUsingNames(order, OrderRoleTypes.BILL_TO.name()); 647 Party party = null; 648 649 if(billToOrderRole != null) { 650 party = billToOrderRole.getParty(); 651 } 652 653 return party; 654 } 655 656 /** Find the BILL_TO Party for a given Order. 657 * 658 * @param party Optional. 659 * @return The CustomerType for the Party. May be null. 660 */ 661 public CustomerType getCustomerTypeFromParty(final Party party) { 662 var customer = party == null ? null : customerControl.getCustomer(party); 663 CustomerType customerType = null; 664 665 if(customer != null) { 666 customerType = customer.getCustomerType(); 667 } 668 669 return customerType; 670 } 671 672 /** Find the BILL_TO Party for a given Order. 673 * 674 * @param order Required. 675 * @return The CustomerType for the BILL_TO Party. May be null. 676 */ 677 public CustomerType getOrderBillToCustomerType(final Order order) { 678 return getCustomerTypeFromParty(getOrderBillToParty(order)); 679 } 680 681 /** Attempt to find a SHIP_TO Party for the Order. If none is found, and billToFallback is set to true, then 682 * attempt to find the BILL_TO Party. If a BILL_TO Party is found, copy it to the SHIP_TO. 683 * 684 * @param order Required. 685 * @param billToFallback Required. 686 * @param createdBy Required if billToFallback is true. 687 * @return The Party that is to be used for the SHIP_TO OrderRoleType. May be null. 688 */ 689 public Party getOrderShipToParty(final Order order, final boolean billToFallback, final BasePK createdBy) { 690 var shipToOrderRole = orderRoleControl.getOrderRoleByOrderAndOrderRoleTypeUsingNames(order, OrderRoleTypes.SHIP_TO.name()); 691 692 if(shipToOrderRole == null && billToFallback) { 693 shipToOrderRole = orderRoleControl.getOrderRoleByOrderAndOrderRoleTypeUsingNames(order, OrderRoleTypes.BILL_TO.name()); 694 695 if(shipToOrderRole != null) { 696 orderRoleControl.createOrderRoleUsingNames(order, shipToOrderRole.getParty(), OrderRoleTypes.SHIP_TO.name(), createdBy); 697 } 698 } 699 700 return shipToOrderRole == null ? null : shipToOrderRole.getParty(); 701 } 702 703}