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.batch.common.BatchConstants;
020import com.echothree.model.control.batch.server.control.BatchControl;
021import com.echothree.model.control.batch.server.logic.BatchLogic;
022import com.echothree.model.control.core.server.control.CoreControl;
023import com.echothree.model.control.order.server.control.OrderBatchControl;
024import com.echothree.model.control.order.server.control.OrderControl;
025import com.echothree.model.control.sales.common.exception.CannotDeleteSalesOrderBatchInUseException;
026import com.echothree.model.control.sales.common.exception.IncorrectSalesOrderBatchAmountException;
027import com.echothree.model.control.sales.common.exception.IncorrectSalesOrderBatchCountException;
028import com.echothree.model.control.sales.common.exception.InvalidSalesOrderBatchStatusException;
029import com.echothree.model.control.sales.common.exception.InvalidSalesOrderStatusException;
030import com.echothree.model.control.sales.common.exception.UnknownSalesOrderBatchStatusChoiceException;
031import com.echothree.model.control.sales.common.choice.SalesOrderBatchStatusChoicesBean;
032import com.echothree.model.control.sales.common.workflow.SalesOrderBatchStatusConstants;
033import com.echothree.model.control.sales.common.workflow.SalesOrderStatusConstants;
034import com.echothree.model.control.sales.server.control.SalesOrderBatchControl;
035import com.echothree.model.control.workflow.server.control.WorkflowControl;
036import com.echothree.model.control.workflow.server.logic.WorkflowDestinationLogic;
037import com.echothree.model.control.workflow.server.logic.WorkflowLogic;
038import com.echothree.model.control.workflow.server.logic.WorkflowStepLogic;
039import com.echothree.model.data.accounting.server.entity.Currency;
040import com.echothree.model.data.batch.server.entity.Batch;
041import com.echothree.model.data.batch.server.entity.BatchEntity;
042import com.echothree.model.data.core.server.entity.EntityInstance;
043import com.echothree.model.data.order.server.entity.Order;
044import com.echothree.model.data.order.server.entity.OrderBatch;
045import com.echothree.model.data.party.common.pk.PartyPK;
046import com.echothree.model.data.party.server.entity.Language;
047import com.echothree.model.data.payment.server.entity.PaymentMethod;
048import com.echothree.model.data.workflow.server.entity.Workflow;
049import com.echothree.model.data.workflow.server.entity.WorkflowDestination;
050import com.echothree.model.data.workflow.server.entity.WorkflowEntityStatus;
051import com.echothree.util.common.message.ExecutionErrors;
052import com.echothree.util.common.persistence.BasePK;
053import com.echothree.util.server.control.BaseLogic;
054import com.echothree.util.server.message.ExecutionErrorAccumulator;
055import com.echothree.util.server.persistence.Session;
056import com.echothree.util.server.string.AmountUtils;
057import java.util.ArrayList;
058import java.util.List;
059import java.util.Map;
060import java.util.Set;
061
062public class SalesOrderBatchLogic
063        extends BaseLogic {
064
065    private SalesOrderBatchLogic() {
066        super();
067    }
068
069    private static class BatchLogicHolder {
070        static SalesOrderBatchLogic instance = new SalesOrderBatchLogic();
071    }
072
073    public static SalesOrderBatchLogic getInstance() {
074        return BatchLogicHolder.instance;
075    }
076    
077    public Batch createBatch(final ExecutionErrorAccumulator eea, final Currency currency, final PaymentMethod paymentMethod, final Long count,
078            final Long amount, final BasePK createdBy) {
079        var salesOrderBatchControl = Session.getModelController(SalesOrderBatchControl.class);
080        var batch = BatchLogic.getInstance().createBatch(eea, BatchConstants.BatchType_SALES_ORDER, createdBy);
081
082        if(!eea.hasExecutionErrors()) {
083            var orderBatchControl = Session.getModelController(OrderBatchControl.class);
084
085            orderBatchControl.createOrderBatch(batch, currency, count, amount, createdBy);
086            salesOrderBatchControl.createSalesOrderBatch(batch, paymentMethod, createdBy);
087        }
088
089        return batch;
090    }
091    
092    public boolean checkBatchInWorkflowSteps(final ExecutionErrorAccumulator eea, final Batch batch, final String... workflowStepNames) {
093        return !WorkflowStepLogic.getInstance().isEntityInWorkflowSteps(eea, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS,
094                getEntityInstanceByBaseEntity(batch), workflowStepNames).isEmpty();
095    }
096
097    public boolean checkBatchAvailableForEntry(final ExecutionErrorAccumulator eea, final Batch batch) {
098        return checkBatchInWorkflowSteps(eea, batch, SalesOrderBatchStatusConstants.WorkflowStep_ENTRY);
099    }
100    
101    public BatchEntity createBatchEntity(final ExecutionErrorAccumulator eea, final Order order, final Batch batch, final BasePK createdBy) {
102        return BatchLogic.getInstance().createBatchEntity(eea, getEntityInstanceByBaseEntity(order), batch, createdBy);
103    }
104
105    public boolean batchEntryExists(final ExecutionErrorAccumulator eea, final Order order, final Batch batch, final String... workflowStepNames) {
106        boolean result = false;
107        
108        if(workflowStepNames.length == 0) {
109            result = BatchLogic.getInstance().batchEntityExists(order, batch);
110        } else {
111            if(!WorkflowStepLogic.getInstance().isEntityInWorkflowSteps(eea, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS,
112                    getEntityInstanceByBaseEntity(order), workflowStepNames).isEmpty()) {
113                result = true;
114            } else {
115                handleExecutionError(InvalidSalesOrderBatchStatusException.class, eea, ExecutionErrors.InvalidSalesOrderBatchStatus.name(),
116                        batch.getLastDetail().getBatchName());
117            }
118        }
119        
120        return result;
121    }
122    
123    public void deleteBatch(final ExecutionErrorAccumulator eea, final Batch batch, final BasePK deletedBy) {
124        var batchControl = Session.getModelController(BatchControl.class);
125
126        if(batchControl.countBatchEntitiesByBatch(batch) == 0) {
127            BatchLogic.getInstance().deleteBatch(eea, batch, deletedBy);
128
129            if(eea == null || !eea.hasExecutionErrors()) {
130                var orderBatchControl = Session.getModelController(OrderBatchControl.class);
131                var salesOrderBatchControl = Session.getModelController(SalesOrderBatchControl.class);
132
133                orderBatchControl.deleteOrderBatch(batch, deletedBy);
134                salesOrderBatchControl.deleteSalesOrderBatch(batch, deletedBy);
135            }
136        } else {
137            handleExecutionError(CannotDeleteSalesOrderBatchInUseException.class, eea, ExecutionErrors.CannotDeleteSalesOrderBatchInUse.name(),
138                    batch.getLastDetail().getBatchName());
139        }
140    }
141
142    public Batch getBatchByName(final ExecutionErrorAccumulator eea, final String batchName) {
143        return BatchLogic.getInstance().getBatchByName(eea, BatchConstants.BatchType_SALES_ORDER, batchName);
144    }
145
146    public Batch getBatchByNameForUpdate(final ExecutionErrorAccumulator eea, final String batchName) {
147        return BatchLogic.getInstance().getBatchByNameForUpdate(eea, BatchConstants.BatchType_SALES_ORDER, batchName);
148    }
149
150    public SalesOrderBatchStatusChoicesBean getSalesOrderBatchStatusChoices(String defaultSalesOrderBatchStatusChoice, Language language, boolean allowNullChoice,
151            Batch batch, PartyPK partyPK) {
152        var workflowControl = Session.getModelController(WorkflowControl.class);
153        SalesOrderBatchStatusChoicesBean salesOrderBatchStatusChoicesBean = new SalesOrderBatchStatusChoicesBean();
154
155        if(batch == null) {
156            workflowControl.getWorkflowEntranceChoices(salesOrderBatchStatusChoicesBean, defaultSalesOrderBatchStatusChoice, language, allowNullChoice,
157                    workflowControl.getWorkflowByName(SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS), partyPK);
158        } else {
159            var coreControl = Session.getModelController(CoreControl.class);
160            EntityInstance entityInstance = coreControl.getEntityInstanceByBasePK(batch.getPrimaryKey());
161            WorkflowEntityStatus workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceUsingNames(SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS,
162                    entityInstance);
163
164            workflowControl.getWorkflowDestinationChoices(salesOrderBatchStatusChoicesBean, defaultSalesOrderBatchStatusChoice, language, allowNullChoice,
165                    workflowEntityStatus.getWorkflowStep(), partyPK);
166        }
167
168        return salesOrderBatchStatusChoicesBean;
169    }
170
171    public void setSalesOrderBatchStatus(final Session session, ExecutionErrorAccumulator eea, Batch batch, String salesOrderBatchStatusChoice, PartyPK modifiedBy) {
172        var coreControl = Session.getModelController(CoreControl.class);
173        var orderControl = Session.getModelController(OrderControl.class);
174        var workflowControl = Session.getModelController(WorkflowControl.class);
175        var workflow = WorkflowLogic.getInstance().getWorkflowByName(eea, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS);
176        EntityInstance entityInstance = coreControl.getEntityInstanceByBasePK(batch.getPrimaryKey());
177        WorkflowEntityStatus workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceForUpdate(workflow, entityInstance);
178        WorkflowDestination workflowDestination = salesOrderBatchStatusChoice == null ? null : workflowControl.getWorkflowDestinationByName(workflowEntityStatus.getWorkflowStep(), salesOrderBatchStatusChoice);
179
180        if(workflowDestination != null || salesOrderBatchStatusChoice == null) {
181            var workflowDestinationLogic = WorkflowDestinationLogic.getInstance();
182            String currentWorkflowStepName = workflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName();
183            Map<String, Set<String>> map = workflowDestinationLogic.getWorkflowDestinationsAsMap(workflowDestination);
184            Long triggerTime = null;
185            
186            if(currentWorkflowStepName.equals(SalesOrderBatchStatusConstants.WorkflowStep_ENTRY)) {
187                if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, SalesOrderBatchStatusConstants.WorkflowStep_AUDIT)) {
188                    var batchControl = Session.getModelController(BatchControl.class);
189                    List<BatchEntity> batchEntities = batchControl.getBatchEntitiesByBatch(batch);
190                    
191                    // Verify all orders are in BATCH_AUDIT.
192                    batchEntities.stream().map((batchEntity) -> workflowControl.getWorkflowEntityStatusByEntityInstanceUsingNames(SalesOrderStatusConstants.Workflow_SALES_ORDER_STATUS, batchEntity.getEntityInstance())).forEach((orderWorkflowEntityStatus) -> {
193                        EntityInstance orderEntityInstance = orderWorkflowEntityStatus.getEntityInstance();
194                        String orderWorkflowStepName = orderWorkflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName();
195                        if (!orderWorkflowStepName.equals(SalesOrderStatusConstants.WorkflowStep_BATCH_AUDIT)) {
196                            Order order = orderControl.getOrderByEntityInstance(orderEntityInstance);
197                            
198                            handleExecutionError(InvalidSalesOrderStatusException.class, eea, ExecutionErrors.InvalidSalesOrderStatus.name(),
199                                    order.getLastDetail().getOrderName(), orderWorkflowStepName);
200                        }
201                    });
202                }
203            } else if(currentWorkflowStepName.equals(SalesOrderBatchStatusConstants.WorkflowStep_AUDIT)) {
204                if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, SalesOrderBatchStatusConstants.Workflow_SALES_ORDER_BATCH_STATUS, SalesOrderBatchStatusConstants.WorkflowStep_COMPLETE)) {
205                    var orderBatchControl = Session.getModelController(OrderBatchControl.class);
206                    OrderBatch orderBatch = orderBatchControl.getOrderBatch(batch);
207                    Long count = orderBatch.getCount();
208                    Long amount = orderBatch.getAmount();
209
210                    if(count != null) {
211                        var batchControl = Session.getModelController(BatchControl.class);
212                        Long batchCount = batchControl.countBatchEntitiesByBatch(batch);
213
214                        if(!count.equals(batchCount)) {
215                            handleExecutionError(IncorrectSalesOrderBatchCountException.class, eea, ExecutionErrors.IncorrectSalesOrderBatchCount.name(),
216                                    count.toString(), batchCount.toString());
217                        }
218                    }
219
220                    if(amount != null) {
221                        Long batchAmount = getBatchOrderTotalsWithAdjustments(batch);
222                        
223                        if(!amount.equals(batchAmount)) {
224                            Currency currency = orderBatch.getCurrency();
225
226                            handleExecutionError(IncorrectSalesOrderBatchAmountException.class, eea, ExecutionErrors.IncorrectSalesOrderBatchAmount.name(),
227                                    AmountUtils.getInstance().formatPriceUnit(currency, amount),
228                                    AmountUtils.getInstance().formatPriceUnit(currency, batchAmount));
229                        }
230                    }
231                }
232            }
233            
234            if(eea == null || !eea.hasExecutionErrors()) {
235                workflowControl.transitionEntityInWorkflow(eea, workflowEntityStatus, workflowDestination, triggerTime, modifiedBy);
236            }
237        } else {
238            handleExecutionError(UnknownSalesOrderBatchStatusChoiceException.class, eea, ExecutionErrors.UnknownSalesOrderBatchStatusChoice.name(), salesOrderBatchStatusChoice);
239        }
240    }
241
242    public List<Order> getBatchOrders(final Batch batch) {
243        var batchControl = Session.getModelController(BatchControl.class);
244        var orderControl = Session.getModelController(OrderControl.class);
245        List<BatchEntity> batchEntities = batchControl.getBatchEntitiesByBatch(batch);
246        List<Order> orders = new ArrayList<>(batchEntities.size());
247
248        batchEntities.forEach((batchEntity) -> {
249            orders.add(orderControl.convertEntityInstanceToOrder(batchEntity.getEntityInstance()));
250        });
251
252        return orders;
253    }
254
255    public Long getBatchOrderTotalsWithAdjustments(Batch batch) {
256        SalesOrderLineLogic salesOrderLineLogic = SalesOrderLineLogic.getInstance();
257        List<Order> orders = getBatchOrders(batch);
258        long total = 0;
259
260        total = orders.stream().map((order) -> salesOrderLineLogic.getOrderTotalWithAdjustments(order)).reduce(total, (accumulator, _item) -> accumulator + _item);
261
262        return total;
263    }
264
265}