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