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.purchase.server.logic; 018 019import com.echothree.model.control.cancellationpolicy.common.CancellationKinds; 020import com.echothree.model.control.cancellationpolicy.server.logic.CancellationPolicyLogic; 021import com.echothree.model.control.core.server.control.CoreControl; 022import com.echothree.model.control.order.common.OrderRoleTypes; 023import com.echothree.model.control.order.common.OrderTypes; 024import com.echothree.model.control.order.server.control.OrderControl; 025import com.echothree.model.control.order.server.control.OrderRoleControl; 026import com.echothree.model.control.order.server.logic.OrderLogic; 027import com.echothree.model.control.purchase.common.choice.PurchaseOrderStatusChoicesBean; 028import com.echothree.model.control.purchase.common.exception.DuplicateHandlingInPurchaseOrderStatusTransitionException; 029import com.echothree.model.control.purchase.common.exception.InvalidPurchaseOrderReferenceException; 030import com.echothree.model.control.purchase.common.exception.InvalidPurchaseOrderStatusException; 031import com.echothree.model.control.purchase.common.exception.PurchaseOrderDuplicateReferenceException; 032import com.echothree.model.control.purchase.common.exception.PurchaseOrderReferenceRequiredException; 033import com.echothree.model.control.purchase.common.exception.UnhandledPurchaseOrderStatusTransitionException; 034import com.echothree.model.control.purchase.common.exception.UnknownPurchaseOrderStatusChoiceException; 035import com.echothree.model.control.purchase.common.workflow.PurchaseOrderStatusConstants; 036import com.echothree.model.control.returnpolicy.common.ReturnKinds; 037import com.echothree.model.control.returnpolicy.server.logic.ReturnPolicyLogic; 038import com.echothree.model.control.sequence.common.SequenceTypes; 039import com.echothree.model.control.sequence.server.logic.SequenceGeneratorLogic; 040import com.echothree.model.control.shipment.server.control.PartyFreeOnBoardControl; 041import com.echothree.model.control.shipment.server.logic.FreeOnBoardLogic; 042import com.echothree.model.control.term.server.control.TermControl; 043import com.echothree.model.control.term.server.logic.TermLogic; 044import com.echothree.model.control.user.server.control.UserControl; 045import com.echothree.model.control.vendor.server.control.VendorControl; 046import com.echothree.model.control.vendor.server.logic.VendorLogic; 047import com.echothree.model.control.workflow.server.control.WorkflowControl; 048import com.echothree.model.control.workflow.server.logic.WorkflowDestinationLogic; 049import com.echothree.model.control.workflow.server.logic.WorkflowLogic; 050import com.echothree.model.control.workflow.server.logic.WorkflowStepLogic; 051import com.echothree.model.data.cancellationpolicy.server.entity.CancellationPolicy; 052import com.echothree.model.data.core.server.entity.EntityInstance; 053import com.echothree.model.data.order.server.entity.Order; 054import com.echothree.model.data.order.server.entity.OrderPriority; 055import com.echothree.model.data.party.common.pk.PartyPK; 056import com.echothree.model.data.party.server.entity.Language; 057import com.echothree.model.data.party.server.entity.Party; 058import com.echothree.model.data.returnpolicy.server.entity.ReturnPolicy; 059import com.echothree.model.data.shipment.server.entity.FreeOnBoard; 060import com.echothree.model.data.term.server.entity.Term; 061import com.echothree.model.data.user.server.entity.UserVisit; 062import com.echothree.model.data.vendor.server.entity.Vendor; 063import com.echothree.model.data.vendor.server.entity.VendorType; 064import com.echothree.util.common.message.ExecutionErrors; 065import com.echothree.util.common.persistence.BasePK; 066import com.echothree.util.server.message.ExecutionErrorAccumulator; 067import com.echothree.util.server.persistence.Session; 068 069public class PurchaseOrderLogic 070 extends OrderLogic { 071 072 private PurchaseOrderLogic() { 073 super(); 074 } 075 076 private static class LogicHolder { 077 static PurchaseOrderLogic instance = new PurchaseOrderLogic(); 078 } 079 080 public static PurchaseOrderLogic getInstance() { 081 return LogicHolder.instance; 082 } 083 084 public void validatePurchaseOrderReference(final ExecutionErrorAccumulator eea, final String reference, 085 final Vendor vendor) { 086 var requireReference = vendor.getRequireReference(); 087 088 if(requireReference != null) { 089 var allowReferenceDuplicates = vendor.getAllowReferenceDuplicates(); 090 var referenceValidationPattern = vendor.getReferenceValidationPattern(); 091 092 if(requireReference && reference == null) { 093 handleExecutionError(PurchaseOrderReferenceRequiredException.class, eea, ExecutionErrors.PurchaseOrderReferenceRequired.name()); 094 } else if(reference != null) { 095 var orderControl = Session.getModelController(OrderControl.class); 096 097 if(!allowReferenceDuplicates && orderControl.countOrdersByBillToAndReference(vendor.getParty(), reference) != 0) { 098 handleExecutionError(PurchaseOrderDuplicateReferenceException.class, eea, ExecutionErrors.PurchaseOrderDuplicateReference.name()); 099 } 100 101 if(referenceValidationPattern != null && !reference.matches(referenceValidationPattern)) { 102 handleExecutionError(InvalidPurchaseOrderReferenceException.class, eea, ExecutionErrors.InvalidPurchaseOrderReference.name()); 103 } 104 } 105 } 106 } 107 108 public CancellationPolicy getCancellationPolicy(final ExecutionErrorAccumulator eea, final VendorType vendorType, final Vendor billToVendor) { 109 return CancellationPolicyLogic.getInstance().getDefaultCancellationPolicyByKind(eea, CancellationKinds.VENDOR_CANCELLATION.name(), 110 new CancellationPolicy[]{ 111 billToVendor == null ? null : billToVendor.getCancellationPolicy(), 112 vendorType.getLastDetail().getDefaultCancellationPolicy() 113 }); 114 } 115 116 public ReturnPolicy getReturnPolicy(final ExecutionErrorAccumulator eea, final VendorType vendorType, final Vendor billToVendor) { 117 return ReturnPolicyLogic.getInstance().getDefaultReturnPolicyByKind(eea, ReturnKinds.VENDOR_RETURN.name(), 118 new ReturnPolicy[]{ 119 billToVendor == null ? null : billToVendor.getReturnPolicy(), 120 vendorType.getLastDetail().getDefaultReturnPolicy() 121 }); 122 } 123 124 /** 125 * Create a new Purchase Order using appropriate defaults where possible for Optional parameters. 126 * 127 * @param session Required. 128 * @param eea Required. 129 * @param userVisit Required. 130 * @param vendorParty Required. 131 * @param holdUntilComplete Optional. 132 * @param allowBackorders Optional. 133 * @param allowSubstitutions Optional. 134 * @param allowCombiningShipments Optional. 135 * @param reference Optional. 136 * @param term Optional. 137 * @param workflowEntranceName Optional. 138 * @param createdByParty Required. 139 * @return The newly created Order, or null if there was an error. 140 */ 141 public Order createPurchaseOrder(final Session session, final ExecutionErrorAccumulator eea, final UserVisit userVisit, 142 final Party vendorParty, Boolean holdUntilComplete, Boolean allowBackorders, Boolean allowSubstitutions, 143 Boolean allowCombiningShipments, final String reference, Term term, FreeOnBoard freeOnBoard, 144 final String workflowEntranceName, final Party createdByParty) { 145 var orderType = getOrderTypeByName(eea, OrderTypes.PURCHASE_ORDER.name()); 146 var billToOrderRoleType = getOrderRoleTypeByName(eea, OrderRoleTypes.BILL_TO.name()); 147 var placingOrderRoleType = getOrderRoleTypeByName(eea, OrderRoleTypes.PLACING.name()); 148 Order order = null; 149 150 if(eea == null || !eea.hasExecutionErrors()) { 151 var orderControl = Session.getModelController(OrderControl.class); 152 var partyFreeOnBoardControl = Session.getModelController(PartyFreeOnBoardControl.class); 153 var termControl = Session.getModelController(TermControl.class); 154 var userControl = Session.getModelController(UserControl.class); 155 var vendorControl = Session.getModelController(VendorControl.class); 156 var currency = userControl.getPreferredCurrencyFromParty(vendorParty); 157 var vendor = vendorControl.getVendor(vendorParty); 158 var vendorType = vendor.getVendorType(); 159 160 holdUntilComplete = holdUntilComplete == null ? vendor.getHoldUntilComplete() : holdUntilComplete; 161 allowBackorders = allowBackorders == null ? vendor.getAllowBackorders() : allowBackorders; 162 allowSubstitutions = allowSubstitutions == null ? vendor.getAllowSubstitutions() : allowSubstitutions; 163 allowCombiningShipments = allowCombiningShipments == null ? vendor.getAllowCombiningShipments() : allowCombiningShipments; 164 165 term = term == null ? termControl.getPartyTerm(vendorParty).getTerm() : term; 166 freeOnBoard = freeOnBoard == null ? partyFreeOnBoardControl.getPartyFreeOnBoard(vendorParty).getFreeOnBoard() : freeOnBoard; 167 168 var cancellationPolicy = getCancellationPolicy(eea, vendorType, vendor); 169 var returnPolicy = getReturnPolicy(eea, vendorType, vendor); 170 171 validatePurchaseOrderReference(eea, reference, vendor); 172 173 var sequence = SequenceGeneratorLogic.getInstance().getDefaultSequence(eea, SequenceTypes.PURCHASE_ORDER.name()); 174 175 if(eea == null || !eea.hasExecutionErrors()) { 176 var coreControl = Session.getModelController(CoreControl.class); 177 var orderRoleControl = Session.getModelController(OrderRoleControl.class); 178 var workflowControl = Session.getModelController(WorkflowControl.class); 179 var userSesson = userControl.getUserSessionByUserVisit(userVisit);; 180 var createdByPartyPK = createdByParty.getPrimaryKey(); 181 182 order = createOrder(eea, orderType, sequence, null, currency, holdUntilComplete, allowBackorders, 183 allowSubstitutions, allowCombiningShipments, term, freeOnBoard, reference, null, 184 cancellationPolicy, returnPolicy, null, createdByPartyPK); 185 186 orderControl.createOrderUserVisit(order, userVisit); 187 188 var entityInstance = coreControl.getEntityInstanceByBasePK(order.getPrimaryKey()); 189 workflowControl.addEntityToWorkflowUsingNames(null, PurchaseOrderStatusConstants.Workflow_PURCHASE_ORDER_STATUS, 190 workflowEntranceName, entityInstance, null, null, createdByPartyPK); 191 192 orderRoleControl.createOrderRole(order, userSesson.getPartyRelationship().getFromParty(), billToOrderRoleType, createdByPartyPK); 193 194 orderRoleControl.createOrderRole(order, createdByParty, placingOrderRoleType, createdByPartyPK); 195 } 196 } 197 198 return order; 199 } 200 201 public Order createPurchaseOrder(final Session session, final ExecutionErrorAccumulator eea, final UserVisit userVisit, 202 final String vendorName, final String termName, final String strHoldUntilComplete, final String strAllowBackorders, 203 final String strAllowSubstitutions, final String strAllowCombiningShipments, final String reference, final String freeOnBoardName, 204 final String workflowEntranceName, final Party createdByParty) { 205 var vendor = VendorLogic.getInstance().getVendorByName(eea, vendorName, null, null); 206 var term = termName == null ? null : TermLogic.getInstance().getTermByName(eea, termName); 207 var freeOnBoard = freeOnBoardName == null ? null : FreeOnBoardLogic.getInstance().getFreeOnBoardByName(eea, freeOnBoardName); 208 Order order = null; 209 210 if(!eea.hasExecutionErrors()) { 211 var holdUntilComplete = strHoldUntilComplete == null ? null : Boolean.valueOf(strHoldUntilComplete); 212 var allowBackorders = strAllowBackorders == null ? null : Boolean.valueOf(strAllowBackorders); 213 var allowSubstitutions = strAllowSubstitutions == null ? null : Boolean.valueOf(strAllowSubstitutions); 214 var allowCombiningShipments = strAllowCombiningShipments == null ? null : Boolean.valueOf(strAllowCombiningShipments); 215 216 order = createPurchaseOrder(session, eea, userVisit, vendor.getParty(), holdUntilComplete, allowBackorders, 217 allowSubstitutions, allowCombiningShipments, reference, term, freeOnBoard, workflowEntranceName, 218 createdByParty); 219 } 220 221 return order; 222 } 223 224 public boolean isOrderInWorkflowSteps(final ExecutionErrorAccumulator eea, final Order order, final String... workflowStepNames) { 225 return isOrderInWorkflowSteps(eea, getEntityInstanceByBaseEntity(order), workflowStepNames); 226 } 227 228 public boolean isOrderInWorkflowSteps(final ExecutionErrorAccumulator eea, final EntityInstance entityInstance, final String... workflowStepNames) { 229 return !WorkflowStepLogic.getInstance().isEntityInWorkflowSteps(eea, PurchaseOrderStatusConstants.Workflow_PURCHASE_ORDER_STATUS, entityInstance, 230 workflowStepNames).isEmpty(); 231 } 232 233 public Order getOrderByName(final ExecutionErrorAccumulator eea, final String orderName) { 234 return getOrderByName(eea, OrderTypes.PURCHASE_ORDER.name(), orderName); 235 } 236 237 public Order getOrderByNameForUpdate(final ExecutionErrorAccumulator eea, final String orderName) { 238 return getOrderByNameForUpdate(eea, OrderTypes.PURCHASE_ORDER.name(), orderName); 239 } 240 241 public OrderPriority getOrderPriorityByName(final ExecutionErrorAccumulator eea, final String orderPriorityName) { 242 return getOrderPriorityByName(eea, OrderTypes.PURCHASE_ORDER.name(), orderPriorityName); 243 } 244 245 public OrderPriority getOrderPriorityByNameForUpdate(final ExecutionErrorAccumulator eea, final String orderPriorityName) { 246 return getOrderPriorityByNameForUpdate(eea, OrderTypes.PURCHASE_ORDER.name(), orderPriorityName); 247 } 248 249 public PurchaseOrderStatusChoicesBean getPurchaseOrderStatusChoices(final String defaultOrderStatusChoice, final Language language, final boolean allowNullChoice, 250 final Order order, final PartyPK partyPK) { 251 var workflowControl = Session.getModelController(WorkflowControl.class); 252 var purchaseOrderStatusChoicesBean = new PurchaseOrderStatusChoicesBean(); 253 254 if(order == null) { 255 workflowControl.getWorkflowEntranceChoices(purchaseOrderStatusChoicesBean, defaultOrderStatusChoice, language, allowNullChoice, 256 workflowControl.getWorkflowByName(PurchaseOrderStatusConstants.Workflow_PURCHASE_ORDER_STATUS), partyPK); 257 } else { 258 var coreControl = Session.getModelController(CoreControl.class); 259 var entityInstance = coreControl.getEntityInstanceByBasePK(order.getPrimaryKey()); 260 var workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceUsingNames(PurchaseOrderStatusConstants.Workflow_PURCHASE_ORDER_STATUS, entityInstance); 261 262 workflowControl.getWorkflowDestinationChoices(purchaseOrderStatusChoicesBean, defaultOrderStatusChoice, language, allowNullChoice, workflowEntityStatus.getWorkflowStep(), partyPK); 263 } 264 265 return purchaseOrderStatusChoicesBean; 266 } 267 268 public void setPurchaseOrderStatus(final Session session, final ExecutionErrorAccumulator eea, final Order order, final String orderStatusChoice, final PartyPK modifiedBy) { 269 var coreControl = Session.getModelController(CoreControl.class); 270 var workflowControl = Session.getModelController(WorkflowControl.class); 271 var workflow = WorkflowLogic.getInstance().getWorkflowByName(eea, PurchaseOrderStatusConstants.Workflow_PURCHASE_ORDER_STATUS); 272 var entityInstance = coreControl.getEntityInstanceByBasePK(order.getPrimaryKey()); 273 var workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceForUpdate(workflow, entityInstance); 274 var workflowDestination = orderStatusChoice == null? null: workflowControl.getWorkflowDestinationByName(workflowEntityStatus.getWorkflowStep(), orderStatusChoice); 275 276 if(workflowDestination != null || orderStatusChoice == null) { 277 var workflowDestinationLogic = WorkflowDestinationLogic.getInstance(); 278 var currentWorkflowStepName = workflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName(); 279 var map = workflowDestinationLogic.getWorkflowDestinationsAsMap(workflowDestination); 280 var handled = false; 281 Long triggerTime = null; 282 283 if(currentWorkflowStepName.equals(PurchaseOrderStatusConstants.WorkflowStep_ENTRY)) { 284 if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, PurchaseOrderStatusConstants.Workflow_PURCHASE_ORDER_STATUS, PurchaseOrderStatusConstants.WorkflowStep_ENTRY_COMPLETE)) { 285 // TODO: What happens moving from ENTRY to ENTRY_COMPLETE? 286 handled = true; 287 } 288 289 if(workflowDestinationLogic.workflowDestinationMapContainsStep(map, PurchaseOrderStatusConstants.Workflow_PURCHASE_ORDER_STATUS, PurchaseOrderStatusConstants.WorkflowStep_CANCELED)) { 290 if(!handled) { 291 // TODO: What happens moving from ENTRY to CANCELED? 292 // TODO: Purchase Order Lines need to be canceled as well. 293 handled = true; 294 } else { 295 handleExecutionError(DuplicateHandlingInPurchaseOrderStatusTransitionException.class, eea, ExecutionErrors.DuplicateHandlingInPurchaseOrderStatusTransition.name(), orderStatusChoice); 296 } 297 } 298 } 299 300 if(eea == null || !eea.hasExecutionErrors()) { 301 if(handled) { 302 workflowControl.transitionEntityInWorkflow(eea, workflowEntityStatus, workflowDestination, triggerTime, modifiedBy); 303 } else { 304 handleExecutionError(UnhandledPurchaseOrderStatusTransitionException.class, eea, ExecutionErrors.UnhandledPurchaseOrderStatusTransition.name(), orderStatusChoice); 305 } 306 } 307 } else { 308 handleExecutionError(UnknownPurchaseOrderStatusChoiceException.class, eea, ExecutionErrors.UnknownPurchaseOrderStatusChoice.name(), orderStatusChoice); 309 } 310 } 311 312 /** Check to see if an Order is available for modification, and if it isn't, send back an error. 313 * 314 * @param session Required. 315 * @param eea Required. 316 * @param order Required. 317 * @param modifiedBy Required. 318 */ 319 public void checkOrderAvailableForModification(final Session session, final ExecutionErrorAccumulator eea, final Order order, final PartyPK modifiedBy) { 320 var workflowControl = Session.getModelController(WorkflowControl.class); 321 var workflowEntityStatus = workflowControl.getWorkflowEntityStatusByEntityInstanceForUpdateUsingNames(PurchaseOrderStatusConstants.Workflow_PURCHASE_ORDER_STATUS, getEntityInstanceByBaseEntity(order)); 322 var workflowStepName = workflowEntityStatus.getWorkflowStep().getLastDetail().getWorkflowStepName(); 323 324 if(!workflowStepName.equals(PurchaseOrderStatusConstants.WorkflowStep_ENTRY)) { 325 handleExecutionError(InvalidPurchaseOrderStatusException.class, eea, ExecutionErrors.InvalidPurchaseOrderStatus.name(), order.getLastDetail().getOrderName(), workflowStepName); 326 } 327 } 328 329 /** Find the BILL_TO Party for a given Order. 330 * 331 * @param order Required. 332 * @return The Party used for the BILL_TO OrderRoleType. May be null. 333 */ 334 public Party getOrderBillToParty(final Order order) { 335 var orderRoleControl = Session.getModelController(OrderRoleControl.class); 336 var billToOrderRole = orderRoleControl.getOrderRoleByOrderAndOrderRoleTypeUsingNames(order, OrderRoleTypes.BILL_TO.name()); 337 Party party = null; 338 339 if(billToOrderRole != null) { 340 party = billToOrderRole.getParty(); 341 } 342 343 return party; 344 } 345 346 /** Find the VendorType for a given Party. 347 * 348 * @param party Optional. 349 * @return The VendorType for the Party. May be null. 350 */ 351 public VendorType getVendorTypeFromParty(final Party party) { 352 var vendorControl = Session.getModelController(VendorControl.class); 353 var vendor = party == null ? null : vendorControl.getVendor(party); 354 VendorType vendorType = null; 355 356 if(vendor != null) { 357 vendorType = vendor.getVendorType(); 358 } 359 360 return vendorType; 361 } 362 363 /** Find the VendorType for the BILL_TO Party for a given Order. 364 * 365 * @param order Required. 366 * @return The VendorType for the BILL_TO Party. May be null. 367 */ 368 public VendorType getOrderBillToVendorType(final Order order) { 369 return getVendorTypeFromParty(getOrderBillToParty(order)); 370 } 371 372 /** Attempt to find a SHIP_TO Party for the Order. If none is found, and billToFallback is set to true, then 373 * attempt to find the BILL_TO Party. If a BILL_TO Party is found, copy it to the SHIP_TO. 374 * 375 * @param order Required. 376 * @param billToFallback Required. 377 * @param createdBy Required if billToFallback is true. 378 * @return The Party that is to be used for the SHIP_TO OrderRoleType. May be null. 379 */ 380 public Party getOrderShipToParty(final Order order, final boolean billToFallback, final BasePK createdBy) { 381 var orderRoleControl = Session.getModelController(OrderRoleControl.class); 382 var shipToOrderRole = orderRoleControl.getOrderRoleByOrderAndOrderRoleTypeUsingNames(order, OrderRoleTypes.SHIP_TO.name()); 383 384 if(shipToOrderRole == null && billToFallback) { 385 shipToOrderRole = orderRoleControl.getOrderRoleByOrderAndOrderRoleTypeUsingNames(order, OrderRoleTypes.BILL_TO.name()); 386 387 if(shipToOrderRole != null) { 388 orderRoleControl.createOrderRoleUsingNames(order, shipToOrderRole.getParty(), OrderRoleTypes.SHIP_TO.name(), createdBy); 389 } 390 } 391 392 return shipToOrderRole == null ? null : shipToOrderRole.getParty(); 393 } 394 395}