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}