001// -------------------------------------------------------------------------------- 002// Copyright 2002-2026 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.offer.server.logic; 018 019import com.echothree.control.user.offer.common.spec.OfferItemUniversalSpec; 020import com.echothree.model.control.accounting.server.logic.CurrencyLogic; 021import com.echothree.model.control.content.server.logic.ContentLogic; 022import com.echothree.model.control.core.common.ComponentVendors; 023import com.echothree.model.control.core.common.EntityTypes; 024import com.echothree.model.control.core.common.exception.InvalidParameterCountException; 025import com.echothree.model.control.core.server.logic.EntityInstanceLogic; 026import com.echothree.model.control.inventory.server.logic.InventoryConditionLogic; 027import com.echothree.model.control.item.common.ItemPriceTypes; 028import com.echothree.model.control.item.common.exception.MissingMaximumUnitPriceException; 029import com.echothree.model.control.item.common.exception.MissingMinimumUnitPriceException; 030import com.echothree.model.control.item.common.exception.MissingUnitPriceException; 031import com.echothree.model.control.item.common.exception.MissingUnitPriceIncrementException; 032import com.echothree.model.control.item.common.exception.UnknownItemPriceException; 033import com.echothree.model.control.item.common.exception.UnknownItemPriceTypeException; 034import com.echothree.model.control.item.server.control.ItemControl; 035import com.echothree.model.control.item.server.logic.ItemLogic; 036import com.echothree.model.control.offer.common.exception.CannotManuallyCreateOfferItemPriceWhenOfferItemPriceFilterSetException; 037import com.echothree.model.control.offer.common.exception.CannotManuallyCreateOfferItemWhenOfferItemSelectorSetException; 038import com.echothree.model.control.offer.common.exception.CannotManuallyDeleteOfferItemPriceWhenOfferItemPriceFilterSetException; 039import com.echothree.model.control.offer.common.exception.CannotManuallyDeleteOfferItemWhenOfferItemSelectorSetException; 040import com.echothree.model.control.offer.common.exception.DuplicateOfferItemException; 041import com.echothree.model.control.offer.common.exception.DuplicateOfferItemPriceException; 042import com.echothree.model.control.offer.common.exception.InvalidItemCompanyException; 043import com.echothree.model.control.offer.common.exception.UnknownOfferItemException; 044import com.echothree.model.control.offer.common.exception.UnknownOfferItemPriceException; 045import com.echothree.model.control.offer.server.control.OfferItemControl; 046import com.echothree.model.control.party.server.control.PartyControl; 047import com.echothree.model.control.uom.server.logic.UnitOfMeasureTypeLogic; 048import com.echothree.model.data.accounting.server.entity.Currency; 049import com.echothree.model.data.inventory.server.entity.InventoryCondition; 050import com.echothree.model.data.item.server.entity.Item; 051import com.echothree.model.data.offer.server.entity.Offer; 052import com.echothree.model.data.offer.server.entity.OfferItem; 053import com.echothree.model.data.offer.server.entity.OfferItemFixedPrice; 054import com.echothree.model.data.offer.server.entity.OfferItemPrice; 055import com.echothree.model.data.offer.server.entity.OfferItemVariablePrice; 056import com.echothree.model.data.offer.server.value.OfferItemFixedPriceValue; 057import com.echothree.model.data.offer.server.value.OfferItemVariablePriceValue; 058import com.echothree.model.data.uom.server.entity.UnitOfMeasureType; 059import com.echothree.util.common.message.ExecutionErrors; 060import com.echothree.util.common.persistence.BasePK; 061import com.echothree.util.server.control.BaseLogic; 062import com.echothree.util.server.message.ExecutionErrorAccumulator; 063import com.echothree.util.server.persistence.EntityPermission; 064import com.echothree.util.server.validation.ParameterUtils; 065import java.util.List; 066import javax.enterprise.context.ApplicationScoped; 067import javax.enterprise.inject.spi.CDI; 068import javax.inject.Inject; 069 070@ApplicationScoped 071public class OfferItemLogic 072 extends BaseLogic { 073 074 @Inject 075 ItemControl itemControl; 076 077 @Inject 078 OfferItemControl offerItemControl; 079 080 @Inject 081 PartyControl partyControl; 082 083 @Inject 084 ContentLogic contentLogic; 085 086 @Inject 087 CurrencyLogic currencyLogic; 088 089 @Inject 090 EntityInstanceLogic entityInstanceLogic; 091 092 @Inject 093 InventoryConditionLogic inventoryConditionLogic; 094 095 @Inject 096 ItemLogic itemLogic; 097 098 @Inject 099 OfferLogic offerLogic; 100 101 @Inject 102 UnitOfMeasureTypeLogic unitOfMeasureTypeLogic; 103 104 protected OfferItemLogic() { 105 super(); 106 } 107 108 public static OfferItemLogic getInstance() { 109 return CDI.current().select(OfferItemLogic.class).get(); 110 } 111 112 // -------------------------------------------------------------------------------- 113 // Offer Items 114 // -------------------------------------------------------------------------------- 115 116 // This one is intended to be used internally to ensure any dependent actions occur. 117 public OfferItem createOfferItem(final Offer offer, final Item item, final BasePK createdBy) { 118 return offerItemControl.createOfferItem(offer, item, createdBy); 119 } 120 121 // This one is intended to be used by interactive users of the application to ensure all necessary 122 // validation occurs. 123 public OfferItem createOfferItem(final ExecutionErrorAccumulator eea, final Offer offer, final Item item, 124 final BasePK createdBy) { 125 OfferItem offerItem = null; 126 final var offerDetail = offer.getLastDetail(); 127 128 if(offerDetail.getOfferItemSelector() == null) { 129 var partyDepartment = partyControl.getPartyDepartment(offerDetail.getDepartmentParty()); 130 var partyDivision = partyControl.getPartyDivision(partyDepartment.getDivisionParty()); 131 var partyCompany = partyControl.getPartyCompany(partyDivision.getCompanyParty()); 132 133 if(partyCompany.getParty().equals(item.getLastDetail().getCompanyParty())) { 134 offerItem = offerItemControl.getOfferItem(offer, item); 135 136 if(offerItem == null) { 137 offerItem = createOfferItem(offer, item, createdBy); 138 } else { 139 handleExecutionError(DuplicateOfferItemException.class, eea, ExecutionErrors.DuplicateOfferItem.name(), 140 offerDetail.getOfferName(), item.getLastDetail().getItemName()); 141 } 142 } else { 143 handleExecutionError(InvalidItemCompanyException.class, eea, ExecutionErrors.InvalidItemCompany.name(), 144 partyCompany.getPartyCompanyName(), 145 partyControl.getPartyCompany(item.getLastDetail().getCompanyParty()).getPartyCompanyName()); 146 } 147 } else { 148 handleExecutionError(CannotManuallyCreateOfferItemWhenOfferItemSelectorSetException.class, eea, 149 ExecutionErrors.CannotManuallyCreateOfferItemWhenOfferItemSelectorSet.name(), 150 offerDetail.getOfferName()); 151 } 152 153 return offerItem; 154 } 155 156 public OfferItem getOfferItem(final ExecutionErrorAccumulator eea, final Offer offer, final Item item, 157 final EntityPermission entityPermission) { 158 var offerItem = offerItemControl.getOfferItem(offer, item, entityPermission); 159 160 if(offerItem == null) { 161 handleExecutionError(UnknownOfferItemException.class, eea, ExecutionErrors.UnknownOfferItem.name(), 162 offer.getLastDetail().getOfferName(), item.getLastDetail().getItemName()); 163 } 164 165 return offerItem; 166 } 167 168 public OfferItem getOfferItem(final ExecutionErrorAccumulator eea, final Offer offer, final Item item) { 169 return getOfferItem(eea, offer, item, EntityPermission.READ_ONLY); 170 } 171 172 public OfferItem getOfferItemForUpdate(final ExecutionErrorAccumulator eea, final Offer offer, final Item item) { 173 return getOfferItem(eea, offer, item, EntityPermission.READ_WRITE); 174 } 175 176 public OfferItem getOfferItemByUniversalSpec(final ExecutionErrorAccumulator eea, final OfferItemUniversalSpec universalSpec, 177 final EntityPermission entityPermission) { 178 var offerName = universalSpec.getOfferName(); 179 var itemName = universalSpec.getItemName(); 180 var nameParameterCount= ParameterUtils.getInstance().countNonNullParameters(offerName, itemName); 181 var possibleEntitySpecs= entityInstanceLogic.countPossibleEntitySpecs(universalSpec); 182 OfferItem offerItem = null; 183 184 if(nameParameterCount == 2 && possibleEntitySpecs == 0) { 185 var offer = offerLogic.getOfferByName(eea, offerName); 186 var item = itemLogic.getItemByName(eea, itemName); 187 188 if(eea == null || !eea.hasExecutionErrors()) { 189 offerItem = getOfferItem(eea, offer, item); 190 } 191 } else if(nameParameterCount == 0 && possibleEntitySpecs == 1) { 192 var entityInstance = entityInstanceLogic.getEntityInstance(eea, universalSpec, 193 ComponentVendors.ECHO_THREE.name(), EntityTypes.OfferItem.name()); 194 195 if(eea == null || !eea.hasExecutionErrors()) { 196 offerItem = offerItemControl.getOfferItemByEntityInstance(entityInstance, entityPermission); 197 } 198 } else { 199 handleExecutionError(InvalidParameterCountException.class, eea, ExecutionErrors.InvalidParameterCount.name()); 200 } 201 202 return offerItem; 203 } 204 205 public OfferItem getOfferItemByUniversalSpec(final ExecutionErrorAccumulator eea, final OfferItemUniversalSpec universalSpec) { 206 return getOfferItemByUniversalSpec(eea, universalSpec, EntityPermission.READ_ONLY); 207 } 208 209 public OfferItem getOfferItemByUniversalSpecForUpdate(final ExecutionErrorAccumulator eea, final OfferItemUniversalSpec universalSpec) { 210 return getOfferItemByUniversalSpec(eea, universalSpec, EntityPermission.READ_WRITE); 211 } 212 213 public void deleteOfferItem(final OfferItem offerItem, final BasePK deletedBy) { 214 offerItemControl.deleteOfferItem(offerItem, deletedBy); 215 } 216 217 public void deleteOfferItem(final ExecutionErrorAccumulator eea, final OfferItem offerItem, final BasePK deletedBy) { 218 final var offerDetail = offerItem.getOffer().getLastDetail(); 219 220 if(offerDetail.getOfferItemSelector() == null) { 221 deleteOfferItem(offerItem, deletedBy); 222 } else { 223 handleExecutionError(CannotManuallyDeleteOfferItemWhenOfferItemSelectorSetException.class, eea, 224 ExecutionErrors.CannotManuallyDeleteOfferItemWhenOfferItemSelectorSet.name(), 225 offerDetail.getOfferName()); 226 } 227 } 228 229 public void deleteOfferItems(List<OfferItem> offerItems, BasePK deletedBy) { 230 offerItems.forEach((offerItem) -> 231 deleteOfferItem(offerItem, deletedBy) 232 ); 233 } 234 235 public void deleteOfferItemsByOffer(Offer offer, BasePK deletedBy) { 236 deleteOfferItems(offerItemControl.getOfferItemsByOfferForUpdate(offer), deletedBy); 237 } 238 239 public void deleteOfferItemsByOffers(List<Offer> offers, BasePK deletedBy) { 240 offers.forEach((offer) -> 241 deleteOfferItemsByOffer(offer, deletedBy) 242 ); 243 } 244 245 // -------------------------------------------------------------------------------- 246 // Offer Item Prices 247 // -------------------------------------------------------------------------------- 248 249 public OfferItemPrice createOfferItemPrice(final OfferItem offerItem, final InventoryCondition inventoryCondition, 250 final UnitOfMeasureType unitOfMeasureType, final Currency currency, final BasePK createdBy) { 251 return offerItemControl.createOfferItemPrice(offerItem, inventoryCondition, unitOfMeasureType, currency, createdBy); 252 } 253 254 public void createOfferItemPrice(final ExecutionErrorAccumulator eea, final String offerName, final String itemName, final String inventoryConditionName, 255 final String unitOfMeasureTypeName, final String currencyIsoName, final String strUnitPrice, final String strMinimumUnitPrice, 256 final String strMaximumUnitPrice, final String strUnitPriceIncrement, final BasePK createdBy) { 257 var offer = offerLogic.getOfferByName(eea, offerName); 258 259 if(eea == null || !eea.hasExecutionErrors()) { 260 final var offerDetail = offer.getLastDetail(); 261 262 if(offerDetail.getOfferItemPriceFilter() == null) { 263 var item = itemLogic.getItemByName(eea, itemName); 264 var inventoryCondition = inventoryConditionLogic.getInventoryConditionByName(eea, inventoryConditionName); 265 var currency = currencyLogic.getCurrencyByName(eea, currencyIsoName); 266 267 if(eea == null || !eea.hasExecutionErrors()) { 268 var itemDetail = item.getLastDetail(); 269 var unitOfMeasureKind = itemDetail.getUnitOfMeasureKind(); 270 var unitOfMeasureType = unitOfMeasureTypeLogic.getUnitOfMeasureTypeByName(eea, 271 unitOfMeasureKind, unitOfMeasureTypeName); 272 273 if(eea == null || !eea.hasExecutionErrors()) { 274 var offerItem = offerItemControl.getOfferItem(offer, item); 275 276 if(offerItem != null) { 277 var itemPrice = itemControl.getItemPrice(item, inventoryCondition, unitOfMeasureType, currency); 278 279 if(itemPrice != null) { 280 var offerItemPrice = offerItemControl.getOfferItemPrice(offerItem, inventoryCondition, 281 unitOfMeasureType, currency); 282 283 if(offerItemPrice == null) { 284 var itemPriceTypeName = itemDetail.getItemPriceType().getItemPriceTypeName(); 285 286 if(itemPriceTypeName.equals(ItemPriceTypes.FIXED.name())) { 287 if(strUnitPrice != null) { 288 var unitPrice = Long.valueOf(strUnitPrice); 289 290 offerItemPrice = this.createOfferItemPrice(offerItem, inventoryCondition, 291 unitOfMeasureType, currency, createdBy); 292 this.createOfferItemFixedPrice(offerItemPrice, unitPrice, createdBy); 293 } else { 294 handleExecutionError(MissingUnitPriceException.class, eea, ExecutionErrors.MissingUnitPrice.name()); 295 } 296 } else if(itemPriceTypeName.equals(ItemPriceTypes.VARIABLE.name())) { 297 Long minimumUnitPrice = null; 298 Long maximumUnitPrice = null; 299 Long unitPriceIncrement = null; 300 301 if(strMinimumUnitPrice != null) { 302 minimumUnitPrice = Long.valueOf(strMinimumUnitPrice); 303 } else { 304 handleExecutionError(MissingMinimumUnitPriceException.class, eea, ExecutionErrors.MissingMinimumUnitPrice.name()); 305 } 306 307 if(strMaximumUnitPrice != null) { 308 maximumUnitPrice = Long.valueOf(strMaximumUnitPrice); 309 } else { 310 handleExecutionError(MissingMaximumUnitPriceException.class, eea, ExecutionErrors.MissingMaximumUnitPrice.name()); 311 } 312 313 if(strUnitPriceIncrement != null) { 314 unitPriceIncrement = Long.valueOf(strUnitPriceIncrement); 315 } else { 316 handleExecutionError(MissingUnitPriceIncrementException.class, eea, ExecutionErrors.MissingUnitPriceIncrement.name()); 317 } 318 319 if(minimumUnitPrice != null && maximumUnitPrice != null && unitPriceIncrement != null) { 320 offerItemPrice = this.createOfferItemPrice(offerItem, inventoryCondition, 321 unitOfMeasureType, currency, createdBy); 322 this.createOfferItemVariablePrice(offerItemPrice, minimumUnitPrice, maximumUnitPrice, 323 unitPriceIncrement, createdBy); 324 } 325 } else { 326 handleExecutionError(UnknownItemPriceTypeException.class, eea, ExecutionErrors.UnknownItemPriceType.name()); 327 } 328 } else { 329 handleExecutionError(DuplicateOfferItemPriceException.class, eea, ExecutionErrors.DuplicateOfferItemPrice.name()); 330 } 331 } else { 332 handleExecutionError(UnknownItemPriceException.class, eea, ExecutionErrors.UnknownItemPrice.name()); 333 } 334 } else { 335 handleExecutionError(UnknownOfferItemException.class, eea, ExecutionErrors.UnknownOfferItem.name(), offerName, itemName); 336 } 337 } 338 } 339 } else { 340 handleExecutionError(CannotManuallyCreateOfferItemPriceWhenOfferItemPriceFilterSetException.class, eea, 341 ExecutionErrors.CannotManuallyCreateOfferItemPriceWhenOfferItemPriceFilterSet.name(), 342 offerDetail.getOfferName()); 343 } 344 } 345 } 346 347 public OfferItemPrice getOfferItemPrice(final ExecutionErrorAccumulator eea, final Offer offer, final Item item, 348 final InventoryCondition inventoryCondition, final UnitOfMeasureType unitOfMeasureType, final Currency currency) { 349 var offerItem = offerItemControl.getOfferItem(offer, item); 350 OfferItemPrice offerItemPrice = null; 351 352 if(offerItem != null) { 353 offerItemPrice = offerItemControl.getOfferItemPrice(offerItem, inventoryCondition, unitOfMeasureType, currency); 354 355 if(offerItemPrice == null) { 356 handleExecutionError(UnknownOfferItemPriceException.class, eea, ExecutionErrors.UnknownOfferItemPrice.name(), offer.getLastDetail().getOfferName(), 357 item.getLastDetail().getItemName(), inventoryCondition.getLastDetail().getInventoryConditionName(), 358 unitOfMeasureType.getLastDetail().getUnitOfMeasureTypeName(), currency.getCurrencyIsoName()); 359 } 360 } else { 361 handleExecutionError(UnknownOfferItemException.class, eea, ExecutionErrors.UnknownOfferItem.name(), 362 offer.getLastDetail().getOfferName(), item.getLastDetail().getItemName()); 363 } 364 365 return offerItemPrice; 366 } 367 368 // This one is intended to be used internally to ensure any dependent actions occur. 369 public void deleteOfferItemPrice(final OfferItemPrice offerItemPrice, final BasePK deletedBy) { 370 var offerItem = offerItemPrice.getOfferItemForUpdate(); 371 var item = offerItem.getItem(); 372 373 contentLogic.deleteContentCategoryItemByOfferItemPrice(offerItemPrice, deletedBy); 374 375 offerItemControl.deleteOfferItemPrice(offerItemPrice, deletedBy); 376 377 // If all OfferItemPrices have been deleted, delete the OfferItem as well. 378 if(offerItemControl.countOfferItemPricesByItem(item) == 0) { 379 deleteOfferItem(offerItem, deletedBy); 380 } 381 } 382 383 // This one is intended to be used by interactive users of the application to ensure all necessary 384 // validation occurs. 385 public void deleteOfferItemPrice(final ExecutionErrorAccumulator eea, final String offerName, final String itemName, 386 final String inventoryConditionName, final String unitOfMeasureTypeName, final String currencyIsoName, 387 final BasePK deletedBy) { 388 var offer = offerLogic.getOfferByName(eea, offerName); 389 390 if(eea == null || !eea.hasExecutionErrors()) { 391 final var offerDetail = offer.getLastDetail(); 392 393 if(offerDetail.getOfferItemPriceFilter() == null) { 394 var item = itemLogic.getItemByName(eea, itemName); 395 var inventoryCondition = inventoryConditionLogic.getInventoryConditionByName(eea, inventoryConditionName); 396 var currency = currencyLogic.getCurrencyByName(eea, currencyIsoName); 397 398 if(eea == null || !eea.hasExecutionErrors()) { 399 var unitOfMeasureKind = item.getLastDetail().getUnitOfMeasureKind(); 400 var unitOfMeasureType = unitOfMeasureTypeLogic.getUnitOfMeasureTypeByName(eea, 401 unitOfMeasureKind, unitOfMeasureTypeName); 402 403 if(eea == null || !eea.hasExecutionErrors()) { 404 var offerItem = offerItemControl.getOfferItem(offer, item); 405 406 if(offerItem != null) { 407 var offerItemPrice = offerItemControl.getOfferItemPriceForUpdate(offerItem, inventoryCondition, 408 unitOfMeasureType, currency); 409 410 if(offerItemPrice != null) { 411 deleteOfferItemPrice(offerItemPrice, deletedBy); 412 } else { 413 handleExecutionError(UnknownOfferItemPriceException.class, eea, ExecutionErrors.UnknownOfferItemPrice.name(), 414 offerName, itemName, inventoryConditionName, unitOfMeasureTypeName, currencyIsoName); 415 } 416 } else { 417 handleExecutionError(UnknownOfferItemException.class, eea, ExecutionErrors.UnknownOfferItem.name(), 418 offerName, itemName); 419 } 420 } 421 } 422 } else { 423 handleExecutionError(CannotManuallyDeleteOfferItemPriceWhenOfferItemPriceFilterSetException.class, eea, 424 ExecutionErrors.CannotManuallyDeleteOfferItemPriceWhenOfferItemPriceFilterSet.name(), 425 offerDetail.getOfferName()); 426 } 427 } 428 } 429 430 public void deleteOfferItemPrices(List<OfferItemPrice> offerItemPrices, BasePK deletedBy) { 431 offerItemPrices.forEach((offerItemPrice) -> 432 deleteOfferItemPrice(offerItemPrice, deletedBy) 433 ); 434 } 435 436 public void deleteOfferItemPricesByOfferItem(OfferItem offerItem, BasePK deletedBy) { 437 deleteOfferItemPrices(offerItemControl.getOfferItemPricesByOfferItemForUpdate(offerItem), deletedBy); 438 } 439 440 public void deleteOfferItemPricesByItemAndUnitOfMeasureType(Item item, UnitOfMeasureType unitOfMeasureType, BasePK deletedBy) { 441 deleteOfferItemPrices(offerItemControl.getOfferItemPricesByItemAndUnitOfMeasureTypeForUpdate(item, unitOfMeasureType), deletedBy); 442 } 443 444 // -------------------------------------------------------------------------------- 445 // Offer Item Fixed Prices 446 // -------------------------------------------------------------------------------- 447 448 public OfferItemFixedPrice createOfferItemFixedPrice(final OfferItemPrice offerItemPrice, final Long unitPrice, final BasePK createdBy) { 449 return offerItemControl.createOfferItemFixedPrice(offerItemPrice, unitPrice, createdBy); 450 } 451 452 public void updateOfferItemFixedPriceFromValue(final OfferItemFixedPriceValue offerItemFixedPriceValue, final BasePK updatedBy) { 453 var offerItemFixedPrice = offerItemControl.updateOfferItemFixedPriceFromValue(offerItemFixedPriceValue, updatedBy); 454 455 if(offerItemFixedPrice != null) { 456 contentLogic.updateContentCatalogItemPricesByOfferItemPrice(offerItemFixedPrice.getOfferItemPrice(), updatedBy); 457 } 458 } 459 460 // -------------------------------------------------------------------------------- 461 // Offer Item Variable Prices 462 // -------------------------------------------------------------------------------- 463 464 public OfferItemVariablePrice createOfferItemVariablePrice(final OfferItemPrice offerItemPrice, final Long minimumUnitPrice, 465 final Long maximumUnitPrice, final Long unitPriceIncrement, final BasePK createdBy) { 466 return offerItemControl.createOfferItemVariablePrice(offerItemPrice, minimumUnitPrice, maximumUnitPrice, unitPriceIncrement, createdBy); 467 } 468 469 public void updateOfferItemVariablePriceFromValue(final OfferItemVariablePriceValue offerItemVariablePriceValue, final BasePK updatedBy) { 470 var offerItemVariablePrice = offerItemControl.updateOfferItemVariablePriceFromValue(offerItemVariablePriceValue, updatedBy); 471 472 if(offerItemVariablePrice != null) { 473 contentLogic.updateContentCatalogItemPricesByOfferItemPrice(offerItemVariablePrice.getOfferItemPrice(), updatedBy); 474 } 475 } 476 477}