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.inventory.server.control; 018 019import com.echothree.model.control.core.common.EventTypes; 020import com.echothree.model.control.inventory.common.transfer.InventoryLocationTransfer; 021import com.echothree.model.control.inventory.server.transfer.InventoryLocationTransferCache; 022import com.echothree.model.data.inventory.server.entity.InventoryCondition; 023import com.echothree.model.data.inventory.server.entity.InventoryLocation; 024import com.echothree.model.data.inventory.server.factory.InventoryLocationFactory; 025import com.echothree.model.data.inventory.server.value.InventoryLocationValue; 026import com.echothree.model.data.item.server.entity.Item; 027import com.echothree.model.data.party.server.entity.Party; 028import com.echothree.model.data.uom.server.entity.UnitOfMeasureType; 029import com.echothree.model.data.user.server.entity.UserVisit; 030import com.echothree.model.data.warehouse.server.entity.Location; 031import static com.echothree.model.jooq.server.tables.inventory.InventoryConditionDetails.InventoryConditionDetails; 032import static com.echothree.model.jooq.server.tables.inventory.InventoryConditions.InventoryConditions; 033import static com.echothree.model.jooq.server.tables.inventory.InventoryLocations.InventoryLocations; 034import static com.echothree.model.jooq.server.tables.item.ItemDetails.ItemDetails; 035import static com.echothree.model.jooq.server.tables.item.Items.Items; 036import static com.echothree.model.jooq.server.tables.party.Parties.Parties; 037import static com.echothree.model.jooq.server.tables.party.PartyDetails.PartyDetails; 038import static com.echothree.model.jooq.server.tables.party.PartyTypes.PartyTypes; 039import static com.echothree.model.jooq.server.tables.uom.UnitOfMeasureKindDetails.UnitOfMeasureKindDetails; 040import static com.echothree.model.jooq.server.tables.uom.UnitOfMeasureKinds.UnitOfMeasureKinds; 041import static com.echothree.model.jooq.server.tables.uom.UnitOfMeasureTypeDetails.UnitOfMeasureTypeDetails; 042import static com.echothree.model.jooq.server.tables.uom.UnitOfMeasureTypes.UnitOfMeasureTypes; 043import static com.echothree.model.jooq.server.tables.warehouse.LocationDetails.LocationDetails; 044import static com.echothree.model.jooq.server.tables.warehouse.Locations.Locations; 045import com.echothree.util.common.persistence.BasePK; 046import com.echothree.util.server.cdi.CommandScope; 047import com.echothree.util.server.control.BaseModelControl; 048import com.echothree.util.server.persistence.EntityPermission; 049import com.echothree.util.server.persistence.Session; 050import java.util.ArrayList; 051import java.util.Collection; 052import java.util.List; 053import javax.inject.Inject; 054import org.jooq.Condition; 055import org.jooq.OrderField; 056 057@CommandScope 058public class InventoryLocationControl 059 extends BaseModelControl { 060 061 @Inject 062 BucketControl bucketControl; 063 064 @Inject 065 InventoryLocationTransferCache inventoryLocationTransferCache; 066 067 /** 068 * Creates a new instance of InventoryLocationControl 069 */ 070 protected InventoryLocationControl() { 071 super(); 072 } 073 074 // -------------------------------------------------------------------------------- 075 // Inventory Locations 076 // -------------------------------------------------------------------------------- 077 078 @Inject 079 protected InventoryLocationFactory inventoryLocationFactory; 080 081 public InventoryLocation createInventoryLocation(Location location, Party ownerParty, Item item, 082 UnitOfMeasureType unitOfMeasureType, InventoryCondition inventoryCondition, BasePK createdBy) { 083 var inventoryLocation = inventoryLocationFactory.create(location, ownerParty, item, unitOfMeasureType, 084 inventoryCondition, session.getStartTime(), Session.MAX_TIME); 085 086 sendEvent(location.getPrimaryKey(), EventTypes.MODIFY, inventoryLocation.getPrimaryKey(), EventTypes.CREATE, createdBy); 087 088 return inventoryLocation; 089 } 090 091 private long countInventoryLocations(Condition condition) { 092 return session.getDslContext() 093 .selectCount() 094 .from(InventoryLocations) 095 .where(condition, InventoryLocations.THRU_TIME.eq(Session.MAX_TIME)) 096 .fetchOptional(0, Long.class) 097 .orElse(0L); 098 } 099 100 public long countInventoryLocationsByLocation(Location location) { 101 return countInventoryLocations(InventoryLocations.LOCATION.eq(location.getPrimaryKey())); 102 } 103 104 public long countInventoryLocationsByOwnerParty(Party ownerParty) { 105 return countInventoryLocations(InventoryLocations.OWNER_PARTY.eq(ownerParty.getPrimaryKey())); 106 } 107 108 public long countInventoryLocationsByItem(Item item) { 109 return countInventoryLocations(InventoryLocations.ITEM.eq(item.getPrimaryKey())); 110 } 111 112 public long countInventoryLocationsByUnitOfMeasureType(UnitOfMeasureType unitOfMeasureType) { 113 return countInventoryLocations(InventoryLocations.UNIT_OF_MEASURE_TYPE.eq(unitOfMeasureType.getPrimaryKey())); 114 } 115 116 public long countInventoryLocationsByInventoryCondition(InventoryCondition inventoryCondition) { 117 return countInventoryLocations(InventoryLocations.INVENTORY_CONDITION.eq(inventoryCondition.getPrimaryKey())); 118 } 119 120 private InventoryLocation getInventoryLocation(Location location, Item item, EntityPermission entityPermission) { 121 var baseQuery = session.getDslContext() 122 .select(InventoryLocations.fields()) 123 .from(InventoryLocations) 124 .where(InventoryLocations.LOCATION.eq(location.getPrimaryKey()), 125 InventoryLocations.ITEM.eq(item.getPrimaryKey()), 126 InventoryLocations.THRU_TIME.eq(Session.MAX_TIME)); 127 128 var query = switch(entityPermission) { 129 case READ_ONLY -> baseQuery; 130 case READ_WRITE -> baseQuery.forUpdate(); 131 }; 132 133 return inventoryLocationFactory.getEntityFromQuery(entityPermission, query); 134 } 135 136 public InventoryLocation getInventoryLocation(Location location, Item item) { 137 return getInventoryLocation(location, item, EntityPermission.READ_ONLY); 138 } 139 140 public InventoryLocation getInventoryLocationForUpdate(Location location, Item item) { 141 return getInventoryLocation(location, item, EntityPermission.READ_WRITE); 142 } 143 144 private InventoryLocation getInventoryLocation(Location location, Party ownerParty, Item item, 145 UnitOfMeasureType unitOfMeasureType, InventoryCondition inventoryCondition, 146 EntityPermission entityPermission) { 147 var baseQuery = session.getDslContext() 148 .select(InventoryLocations.fields()) 149 .from(InventoryLocations) 150 .where(InventoryLocations.LOCATION.eq(location.getPrimaryKey()), 151 InventoryLocations.OWNER_PARTY.eq(ownerParty.getPrimaryKey()), 152 InventoryLocations.ITEM.eq(item.getPrimaryKey()), 153 InventoryLocations.UNIT_OF_MEASURE_TYPE.eq(unitOfMeasureType.getPrimaryKey()), 154 InventoryLocations.INVENTORY_CONDITION.eq(inventoryCondition.getPrimaryKey()), 155 InventoryLocations.THRU_TIME.eq(Session.MAX_TIME)); 156 157 var query = switch(entityPermission) { 158 case READ_ONLY -> baseQuery; 159 case READ_WRITE -> baseQuery.forUpdate(); 160 }; 161 162 return inventoryLocationFactory.getEntityFromQuery(entityPermission, query); 163 } 164 165 public InventoryLocation getInventoryLocation(Location location, Party ownerParty, Item item, 166 UnitOfMeasureType unitOfMeasureType, InventoryCondition inventoryCondition) { 167 return getInventoryLocation(location, ownerParty, item, unitOfMeasureType, inventoryCondition, 168 EntityPermission.READ_ONLY); 169 } 170 171 public InventoryLocation getInventoryLocationForUpdate(Location location, Party ownerParty, Item item, 172 UnitOfMeasureType unitOfMeasureType, InventoryCondition inventoryCondition) { 173 return getInventoryLocation(location, ownerParty, item, unitOfMeasureType, inventoryCondition, 174 EntityPermission.READ_WRITE); 175 } 176 177 public InventoryLocationValue getInventoryLocationValue(InventoryLocation inventoryLocation) { 178 return inventoryLocation == null ? null : inventoryLocation.getInventoryLocationValue().clone(); 179 } 180 181 public InventoryLocationValue getInventoryLocationValueForUpdate(Location location, Item item) { 182 return getInventoryLocationValue(getInventoryLocationForUpdate(location, item)); 183 } 184 185 public InventoryLocationValue getInventoryLocationValueForUpdate(Location location, Party ownerParty, Item item, 186 UnitOfMeasureType unitOfMeasureType, InventoryCondition inventoryCondition) { 187 return getInventoryLocationValue(getInventoryLocationForUpdate(location, ownerParty, item, unitOfMeasureType, 188 inventoryCondition)); 189 } 190 191 private List<InventoryLocation> getInventoryLocations(Condition condition, EntityPermission entityPermission, 192 OrderField<?>... orderFields) { 193 var query = switch(entityPermission) { 194 case READ_ONLY -> session.applyLimit(session.getDslContext() 195 .select(InventoryLocations.fields()) 196 .from(InventoryLocations) 197 .join(Locations).on(InventoryLocations.LOCATION.eq(Locations.LOCATION)) 198 .join(LocationDetails).on(Locations.LAST_DETAIL.eq(LocationDetails.LOCATION_DETAIL)) 199 .join(Parties).on(InventoryLocations.OWNER_PARTY.eq(Parties.PARTY)) 200 .join(PartyDetails).on(Parties.LAST_DETAIL.eq(PartyDetails.PARTY_DETAIL)) 201 .join(PartyTypes).on(PartyDetails.PARTY_TYPE.eq(PartyTypes.PARTY_TYPE)) 202 .join(Items).on(InventoryLocations.ITEM.eq(Items.ITEM)) 203 .join(ItemDetails).on(Items.LAST_DETAIL.eq(ItemDetails.ITEM_DETAIL)) 204 .join(UnitOfMeasureTypes).on(InventoryLocations.UNIT_OF_MEASURE_TYPE.eq(UnitOfMeasureTypes.UNIT_OF_MEASURE_TYPE)) 205 .join(UnitOfMeasureTypeDetails).on(UnitOfMeasureTypes.LAST_DETAIL.eq(UnitOfMeasureTypeDetails.UNIT_OF_MEASURE_TYPE_DETAIL)) 206 .join(UnitOfMeasureKinds).on(UnitOfMeasureTypeDetails.UNIT_OF_MEASURE_KIND.eq(UnitOfMeasureKinds.UNIT_OF_MEASURE_KIND)) 207 .join(UnitOfMeasureKindDetails).on(UnitOfMeasureKinds.LAST_DETAIL.eq(UnitOfMeasureKindDetails.UNIT_OF_MEASURE_KIND_DETAIL)) 208 .join(InventoryConditions).on(InventoryLocations.INVENTORY_CONDITION.eq(InventoryConditions.INVENTORY_CONDITION)) 209 .join(InventoryConditionDetails).on(InventoryConditions.LAST_DETAIL.eq(InventoryConditionDetails.INVENTORY_CONDITION_DETAIL)) 210 .where(condition, InventoryLocations.THRU_TIME.eq(Session.MAX_TIME)) 211 .orderBy(orderFields), InventoryLocationFactory.class); 212 case READ_WRITE -> session.getDslContext() 213 .select(InventoryLocations.fields()) 214 .from(InventoryLocations) 215 .where(condition, InventoryLocations.THRU_TIME.eq(Session.MAX_TIME)) 216 .forUpdate(); 217 }; 218 219 return inventoryLocationFactory.getEntitiesFromQuery(entityPermission, query); 220 } 221 222 private List<InventoryLocation> getInventoryLocationsByLocation(Location location, EntityPermission entityPermission) { 223 return getInventoryLocations(InventoryLocations.LOCATION.eq(location.getPrimaryKey()), entityPermission, 224 PartyTypes.SORT_ORDER, PartyTypes.PARTY_TYPE_NAME, PartyDetails.PARTY_NAME, 225 ItemDetails.ITEM_NAME, UnitOfMeasureKindDetails.SORT_ORDER, 226 UnitOfMeasureKindDetails.UNIT_OF_MEASURE_KIND_NAME, UnitOfMeasureTypeDetails.SORT_ORDER, 227 UnitOfMeasureTypeDetails.UNIT_OF_MEASURE_TYPE_NAME, InventoryConditionDetails.SORT_ORDER, 228 InventoryConditionDetails.INVENTORY_CONDITION_NAME); 229 } 230 231 public List<InventoryLocation> getInventoryLocationsByLocation(Location location) { 232 return getInventoryLocationsByLocation(location, EntityPermission.READ_ONLY); 233 } 234 235 public List<InventoryLocation> getInventoryLocationsByLocationForUpdate(Location location) { 236 return getInventoryLocationsByLocation(location, EntityPermission.READ_WRITE); 237 } 238 239 private List<InventoryLocation> getInventoryLocationsByOwnerParty(Party ownerParty, EntityPermission entityPermission) { 240 return getInventoryLocations(InventoryLocations.OWNER_PARTY.eq(ownerParty.getPrimaryKey()), entityPermission, 241 LocationDetails.LOCATION_NAME, ItemDetails.ITEM_NAME, UnitOfMeasureKindDetails.SORT_ORDER, 242 UnitOfMeasureKindDetails.UNIT_OF_MEASURE_KIND_NAME, UnitOfMeasureTypeDetails.SORT_ORDER, 243 UnitOfMeasureTypeDetails.UNIT_OF_MEASURE_TYPE_NAME, InventoryConditionDetails.SORT_ORDER, 244 InventoryConditionDetails.INVENTORY_CONDITION_NAME); 245 } 246 247 public List<InventoryLocation> getInventoryLocationsByOwnerParty(Party ownerParty) { 248 return getInventoryLocationsByOwnerParty(ownerParty, EntityPermission.READ_ONLY); 249 } 250 251 public List<InventoryLocation> getInventoryLocationsByOwnerPartyForUpdate(Party ownerParty) { 252 return getInventoryLocationsByOwnerParty(ownerParty, EntityPermission.READ_WRITE); 253 } 254 255 private List<InventoryLocation> getInventoryLocationsByItem(Item item, EntityPermission entityPermission) { 256 return getInventoryLocations(InventoryLocations.ITEM.eq(item.getPrimaryKey()), entityPermission, 257 LocationDetails.LOCATION_NAME, PartyTypes.SORT_ORDER, PartyTypes.PARTY_TYPE_NAME, 258 PartyDetails.PARTY_NAME, UnitOfMeasureKindDetails.SORT_ORDER, 259 UnitOfMeasureKindDetails.UNIT_OF_MEASURE_KIND_NAME, UnitOfMeasureTypeDetails.SORT_ORDER, 260 UnitOfMeasureTypeDetails.UNIT_OF_MEASURE_TYPE_NAME, InventoryConditionDetails.SORT_ORDER, 261 InventoryConditionDetails.INVENTORY_CONDITION_NAME); 262 } 263 264 public List<InventoryLocation> getInventoryLocationsByItem(Item item) { 265 return getInventoryLocationsByItem(item, EntityPermission.READ_ONLY); 266 } 267 268 public List<InventoryLocation> getInventoryLocationsByItemForUpdate(Item item) { 269 return getInventoryLocationsByItem(item, EntityPermission.READ_WRITE); 270 } 271 272 private List<InventoryLocation> getInventoryLocationsByUnitOfMeasureType(UnitOfMeasureType unitOfMeasureType, 273 EntityPermission entityPermission) { 274 return getInventoryLocations(InventoryLocations.UNIT_OF_MEASURE_TYPE.eq(unitOfMeasureType.getPrimaryKey()), entityPermission, 275 LocationDetails.LOCATION_NAME, PartyTypes.SORT_ORDER, PartyTypes.PARTY_TYPE_NAME, 276 PartyDetails.PARTY_NAME, ItemDetails.ITEM_NAME, InventoryConditionDetails.SORT_ORDER, 277 InventoryConditionDetails.INVENTORY_CONDITION_NAME); 278 } 279 280 public List<InventoryLocation> getInventoryLocationsByUnitOfMeasureType(UnitOfMeasureType unitOfMeasureType) { 281 return getInventoryLocationsByUnitOfMeasureType(unitOfMeasureType, EntityPermission.READ_ONLY); 282 } 283 284 public List<InventoryLocation> getInventoryLocationsByUnitOfMeasureTypeForUpdate(UnitOfMeasureType unitOfMeasureType) { 285 return getInventoryLocationsByUnitOfMeasureType(unitOfMeasureType, EntityPermission.READ_WRITE); 286 } 287 288 private List<InventoryLocation> getInventoryLocationsByInventoryCondition(InventoryCondition inventoryCondition, 289 EntityPermission entityPermission) { 290 return getInventoryLocations(InventoryLocations.INVENTORY_CONDITION.eq(inventoryCondition.getPrimaryKey()), entityPermission, 291 LocationDetails.LOCATION_NAME, PartyTypes.SORT_ORDER, PartyTypes.PARTY_TYPE_NAME, 292 PartyDetails.PARTY_NAME, ItemDetails.ITEM_NAME, UnitOfMeasureKindDetails.SORT_ORDER, 293 UnitOfMeasureKindDetails.UNIT_OF_MEASURE_KIND_NAME, UnitOfMeasureTypeDetails.SORT_ORDER, 294 UnitOfMeasureTypeDetails.UNIT_OF_MEASURE_TYPE_NAME); 295 } 296 297 public List<InventoryLocation> getInventoryLocationsByInventoryCondition(InventoryCondition inventoryCondition) { 298 return getInventoryLocationsByInventoryCondition(inventoryCondition, EntityPermission.READ_ONLY); 299 } 300 301 public List<InventoryLocation> getInventoryLocationsByInventoryConditionForUpdate(InventoryCondition inventoryCondition) { 302 return getInventoryLocationsByInventoryCondition(inventoryCondition, EntityPermission.READ_WRITE); 303 } 304 305 public InventoryLocationTransfer getInventoryLocationTransfer(UserVisit userVisit, InventoryLocation inventoryLocation) { 306 return inventoryLocation == null ? null : inventoryLocationTransferCache.getTransfer(userVisit, inventoryLocation); 307 } 308 309 public InventoryLocationTransfer getInventoryLocationTransfer(UserVisit userVisit, Location location, Item item) { 310 return getInventoryLocationTransfer(userVisit, getInventoryLocation(location, item)); 311 } 312 313 public InventoryLocationTransfer getInventoryLocationTransfer(UserVisit userVisit, Location location, Party ownerParty, 314 Item item, UnitOfMeasureType unitOfMeasureType, InventoryCondition inventoryCondition) { 315 return getInventoryLocationTransfer(userVisit, getInventoryLocation(location, ownerParty, item, unitOfMeasureType, 316 inventoryCondition)); 317 } 318 319 public List<InventoryLocationTransfer> getInventoryLocationTransfers(UserVisit userVisit, 320 Collection<InventoryLocation> inventoryLocations) { 321 var transfers = new ArrayList<InventoryLocationTransfer>(inventoryLocations.size()); 322 323 inventoryLocations.forEach(inventoryLocation -> 324 transfers.add(inventoryLocationTransferCache.getTransfer(userVisit, inventoryLocation)) 325 ); 326 327 return transfers; 328 } 329 330 public List<InventoryLocationTransfer> getInventoryLocationTransfersByLocation(UserVisit userVisit, Location location) { 331 return getInventoryLocationTransfers(userVisit, getInventoryLocationsByLocation(location)); 332 } 333 334 public List<InventoryLocationTransfer> getInventoryLocationTransfersByOwnerParty(UserVisit userVisit, Party ownerParty) { 335 return getInventoryLocationTransfers(userVisit, getInventoryLocationsByOwnerParty(ownerParty)); 336 } 337 338 public List<InventoryLocationTransfer> getInventoryLocationTransfersByItem(UserVisit userVisit, Item item) { 339 return getInventoryLocationTransfers(userVisit, getInventoryLocationsByItem(item)); 340 } 341 342 public List<InventoryLocationTransfer> getInventoryLocationTransfersByUnitOfMeasureType(UserVisit userVisit, 343 UnitOfMeasureType unitOfMeasureType) { 344 return getInventoryLocationTransfers(userVisit, getInventoryLocationsByUnitOfMeasureType(unitOfMeasureType)); 345 } 346 347 public List<InventoryLocationTransfer> getInventoryLocationTransfersByInventoryCondition(UserVisit userVisit, 348 InventoryCondition inventoryCondition) { 349 return getInventoryLocationTransfers(userVisit, getInventoryLocationsByInventoryCondition(inventoryCondition)); 350 } 351 352 public void deleteInventoryLocation(InventoryLocation inventoryLocation, BasePK deletedBy) { 353 bucketControl.removeInventoryLocationBucketsByInventoryLocation(inventoryLocation, deletedBy); 354 355 inventoryLocation.setThruTime(session.getStartTime()); 356 357 sendEvent(inventoryLocation.getLocationPK(), EventTypes.MODIFY, inventoryLocation.getPrimaryKey(), EventTypes.DELETE, deletedBy); 358 } 359 360 public void deleteInventoryLocations(List<InventoryLocation> inventoryLocations, BasePK deletedBy) { 361 inventoryLocations.forEach(inventoryLocation -> deleteInventoryLocation(inventoryLocation, deletedBy)); 362 } 363 364 public void deleteInventoryLocationsByLocation(Location location, BasePK deletedBy) { 365 deleteInventoryLocations(getInventoryLocationsByLocationForUpdate(location), deletedBy); 366 } 367 368 public void deleteInventoryLocationsByOwnerParty(Party ownerParty, BasePK deletedBy) { 369 deleteInventoryLocations(getInventoryLocationsByOwnerPartyForUpdate(ownerParty), deletedBy); 370 } 371 372 public void deleteInventoryLocationsByItem(Item item, BasePK deletedBy) { 373 deleteInventoryLocations(getInventoryLocationsByItemForUpdate(item), deletedBy); 374 } 375 376 public void deleteInventoryLocationsByUnitOfMeasureType(UnitOfMeasureType unitOfMeasureType, BasePK deletedBy) { 377 deleteInventoryLocations(getInventoryLocationsByUnitOfMeasureTypeForUpdate(unitOfMeasureType), deletedBy); 378 } 379 380 public void deleteInventoryLocationsByInventoryCondition(InventoryCondition inventoryCondition, BasePK deletedBy) { 381 deleteInventoryLocations(getInventoryLocationsByInventoryConditionForUpdate(inventoryCondition), deletedBy); 382 } 383 384}