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.order.server.logic;
018
019import com.echothree.model.control.order.common.exception.CannotSpecifyPartyPaymentMethodException;
020import com.echothree.model.control.order.common.exception.CannotSpecifyPaymentMethodAndPartyPaymentMethodException;
021import com.echothree.model.control.order.common.exception.CannotSpecifyWasPresentException;
022import com.echothree.model.control.order.common.exception.DuplicateOrderPaymentPreferenceSequenceException;
023import com.echothree.model.control.order.common.exception.DuplicateOrderShipmentGroupSequenceException;
024import com.echothree.model.control.order.common.exception.MustSpecifyPartyPaymentMethodException;
025import com.echothree.model.control.order.common.exception.MustSpecifyPaymentMethodOrPartyPaymentMethodException;
026import com.echothree.model.control.order.common.exception.MustSpecifyWasPresentException;
027import com.echothree.model.control.order.common.exception.UnknownOrderAliasTypeNameException;
028import com.echothree.model.control.order.common.exception.UnknownOrderNameException;
029import com.echothree.model.control.order.common.exception.UnknownOrderPaymentPreferenceSequenceException;
030import com.echothree.model.control.order.common.exception.UnknownOrderPriorityNameException;
031import com.echothree.model.control.order.common.exception.UnknownOrderRoleTypeNameException;
032import com.echothree.model.control.order.common.exception.UnknownOrderSequenceException;
033import com.echothree.model.control.order.common.exception.UnknownOrderSequenceTypeException;
034import com.echothree.model.control.order.common.exception.UnknownOrderTypeNameException;
035import com.echothree.model.control.order.server.control.OrderAliasControl;
036import com.echothree.model.control.order.server.control.OrderControl;
037import com.echothree.model.control.order.server.control.OrderLineControl;
038import com.echothree.model.control.order.server.control.OrderPaymentPreferenceControl;
039import com.echothree.model.control.order.server.control.OrderPriorityControl;
040import com.echothree.model.control.order.server.control.OrderRoleControl;
041import com.echothree.model.control.order.server.control.OrderShipmentGroupControl;
042import com.echothree.model.control.order.server.control.OrderTypeControl;
043import com.echothree.model.control.payment.common.PaymentMethodTypes;
044import com.echothree.model.control.payment.server.logic.PaymentMethodLogic;
045import com.echothree.model.control.sequence.server.control.SequenceControl;
046import com.echothree.model.control.sequence.server.logic.SequenceGeneratorLogic;
047import com.echothree.model.control.shipping.server.logic.ShippingMethodLogic;
048import com.echothree.model.data.accounting.server.entity.Currency;
049import com.echothree.model.data.cancellationpolicy.server.entity.CancellationPolicy;
050import com.echothree.model.data.contact.server.entity.PartyContactMechanism;
051import com.echothree.model.data.item.server.entity.Item;
052import com.echothree.model.data.item.server.entity.ItemDeliveryType;
053import com.echothree.model.data.order.server.entity.Order;
054import com.echothree.model.data.order.server.entity.OrderAliasType;
055import com.echothree.model.data.order.server.entity.OrderPaymentPreference;
056import com.echothree.model.data.order.server.entity.OrderPriority;
057import com.echothree.model.data.order.server.entity.OrderRoleType;
058import com.echothree.model.data.order.server.entity.OrderShipmentGroup;
059import com.echothree.model.data.order.server.entity.OrderStatus;
060import com.echothree.model.data.order.server.entity.OrderType;
061import com.echothree.model.data.payment.server.entity.PartyPaymentMethod;
062import com.echothree.model.data.payment.server.entity.PaymentMethod;
063import com.echothree.model.data.returnpolicy.server.entity.ReturnPolicy;
064import com.echothree.model.data.sequence.server.entity.Sequence;
065import com.echothree.model.data.sequence.server.entity.SequenceType;
066import com.echothree.model.data.shipment.server.entity.FreeOnBoard;
067import com.echothree.model.data.shipping.server.entity.ShippingMethod;
068import com.echothree.model.data.term.server.entity.Term;
069import com.echothree.util.common.message.ExecutionErrors;
070import com.echothree.util.common.persistence.BasePK;
071import com.echothree.util.server.control.BaseLogic;
072import com.echothree.util.server.message.ExecutionErrorAccumulator;
073import com.echothree.util.server.persistence.EntityPermission;
074import com.echothree.util.server.persistence.Session;
075import java.util.HashSet;
076import java.util.Set;
077
078public class OrderLogic
079        extends BaseLogic {
080
081    protected OrderLogic() {
082        super();
083    }
084
085    private static class OrderLogicHolder {
086        static OrderLogic instance = new OrderLogic();
087    }
088
089    public static OrderLogic getInstance() {
090        return OrderLogicHolder.instance;
091    }
092
093    public Currency getOrderCurrency(final Order order) {
094        return order.getLastDetail().getCurrency();
095    }
096    
097    public OrderType getOrderTypeByName(final ExecutionErrorAccumulator eea, final String orderTypeName) {
098        var orderTypeControl = Session.getModelController(OrderTypeControl.class);
099        var orderType = orderTypeControl.getOrderTypeByName(orderTypeName);
100
101        if(orderType == null) {
102            handleExecutionError(UnknownOrderTypeNameException.class, eea, ExecutionErrors.UnknownOrderTypeName.name(), orderTypeName);
103        }
104
105        return orderType;
106    }
107
108    public OrderRoleType getOrderRoleTypeByName(final ExecutionErrorAccumulator eea, final String orderRoleTypeName) {
109        var orderRoleControl = Session.getModelController(OrderRoleControl.class);
110        var orderRoleType = orderRoleControl.getOrderRoleTypeByName(orderRoleTypeName);
111
112        if(orderRoleType == null) {
113            handleExecutionError(UnknownOrderRoleTypeNameException.class, eea, ExecutionErrors.UnknownOrderRoleTypeName.name(), orderRoleTypeName);
114        }
115
116        return orderRoleType;
117    }
118
119    public SequenceType getOrderSequenceType(final ExecutionErrorAccumulator eea, final OrderType orderType) {
120        var parentOrderType = orderType;
121        SequenceType sequenceType;
122
123        do {
124            var orderTypeDetail = parentOrderType.getLastDetail();
125
126            sequenceType = orderTypeDetail.getOrderSequenceType();
127
128            if(sequenceType == null) {
129                parentOrderType = orderTypeDetail.getParentOrderType();
130            } else {
131                break;
132            }
133        } while(parentOrderType != null);
134
135        if(sequenceType == null) {
136            var sequenceControl = Session.getModelController(SequenceControl.class);
137
138            sequenceType = sequenceControl.getDefaultSequenceType();
139        }
140
141        if(sequenceType == null) {
142            handleExecutionError(UnknownOrderSequenceTypeException.class, eea, ExecutionErrors.UnknownOrderSequenceType.name(), orderType.getLastDetail().getOrderTypeName());
143        }
144
145        return sequenceType;
146    }
147
148    public Sequence getOrderSequence(final ExecutionErrorAccumulator eea, final OrderType orderType) {
149        var sequenceType = getOrderSequenceType(eea, orderType);
150        Sequence sequence = null;
151
152        if(eea == null || !eea.hasExecutionErrors()) {
153            var sequenceControl = Session.getModelController(SequenceControl.class);
154
155            sequence = sequenceControl.getDefaultSequence(sequenceType);
156        }
157
158        if(sequence == null) {
159            handleExecutionError(UnknownOrderSequenceException.class, eea, ExecutionErrors.UnknownOrderSequence.name(), orderType.getLastDetail().getOrderTypeName());
160        }
161
162        return sequence;
163    }
164
165    public String getOrderName(final ExecutionErrorAccumulator eea, final OrderType orderType, Sequence sequence) {
166        String orderName = null;
167
168        if(sequence == null) {
169            sequence = getOrderSequence(eea, orderType);
170        }
171
172        if(eea == null || !eea.hasExecutionErrors()) {
173            orderName = SequenceGeneratorLogic.getInstance().getNextSequenceValue(sequence);
174        }
175
176        return orderName;
177    }
178
179    public Order createOrder(final ExecutionErrorAccumulator eea, OrderType orderType, Sequence sequence, final OrderPriority orderPriority,
180            final Currency currency, final Boolean holdUntilComplete, final Boolean allowBackorders, final Boolean allowSubstitutions,
181            final Boolean allowCombiningShipments, final Term term, final FreeOnBoard freeOnBoard, final String reference, final String description,
182            final CancellationPolicy cancellationPolicy, final ReturnPolicy returnPolicy, final Boolean taxable, final BasePK createdBy) {
183        var orderName = getOrderName(eea, orderType, sequence);
184        Order order = null;
185
186        if(eea == null || !eea.hasExecutionErrors()) {
187            var orderControl = Session.getModelController(OrderControl.class);
188            
189            order = orderControl.createOrder(orderType, orderName, orderPriority, currency, holdUntilComplete, allowBackorders, allowSubstitutions,
190                    allowCombiningShipments, term, freeOnBoard, reference, description, cancellationPolicy, returnPolicy, taxable, createdBy);
191        }
192
193        return order;
194    }
195
196    public OrderAliasType getOrderAliasTypeByName(final ExecutionErrorAccumulator eea, final OrderType orderType, final String orderAliasTypeName) {
197        var orderAliasControl = Session.getModelController(OrderAliasControl.class);
198        var orderAliasType = orderAliasControl.getOrderAliasTypeByName(orderType, orderAliasTypeName);
199
200        if(orderAliasType == null) {
201            handleExecutionError(UnknownOrderAliasTypeNameException.class, eea, ExecutionErrors.UnknownOrderAliasTypeName.name(),
202                    orderType.getLastDetail().getOrderTypeName(), orderAliasTypeName);
203        }
204
205        return orderAliasType;
206    }
207
208    private Order getOrderByName(final ExecutionErrorAccumulator eea, final String orderTypeName, final String orderName,
209            final EntityPermission entityPermission) {
210        var orderControl = Session.getModelController(OrderControl.class);
211        var orderType = getOrderTypeByName(eea, orderTypeName);
212        Order order = null;
213
214        if(eea == null || !eea.hasExecutionErrors()) {
215            order = orderControl.getOrderByName(orderType, orderName, entityPermission);
216
217            if(order == null) {
218                handleExecutionError(UnknownOrderNameException.class, eea, ExecutionErrors.UnknownOrderName.name(), orderTypeName, orderName);
219            }
220        }
221
222        return order;
223    }
224
225    public Order getOrderByName(final ExecutionErrorAccumulator eea, final String orderTypeName, final String orderName) {
226        return getOrderByName(eea, orderTypeName, orderName, EntityPermission.READ_ONLY);
227    }
228
229    public Order getOrderByNameForUpdate(final ExecutionErrorAccumulator eea, final String orderTypeName, final String orderName) {
230        return getOrderByName(eea, orderTypeName, orderName, EntityPermission.READ_WRITE);
231    }
232    
233    public OrderPriority getOrderPriorityByName(final ExecutionErrorAccumulator eea, final String orderTypeName, final String orderPriorityName) {
234        var orderType = getOrderTypeByName(eea, orderTypeName);
235        OrderPriority orderPriority = null;
236
237        if(eea == null || !eea.hasExecutionErrors()) {
238            var orderPriorityControl = Session.getModelController(OrderPriorityControl.class);
239
240            orderPriority = orderPriorityControl.getOrderPriorityByName(orderType, orderPriorityName);
241
242            if(orderPriority == null) {
243                handleExecutionError(UnknownOrderPriorityNameException.class, eea, ExecutionErrors.UnknownOrderPriorityName.name(), orderTypeName, orderPriorityName);
244            }
245        }
246
247        return orderPriority;
248    }
249
250    public OrderPriority getOrderPriorityByNameForUpdate(final ExecutionErrorAccumulator eea, final String orderTypeName, final String orderPriorityName) {
251        var orderType = getOrderTypeByName(eea, orderTypeName);
252        OrderPriority orderPriority = null;
253
254        if(eea == null || !eea.hasExecutionErrors()) {
255            var orderPriorityControl = Session.getModelController(OrderPriorityControl.class);
256
257            orderPriority = orderPriorityControl.getOrderPriorityByNameForUpdate(orderType, orderPriorityName);
258
259            if(orderPriority == null) {
260                handleExecutionError(UnknownOrderPriorityNameException.class, eea, ExecutionErrors.UnknownOrderPriorityName.name(), orderTypeName, orderPriorityName);
261            }
262        }
263
264        return orderPriority;
265    }
266
267    public OrderShipmentGroup createOrderShipmentGroup(final ExecutionErrorAccumulator eea, final Order order, Integer orderShipmentGroupSequence,
268            final ItemDeliveryType itemDeliveryType, final Boolean isDefault, final PartyContactMechanism partyContactMechanism,
269            final ShippingMethod shippingMethod, final Boolean holdUntilComplete, final BasePK createdBy) {
270        var orderControl = Session.getModelController(OrderControl.class);
271        var orderShipmentGroupControl = Session.getModelController(OrderShipmentGroupControl.class);
272        var orderStatus = orderControl.getOrderStatusForUpdate(order);
273        OrderShipmentGroup orderShipmentGroup = null;
274
275        if(orderShipmentGroupSequence == null) {
276            orderShipmentGroupSequence = orderStatus.getOrderShipmentGroupSequence() + 1;
277            orderStatus.setOrderShipmentGroupSequence(orderShipmentGroupSequence);
278        } else {
279            orderShipmentGroup = orderShipmentGroupControl.getOrderShipmentGroupBySequence(order, orderShipmentGroupSequence);
280
281            if(orderShipmentGroup == null) {
282                // If the orderShipmentGroupSequence is > the last one that was recorded in the OrderStatus, jump the
283                // one in OrderStatus forward - it should always record the greatest orderShipmentGroupSequence used.
284                if(orderShipmentGroupSequence > orderStatus.getOrderShipmentGroupSequence()) {
285                    orderStatus.setOrderShipmentGroupSequence(orderShipmentGroupSequence);
286                }
287            } else {
288                handleExecutionError(DuplicateOrderShipmentGroupSequenceException.class, eea, ExecutionErrors.DuplicateOrderShipmentGroupSequence.name(),
289                        order.getLastDetail().getOrderName(), orderShipmentGroupSequence.toString());
290            }
291        }
292
293        if(orderShipmentGroup == null) {
294            orderShipmentGroup = orderShipmentGroupControl.createOrderShipmentGroup(order, orderShipmentGroupSequence, itemDeliveryType, isDefault, partyContactMechanism,
295                    shippingMethod, holdUntilComplete, createdBy);
296        }
297
298        return orderShipmentGroup;
299    }
300
301    public OrderShipmentGroup getDefaultOrderShipmentGroup(final Order order, final ItemDeliveryType itemDeliveryType) {
302        var orderShipmentGroupControl = Session.getModelController(OrderShipmentGroupControl.class);
303
304        return orderShipmentGroupControl.getDefaultOrderShipmentGroup(order, itemDeliveryType);
305    }
306
307    public Set<Item> getItemsFromOrder(final Order order) {
308        var orderLineControl = Session.getModelController(OrderLineControl.class);
309        var orderLines = orderLineControl.getOrderLinesByOrder(order);
310        var items = new HashSet<Item>(orderLines.size());
311
312        orderLines.forEach((orderLine) -> {
313            items.add(orderLine.getLastDetail().getItem());
314        });
315        
316        return items;
317    }
318    
319    public OrderPaymentPreference createOrderPaymentPreference(final Session session, final ExecutionErrorAccumulator eea, final Order order,
320            Integer orderPaymentPreferenceSequence, PaymentMethod paymentMethod, final PartyPaymentMethod partyPaymentMethod,
321            final Boolean wasPresent, final Long maximumAmount, final Integer sortOrder, final BasePK createdBy) {
322        var parameterCount = (paymentMethod == null ? 0 : 1) + (partyPaymentMethod == null ? 0 : 1);
323        OrderPaymentPreference orderPaymentPreference = null;
324
325        // Either the paymentMethod or the partyPaymentMethod must be specified.
326        if(parameterCount == 1) {
327            String paymentMethodTypeName;
328
329            if(paymentMethod == null) {
330                paymentMethod = partyPaymentMethod.getLastDetail().getPaymentMethod();
331            }
332            
333            paymentMethodTypeName = paymentMethod.getLastDetail().getPaymentMethodType().getLastDetail().getPaymentMethodTypeName();
334            
335            // If the type is CREDIT_CARD, GIFT_CARD, or GIFT_CERTIFICATE, then the partyPaymentMethod must be specified.
336            if((paymentMethodTypeName.equals(PaymentMethodTypes.CREDIT_CARD.name())
337                    || paymentMethodTypeName.equals(PaymentMethodTypes.GIFT_CARD.name())
338                    || paymentMethodTypeName.equals(PaymentMethodTypes.GIFT_CERTIFICATE.name())) && partyPaymentMethod == null) {
339                handleExecutionError(MustSpecifyPartyPaymentMethodException.class, eea, ExecutionErrors.MustSpecifyPartyPaymentMethod.name());
340            } else if(partyPaymentMethod != null) {
341                // Otherwise, the partyPaymentMethod should always be null.
342                handleExecutionError(CannotSpecifyPartyPaymentMethodException.class, eea, ExecutionErrors.CannotSpecifyPartyPaymentMethod.name());
343            }
344            
345            // If the type is CREDIT_CARD then wasPresent must be specified.
346            if(paymentMethodTypeName.equals(PaymentMethodTypes.CREDIT_CARD.name()) && wasPresent == null) {
347                handleExecutionError(MustSpecifyWasPresentException.class, eea, ExecutionErrors.MustSpecifyWasPresent.name());
348            } else if(wasPresent != null) {
349                // Otherwise, the partyPaymentMethod should always be null.
350                handleExecutionError(CannotSpecifyWasPresentException.class, eea, ExecutionErrors.CannotSpecifyWasPresent.name());
351            }
352            
353            if(eea == null || !eea.hasExecutionErrors()) {
354                PaymentMethodLogic.getInstance().checkAcceptanceOfItems(session, eea, paymentMethod, getItemsFromOrder(order), createdBy);
355                
356                if(eea == null || !eea.hasExecutionErrors()) {
357                    var orderPaymentPreferenceControl = Session.getModelController(OrderPaymentPreferenceControl.class);
358                    var orderControl = Session.getModelController(OrderControl.class);
359                    OrderStatus orderStatus = orderControl.getOrderStatusForUpdate(order);
360
361                    if(orderPaymentPreferenceSequence == null) {
362                        orderPaymentPreferenceSequence = orderStatus.getOrderPaymentPreferenceSequence() + 1;
363                        orderStatus.setOrderPaymentPreferenceSequence(orderPaymentPreferenceSequence);
364                    } else {
365                        orderPaymentPreference = orderPaymentPreferenceControl.getOrderPaymentPreferenceBySequence(order, orderPaymentPreferenceSequence);
366
367                        if(orderPaymentPreference == null) {
368                            // If the orderPaymentPreferenceSequence is > the last one that was recorded in the OrderStatus, jump the
369                            // one in OrderStatus forward - it should always record the greatest orderPaymentPreferenceSequence used.
370                            if(orderPaymentPreferenceSequence > orderStatus.getOrderPaymentPreferenceSequence()) {
371                                orderStatus.setOrderPaymentPreferenceSequence(orderPaymentPreferenceSequence);
372                            }
373                        } else {
374                            handleExecutionError(DuplicateOrderPaymentPreferenceSequenceException.class, eea, ExecutionErrors.DuplicateOrderPaymentPreferenceSequence.name(),
375                                    order.getLastDetail().getOrderName(), orderPaymentPreferenceSequence.toString());
376                        }
377                    }
378
379                    if(orderPaymentPreference == null) {
380                        orderPaymentPreference = orderPaymentPreferenceControl.createOrderPaymentPreference(order, orderPaymentPreferenceSequence, paymentMethod, partyPaymentMethod,
381                                wasPresent, maximumAmount, sortOrder, createdBy);
382                    }
383                }
384            }
385        } else {
386            if(parameterCount == 0) {
387                handleExecutionError(MustSpecifyPaymentMethodOrPartyPaymentMethodException.class, eea, ExecutionErrors.MustSpecifyPaymentMethodOrPartyPaymentMethod.name());
388            } else {
389                handleExecutionError(CannotSpecifyPaymentMethodAndPartyPaymentMethodException.class, eea, ExecutionErrors.CannotSpecifyPaymentMethodAndPartyPaymentMethod.name());
390            }
391        }
392
393        return orderPaymentPreference;
394    }
395
396    private OrderPaymentPreference getOrderPaymentPreferenceByName(final ExecutionErrorAccumulator eea, final String orderTypeName, final String orderName,
397            final String orderPaymentPreferenceSequence, final EntityPermission entityPermission) {
398        var orderPaymentPreferenceControl = Session.getModelController(OrderPaymentPreferenceControl.class);
399        var order = getOrderByName(eea, orderTypeName, orderName);
400        OrderPaymentPreference orderPaymentPreference = null;
401        
402        if(eea == null || !eea.hasExecutionErrors()) {
403            orderPaymentPreference = orderPaymentPreferenceControl.getOrderPaymentPreferenceBySequence(order, Integer.valueOf(orderPaymentPreferenceSequence), entityPermission);
404            
405            if(orderPaymentPreference == null) {
406                handleExecutionError(UnknownOrderPaymentPreferenceSequenceException.class, eea, ExecutionErrors.UnknownOrderPaymentPreferenceSequence.name(), orderTypeName,
407                        orderName, orderPaymentPreferenceSequence);
408            }
409        }
410
411        return orderPaymentPreference;
412    }
413
414    public OrderPaymentPreference getOrderPaymentPreferenceByName(final ExecutionErrorAccumulator eea, final String orderTypeName, final String orderName,
415            final String orderPaymentPreferenceSequence) {
416        return getOrderPaymentPreferenceByName(eea, orderTypeName, orderName, orderPaymentPreferenceSequence, EntityPermission.READ_ONLY);
417    }
418
419    public OrderPaymentPreference getOrderPaymentPreferenceByNameForUpdate(final ExecutionErrorAccumulator eea, final String orderTypeName, final String orderName,
420            final String orderPaymentPreferenceSequence) {
421        return getOrderPaymentPreferenceByName(eea, orderTypeName, orderName, orderPaymentPreferenceSequence, EntityPermission.READ_WRITE);
422    }
423    
424    public void checkItemAgainstOrderPaymentPreferences(final Session session, final ExecutionErrorAccumulator eea, final Order order, final Item item,
425            final BasePK evaluatedBy) {
426        var orderPaymentPreferenceControl = Session.getModelController(OrderPaymentPreferenceControl.class);
427        var orderPaymentPreferences = orderPaymentPreferenceControl.getOrderPaymentPreferencesByOrder(order);
428        
429        orderPaymentPreferences.forEach((orderPaymentPreference) -> {
430            PaymentMethodLogic.getInstance().checkAcceptanceOfItem(session, eea, orderPaymentPreference.getLastDetail().getPaymentMethod(), item, evaluatedBy);
431        });
432    }
433    
434    public void checkItemAgainstShippingMethod(final Session session, final ExecutionErrorAccumulator eea, final OrderShipmentGroup orderShipmentGroup,
435            final Item item, final BasePK evaluatedBy) {
436        var shippingMethod = orderShipmentGroup.getLastDetail().getShippingMethod();
437        
438        if(shippingMethod != null) {
439            ShippingMethodLogic.getInstance().checkAcceptanceOfItem(session, eea, shippingMethod, item, evaluatedBy);
440        }
441    }
442
443}