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.accounting.server.logic; 018 019import com.echothree.control.user.accounting.common.spec.TransactionUniversalSpec; 020import com.echothree.model.control.accounting.common.TransactionTimeTypes; 021import com.echothree.model.control.accounting.common.exception.TransactionNotBalancedException; 022import com.echothree.model.control.accounting.common.exception.UnknownTransactionNameException; 023import com.echothree.model.control.accounting.common.workflow.TransactionStatusConstants; 024import com.echothree.model.control.accounting.server.control.AccountingControl; 025import com.echothree.model.control.accounting.server.database.TransactionBalancedQuery; 026import com.echothree.model.control.core.common.ComponentVendors; 027import com.echothree.model.control.core.common.EntityTypes; 028import com.echothree.model.control.core.common.exception.InvalidParameterCountException; 029import com.echothree.model.control.core.server.control.EntityInstanceControl; 030import com.echothree.model.control.core.server.logic.EntityInstanceLogic; 031import com.echothree.model.control.party.server.control.PartyControl; 032import com.echothree.model.control.workflow.server.control.WorkflowControl; 033import com.echothree.model.control.workflow.server.logic.WorkflowEntranceLogic; 034import com.echothree.model.control.workflow.server.logic.WorkflowLogic; 035import com.echothree.model.data.accounting.server.entity.Currency; 036import com.echothree.model.data.accounting.server.entity.GlAccount; 037import com.echothree.model.data.accounting.server.entity.Transaction; 038import com.echothree.model.data.accounting.server.entity.TransactionEntityRole; 039import com.echothree.model.data.accounting.server.entity.TransactionEntityRoleType; 040import com.echothree.model.data.accounting.server.entity.TransactionGlAccountCategory; 041import com.echothree.model.data.accounting.server.entity.TransactionGlEntry; 042import com.echothree.model.data.accounting.server.entity.TransactionType; 043import com.echothree.model.data.party.server.entity.Party; 044import com.echothree.util.common.message.ExecutionErrors; 045import com.echothree.util.common.persistence.BasePK; 046import com.echothree.util.server.control.BaseLogic; 047import com.echothree.util.server.message.ExecutionErrorAccumulator; 048import com.echothree.util.server.persistence.EntityPermission; 049import com.echothree.util.server.persistence.Session; 050import javax.enterprise.context.ApplicationScoped; 051import javax.enterprise.inject.spi.CDI; 052 053@ApplicationScoped 054public class TransactionLogic 055 extends BaseLogic { 056 057 protected TransactionLogic() { 058 super(); 059 } 060 061 public static TransactionLogic getInstance() { 062 return CDI.current().select(TransactionLogic.class).get(); 063 } 064 065 public Transaction createTransactionUsingNames(final Session session, final Party groupParty, final String transactionTypeName, 066 final Long transactionTime, final BasePK createdBy) { 067 var accountingControl = Session.getModelController(AccountingControl.class); 068 069 return createTransaction(session, groupParty, accountingControl.getTransactionTypeByName(transactionTypeName), 070 transactionTime, createdBy); 071 } 072 073 public Transaction createTransaction(final Session session, final Party groupParty, final TransactionType transactionType, 074 final Long transactionTime, final BasePK createdBy) { 075 var accountingControl = Session.getModelController(AccountingControl.class); 076 var transaction = accountingControl.createTransaction(groupParty, transactionType, createdBy); 077 078 TransactionTimeLogic.getInstance().createTransactionTime(null, transaction, TransactionTimeTypes.TRANSACTION_TIME.name(), 079 transactionTime == null ? session.START_TIME_LONG : transactionTime, createdBy); 080 081 return transaction; 082 } 083 084 public Transaction getTransactionByName(final ExecutionErrorAccumulator eea, final String transactionName, 085 final EntityPermission entityPermission) { 086 var accountingControl = Session.getModelController(AccountingControl.class); 087 var transaction = accountingControl.getTransactionByName(transactionName, entityPermission); 088 089 if(transaction == null) { 090 handleExecutionError(UnknownTransactionNameException.class, eea, ExecutionErrors.UnknownTransactionName.name(), transactionName); 091 } 092 093 return transaction; 094 } 095 096 public Transaction getTransactionByName(final ExecutionErrorAccumulator eea, final String transactionName) { 097 return getTransactionByName(eea, transactionName, EntityPermission.READ_ONLY); 098 } 099 100 public Transaction getTransactionByNameForUpdate(final ExecutionErrorAccumulator eea, final String transactionName) { 101 return getTransactionByName(eea, transactionName, EntityPermission.READ_WRITE); 102 } 103 104 public Transaction getTransactionByUniversalSpec(final ExecutionErrorAccumulator eea, 105 final TransactionUniversalSpec universalSpec, final EntityPermission entityPermission) { 106 Transaction transaction = null; 107 var accountingControl = Session.getModelController(AccountingControl.class); 108 var transactionName = universalSpec.getTransactionName(); 109 var parameterCount = (transactionName == null ? 0 : 1) + EntityInstanceLogic.getInstance().countPossibleEntitySpecs(universalSpec); 110 111 switch(parameterCount) { 112 case 1 -> { 113 if(transactionName == null) { 114 var entityInstance = EntityInstanceLogic.getInstance().getEntityInstance(eea, universalSpec, 115 ComponentVendors.ECHO_THREE.name(), EntityTypes.Transaction.name()); 116 117 if(!eea.hasExecutionErrors()) { 118 transaction = accountingControl.getTransactionByEntityInstance(entityInstance, entityPermission); 119 } 120 } else { 121 transaction = getTransactionByName(eea, transactionName, entityPermission); 122 } 123 } 124 default -> 125 handleExecutionError(InvalidParameterCountException.class, eea, ExecutionErrors.InvalidParameterCount.name()); 126 } 127 128 return transaction; 129 } 130 131 public Transaction getTransactionByUniversalSpec(final ExecutionErrorAccumulator eea, 132 final TransactionUniversalSpec universalSpec) { 133 return getTransactionByUniversalSpec(eea, universalSpec, EntityPermission.READ_ONLY); 134 } 135 136 public Transaction getTransactionByUniversalSpecForUpdate(final ExecutionErrorAccumulator eea, 137 final TransactionUniversalSpec universalSpec) { 138 return getTransactionByUniversalSpec(eea, universalSpec, EntityPermission.READ_WRITE); 139 } 140 141 public TransactionGlEntry createTransactionGlEntryUsingNames(final Transaction transaction, final Party groupParty, 142 final String transactionGlAccountCategoryName, final GlAccount glAccount, final Currency originalCurrency, 143 final Long originalDebit, final Long originalCredit, final BasePK createdBy) { 144 var accountingControl = Session.getModelController(AccountingControl.class); 145 var transactionDetail = transaction.getLastDetail(); 146 147 return createTransactionGlEntry(transaction, groupParty == null ? transactionDetail.getGroupParty() : groupParty, 148 accountingControl.getTransactionGlAccountCategoryByName(transactionDetail.getTransactionType(), transactionGlAccountCategoryName), 149 glAccount, originalCurrency, originalDebit, originalCredit, createdBy); 150 } 151 152 private GlAccount getGlAccount(final AccountingControl accountingControl, final TransactionGlAccountCategory transactionGlAccountCategory, 153 GlAccount glAccount) { 154 if(glAccount == null) { 155 var transactionGlAccount = accountingControl.getTransactionGlAccount(transactionGlAccountCategory); 156 157 if(transactionGlAccount == null) { 158 throw new IllegalArgumentException("glAccount is a required parameter"); 159 } else { 160 glAccount = transactionGlAccount.getGlAccount(); 161 } 162 } 163 164 return glAccount; 165 } 166 167 private Integer getTransactionGlEntrySequence(final AccountingControl accountingControl, final Transaction transaction) { 168 var transactionStatus = accountingControl.getTransactionStatusForUpdate(transaction); 169 Integer transactionGlEntrySequence = transactionStatus.getTransactionGlEntrySequence() + 1; 170 171 transactionStatus.setTransactionGlEntrySequence(transactionGlEntrySequence); 172 173 return transactionGlEntrySequence; 174 } 175 176 private Long getAmount(final GlAccount glAccount, final Currency originalCurrency, final Long originalAmount) { 177 var currency = glAccount.getLastDetail().getCurrency(); 178 179 Long amount; 180 if(originalCurrency.equals(currency)) { 181 amount = originalAmount; 182 } else { 183 throw new IllegalArgumentException("Currency conversion is not available"); 184 } 185 186 return amount; 187 } 188 189 public TransactionGlEntry createTransactionGlEntry(final Transaction transaction, final Party groupParty, 190 final TransactionGlAccountCategory transactionGlAccountCategory, GlAccount glAccount, final Currency originalCurrency, 191 final Long originalDebit, final Long originalCredit, final BasePK createdBy) { 192 var accountingControl = Session.getModelController(AccountingControl.class); 193 194 glAccount = getGlAccount(accountingControl, transactionGlAccountCategory, glAccount); 195 196 var debit = originalDebit == null ? null : getAmount(glAccount, originalCurrency, originalDebit); 197 var credit = originalCredit == null ? null : getAmount(glAccount, originalCurrency, originalCredit); 198 199 return accountingControl.createTransactionGlEntry(transaction, getTransactionGlEntrySequence(accountingControl, transaction), 200 groupParty, transactionGlAccountCategory, glAccount, originalCurrency, originalDebit, 201 originalCredit, debit, credit, createdBy); 202 } 203 204 public TransactionEntityRole createTransactionEntityRoleUsingNames(final Transaction transaction, 205 final String transactionEntityRoleTypeName, final BasePK pk, final BasePK createdBy) { 206 var accountingControl = Session.getModelController(AccountingControl.class); 207 var transactionEntityRoleType = accountingControl.getTransactionEntityRoleTypeByName(transaction.getLastDetail().getTransactionType(), 208 transactionEntityRoleTypeName); 209 210 return createTransactionEntityRole(transaction, transactionEntityRoleType, pk, createdBy); 211 } 212 213 public TransactionEntityRole createTransactionEntityRole(final Transaction transaction, 214 final TransactionEntityRoleType transactionEntityRoleType, final BasePK pk, final BasePK createdBy) { 215 var accountingControl = Session.getModelController(AccountingControl.class); 216 var entityInstanceControl = Session.getModelController(EntityInstanceControl.class); 217 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(pk); 218 219 if(!transactionEntityRoleType.getLastDetail().getEntityType().equals(entityInstance.getEntityType())) { 220 throw new IllegalArgumentException("entityInstance is not of the required EntityType"); 221 } 222 223 return accountingControl.createTransactionEntityRole(transaction, transactionEntityRoleType, entityInstance, createdBy); 224 } 225 226 private void validateTransactionBalanced(final ExecutionErrorAccumulator eea, final Transaction transaction) { 227 var transactionBalancedResults = new TransactionBalancedQuery().execute(transaction); 228 229 if(!transactionBalancedResults.isEmpty()) { 230 var transactionBalancedResult = transactionBalancedResults.getFirst(); 231 var originalDifference = transactionBalancedResult.getOriginalDifference(); 232 var difference = transactionBalancedResult.getDifference(); 233 234 if(originalDifference != 0 || difference != 0) { 235 handleExecutionError(TransactionNotBalancedException.class, eea, ExecutionErrors.TransactionNotBalanced.name(), 236 originalDifference, difference); 237 } 238 } 239 } 240 241 public void postTransaction(final ExecutionErrorAccumulator eea, final Session session, final Transaction transaction, 242 final BasePK createdBy) { 243 var accountingControl = Session.getModelController(AccountingControl.class); 244 var entityInstanceControl = Session.getModelController(EntityInstanceControl.class); 245 var workflowControl = Session.getModelController(WorkflowControl.class); 246 247 validateTransactionBalanced(eea, transaction); 248 249 if(eea != null && !hasExecutionErrors(eea)) { 250 accountingControl.removeTransactionStatusByTransaction(transaction); 251 252 PostingLogic.getInstance().postTransaction(session, transaction, createdBy); 253 254 // If it isn't in the Transaction Status workflow, assume this is a system generated transaction and 255 // we've gone directly to posting it. 256 var workflow = WorkflowLogic.getInstance().getWorkflowByName(null, TransactionStatusConstants.Workflow_TRANSACTION_STATUS); 257 var entityInstance = entityInstanceControl.getEntityInstanceByBasePK(transaction.getPrimaryKey()); 258 if(!workflowControl.isEntityInWorkflow(workflow, entityInstance)) { 259 var workflowEntrance = WorkflowEntranceLogic.getInstance().getWorkflowEntranceByName(null, workflow, 260 TransactionStatusConstants.WorkflowEntrance_TRANSACTION_STATUS_NEW_POSTED); 261 262 workflowControl.addEntityToWorkflow(workflowEntrance, entityInstance, null, null, createdBy); 263 } 264 } 265 } 266 267 public void testTransaction(final ExecutionErrorAccumulator eea, final Session session, final BasePK testedBy) { 268 var accountingControl = Session.getModelController(AccountingControl.class); 269 var partyControl = Session.getModelController(PartyControl.class); 270 var companyParty = partyControl.getDefaultPartyCompany().getParty(); 271 var divisionParty = partyControl.getDefaultPartyDivision(companyParty).getParty(); 272 var departmentParty = partyControl.getDefaultPartyDepartment(divisionParty).getParty(); 273 var originalCurrency = accountingControl.getDefaultCurrency(); 274 275 var transaction = createTransactionUsingNames(session, departmentParty, "TEST", null, testedBy); 276 createTransactionGlEntryUsingNames(transaction, null, "TEST_ACCOUNT_A", null, originalCurrency, null, 1999L, testedBy); 277 createTransactionGlEntryUsingNames(transaction, null, "TEST_ACCOUNT_B", null, originalCurrency, 1999L, null, testedBy); 278 createTransactionEntityRoleUsingNames(transaction, "TEST_ENTITY_INSTANCE_ROLE_TYPE", testedBy, testedBy); 279 postTransaction(eea, session, transaction, testedBy); 280 } 281 282}