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}