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