package crmdna.inventory; import com.google.appengine.api.datastore.*; import com.google.appengine.api.datastore.Query.FilterOperator; import com.google.gson.Gson; import com.googlecode.objectify.Key; import com.googlecode.objectify.cmd.Query; import crmdna.client.Client; import crmdna.common.DSUtils; import crmdna.common.UnitUtils; import crmdna.common.UnitUtils.PhysicalQuantity; import crmdna.common.UnitUtils.ReportingUnit; import crmdna.common.Utils.Currency; import crmdna.group.Group; import crmdna.inventory.InventoryItemCore.CheckInOrOut; import crmdna.user.User; import crmdna.user.User.GroupLevelPrivilege; import java.util.*; import static crmdna.common.AssertUtils.*; import static crmdna.common.OfyService.ofy; import static crmdna.common.ProjectionQuery.pq; public class InventoryItem { public static InventoryItemProp create(String client, long groupId, long inventoryItemTypeId, String displayName, PhysicalQuantity physicalQuantity, ReportingUnit reportingUnit, String login) { Client.ensureValid(client); Group.safeGet(client, groupId); InventoryItemType.safeGet(client, inventoryItemTypeId); User.ensureGroupLevelPrivilege(client, groupId, login, GroupLevelPrivilege.UPDATE_INVENTORY_ITEM); return InventoryItemCore.create(client, groupId, inventoryItemTypeId, displayName, physicalQuantity, reportingUnit); } public static InventoryItemProp update(String client, long inventoryItemId, Long newInventoryItemTypeId, String newDisplayName, ReportingUnit newReportingUnit, String login) { Client.ensureValid(client); InventoryItemEntity entity = safeGet(client, inventoryItemId); User.ensureGroupLevelPrivilege(client, entity.groupId, login, GroupLevelPrivilege.UPDATE_INVENTORY_ITEM); Group.safeGet(client, entity.groupId); return InventoryItemCore.update(client, inventoryItemId, newInventoryItemTypeId, newDisplayName, newReportingUnit); } public static InventoryItemEntity safeGet(String client, long inventoryItemId) { Client.ensureValid(client); return InventoryItemCore.safeGet(client, inventoryItemId); } public static Map<Long, InventoryItemEntity> get(String client, Set<Long> inventoryItemIds) { Client.ensureValid(client); return InventoryItemCore.get(client, inventoryItemIds); } public static InventoryItemEntity safeGetByName(String client, String name) { Client.ensureValid(client); return InventoryItemCore.safeGetByName(client, name); } public static List<InventoryItemProp> query_to_be_removed(String client, InventoryItemQueryCondition qc, String login) { List<Key<InventoryItemEntity>> keys = InventoryItemCore.queryKeys(client, qc).list(); List<Long> ids = new ArrayList<>(); List<InventoryItemProp> props = new ArrayList<>(keys.size()); for (Key<InventoryItemEntity> key : keys) { long id = key.getId(); ids.add(id); InventoryItemProp prop = new InventoryItemProp(); prop.inventoryItemId = id; props.add(prop); } List<com.google.appengine.api.datastore.Key> rawKeys = new ArrayList<>(); for (Key<?> key : keys) { rawKeys.add(key.getRaw()); } System.out.println("ids = " + new Gson().toJson(ids)); ensureEqual(keys.size(), props.size()); AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService(); // com.google.appengine.api.datastore.Query.Filter filter = new // com.google.appengine.api.datastore.Query.FilterPredicate( // "inventoryItemId", FilterOperator.IN, ids); com.google.appengine.api.datastore.Query.Filter filter = new com.google.appengine.api.datastore.Query.FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.IN, rawKeys); com.google.appengine.api.datastore.Query q = new com.google.appengine.api.datastore.Query(InventoryItemEntity.class.getSimpleName()); q.setFilter(filter).addProjection(new PropertyProjection("groupId", Long.class)); Iterable<Entity> groupIds = datastore.prepare(q).asList(FetchOptions.Builder.withLimit(10000)); q = new com.google.appengine.api.datastore.Query("InventoryItemEntity"); q.setFilter(filter).addProjection(new PropertyProjection("inventoryItemTypeId", Long.class)); Iterable<Entity> inventoryItemTypeIds = datastore.prepare(q).asIterable(); int i = 0; for (Entity entity : groupIds) { InventoryItemProp prop = props.get(i); prop.groupId = (long) entity.getProperty("groupId"); i++; } System.out.println("i = " + i + ", key.size() = " + keys.size()); i = 0; for (Entity entity : inventoryItemTypeIds) { InventoryItemProp prop = props.get(i); prop.inventoryItemTypeId = (long) entity.getProperty("inventoryItemTypeId"); i++; } ensureEqual(keys.size(), i); System.out.println("before populateDependents. props: " + new Gson().toJson(props)); InventoryItemProp.populateDependents(client, props); Collections.sort(props); return props; } public static List<InventoryItemProp> query(String client, InventoryItemQueryCondition qc, String login) { Client.ensureValid(client); User.ensureValidUser(client, login); return InventoryItemCore.query(client, qc); } public static Map<Long, InventoryItemEntity> queryEntities(String client, InventoryItemQueryCondition qc, String login) { Client.ensureValid(client); User.ensureValidUser(client, login); return InventoryItemCore.queryEntities(client, qc); } public static List<InventoryItemProp> query2_to_be_removed(String client, InventoryItemQueryCondition qc, String login) { List<Key<InventoryItemEntity>> keys = InventoryItemCore.queryKeys(client, qc).list(); List<Long> ids = new ArrayList<>(); List<InventoryItemProp> props = new ArrayList<>(keys.size()); for (Key<InventoryItemEntity> key : keys) { long id = key.getId(); ids.add(id); InventoryItemProp prop = new InventoryItemProp(); prop.inventoryItemId = id; props.add(prop); } String kind = "InventoryItemEntity"; // List<Long> groupIds = DSUtils.executeProjectionQuery(kind, keys, // "groupId", Long.class); List<Long> groupIds = DSUtils.executeProjectionQuery2(InventoryItemEntity.class, keys, "groupId", Long.class); List<Long> inventoryItemTypeIds = DSUtils.executeProjectionQuery(kind, keys, "inventoryItemTypeId", Long.class); ensureEqual(keys.size(), groupIds.size(), "Records returned by projection query [" + groupIds.size() + "] is different from number of keys [" + keys.size() + "]"); ensureEqual(keys.size(), inventoryItemTypeIds.size(), "Projection query result mismatch"); for (int i = 0; i < keys.size(); i++) { InventoryItemProp prop = props.get(i); prop.groupId = groupIds.get(i); prop.inventoryItemTypeId = inventoryItemTypeIds.get(i); } InventoryItemProp.populateDependents(client, props); Collections.sort(props); return props; } public static List<InventoryItemProp> query3_to_be_removed(String client, InventoryItemQueryCondition qc, String login) { List<Key<InventoryItemEntity>> keys = InventoryItemCore.queryKeys(client, qc).list(); List<Long> ids = new ArrayList<>(); List<InventoryItemProp> props = new ArrayList<>(keys.size()); for (Key<InventoryItemEntity> key : keys) { long id = key.getId(); ids.add(id); InventoryItemProp prop = new InventoryItemProp(); prop.inventoryItemId = id; props.add(prop); } List<Long> groupIds = pq(InventoryItemEntity.class, Long.class).keys(keys).property("groupId").execute(); List<Long> inventoryItemTypeIds = pq(InventoryItemEntity.class, Long.class).keys(keys).property("inventoryItemTypeId") .execute(); ensureEqual(keys.size(), groupIds.size(), "Records returned by projection query [" + groupIds.size() + "] is different from number of keys [" + keys.size() + "]"); ensureEqual(keys.size(), inventoryItemTypeIds.size(), "Projection query result mismatch"); for (int i = 0; i < keys.size(); i++) { InventoryItemProp prop = props.get(i); prop.groupId = groupIds.get(i); prop.inventoryItemTypeId = inventoryItemTypeIds.get(i); } InventoryItemProp.populateDependents(client, props); Collections.sort(props); return props; } public static InventoryCheckInProp checkIn(String client, long inventoryItemId, Date date, double qtyInReportingUnit, ReportingUnit reportingUnit, double pricePerReportingUnit, Currency ccy, String changeDescription, final String login) { Client.ensureValid(client); InventoryItemEntity inventoryItemEntity = InventoryItem.safeGet(client, inventoryItemId); User.ensureGroupLevelPrivilege(client, inventoryItemEntity.groupId, login, GroupLevelPrivilege.UPDATE_INVENTORY_QUANTITY); return InventoryItemCore.checkIn(client, inventoryItemId, date, qtyInReportingUnit, reportingUnit, pricePerReportingUnit, ccy, changeDescription, login); } public static InventoryCheckOutProp checkOut(String client, final long inventoryItemId, Date date, double qtyInReportingUnit, final ReportingUnit reportingUnit, Double pricePerReportingUnit, Currency ccy, String comment, Set<String> tags, final String login) { Client.ensureValid(client); InventoryItemEntity inventoryItemEntity = InventoryItem.safeGet(client, inventoryItemId); User.ensureGroupLevelPrivilege(client, inventoryItemEntity.groupId, login, GroupLevelPrivilege.UPDATE_INVENTORY_QUANTITY); return InventoryItemCore.checkOut(client, inventoryItemId, date, qtyInReportingUnit, reportingUnit, pricePerReportingUnit, ccy, comment, tags, login); } public static void delete(String client, long inventoryItemId, String login) { Client.ensureValid(client); InventoryItemEntity entity = safeGet(client, inventoryItemId); User.ensureGroupLevelPrivilege(client, entity.groupId, login, GroupLevelPrivilege.UPDATE_INVENTORY_ITEM); InventoryItemCore.delete(client, inventoryItemId); } public static List<StockChangeProp> queryStockChanges(String client, StockChangeQueryCondition qc, String login) { Client.ensureValid(client); User.ensureValidUser(client, login); ensureNotNull(qc); Group.safeGet(client, qc.groupId); ensure(qc.startMS > 0); ensure(qc.endMS > 0); ensure(qc.endMS >= qc.startMS, "endMS should be greater or equal to startMS"); ensure(qc.groupId != 0, "qc.groupId is 0"); Group.safeGet(client, qc.groupId); ensure(qc.includeCheckIn || qc.includeCheckOut, "Either checkIn or checkOut should be specified"); if (qc.inventoryItemIds == null) qc.inventoryItemIds = new HashSet<>(); if (qc.inventoryItemTypeIds == null) qc.inventoryItemTypeIds = new HashSet<>(); ensure(qc.inventoryItemIds.isEmpty() || qc.inventoryItemTypeIds.isEmpty(), "Both inventoryItemIds and inventoryItemTypeIds cannot be specified"); InventoryItemQueryCondition iiqc = new InventoryItemQueryCondition(); iiqc.groupId = qc.groupId; iiqc.inventoryItemTypeIds = qc.inventoryItemTypeIds; Map<Long, InventoryItemEntity> allInventoryItemEntitiesForGroup = InventoryItem.queryEntities(client, iiqc, login); if ((qc.inventoryItemIds != null) && !qc.inventoryItemIds.isEmpty()) { ensure(allInventoryItemEntitiesForGroup.keySet().containsAll(qc.inventoryItemIds), "All specified inventoryItemIds should belong to group [" + qc.groupId + "]"); } else { qc.inventoryItemIds = allInventoryItemEntitiesForGroup.keySet(); } // return if no inventory item is available for the group if (qc.inventoryItemIds.isEmpty()) return new ArrayList<>(); List<InventoryCheckInEntity> checkIns = new ArrayList<>(); if (qc.includeCheckIn) { Query<InventoryCheckInEntity> q = ofy(client).load().type(InventoryCheckInEntity.class); q = q.filter("ms >=", qc.startMS).filter("ms <=", qc.endMS) .filter("inventoryItemId in", qc.inventoryItemIds); if ((qc.logins != null) && !qc.logins.isEmpty()) q = q.filter("login in", qc.logins); q = q.order("-ms"); checkIns = q.list(); } List<InventoryCheckOutEntity> checkOuts = new ArrayList<>(); if (qc.includeCheckOut) { Query<InventoryCheckOutEntity> q = ofy(client).load().type(InventoryCheckOutEntity.class); q = q.filter("ms >=", qc.startMS).filter("ms <=", qc.endMS) .filter("inventoryItemId in", qc.inventoryItemIds); if ((qc.logins != null) && !qc.logins.isEmpty()) q = q.filter("login in", qc.logins); if ((qc.tags != null) && !qc.tags.isEmpty()) q = q.filter("tags", qc.tags); q = q.order("-ms"); checkOuts = q.list(); } List<StockChangeProp> stockChangeProps = new ArrayList<>(); for (InventoryCheckInEntity checkInEntity : checkIns) { ensure(qc.inventoryItemIds.contains(checkInEntity.inventoryItemId), "Inventory id [" + checkInEntity.inventoryItemId + "] missing"); InventoryItemEntity inventoryItemEntity = allInventoryItemEntitiesForGroup.get(checkInEntity.inventoryItemId); StockChangeProp prop = new StockChangeProp(); prop.timestamp = new Date(checkInEntity.ms); prop.login = checkInEntity.login; prop.inventoryItemId = checkInEntity.inventoryItemId; prop.inventoryItem = inventoryItemEntity.displayName; prop.changeInReportingUnit = UnitUtils.safeGetQtyInReportingUnit(inventoryItemEntity.physicalQuantity, checkInEntity.qtyInDefaultUnit, inventoryItemEntity.reportingUnit); prop.reportingUnit = inventoryItemEntity.reportingUnit; prop.cost = checkInEntity.pricePerDefaultUnit * checkInEntity.qtyInDefaultUnit; prop.ccy = checkInEntity.ccy; prop.checkInOrOut = CheckInOrOut.CHECK_IN; prop.checkInOrOutId = checkInEntity.checkInId; prop.comment = checkInEntity.comment; stockChangeProps.add(prop); } for (InventoryCheckOutEntity checkOutEntity : checkOuts) { ensure(allInventoryItemEntitiesForGroup.containsKey(checkOutEntity.inventoryItemId), "Inventory id [" + checkOutEntity.inventoryItemId + "] missing in map"); InventoryItemEntity inventoryItemEntity = allInventoryItemEntitiesForGroup.get(checkOutEntity.inventoryItemId); StockChangeProp prop = new StockChangeProp(); prop.timestamp = new Date(checkOutEntity.ms); prop.login = checkOutEntity.login; prop.inventoryItemId = checkOutEntity.inventoryItemId; prop.inventoryItem = inventoryItemEntity.displayName; prop.changeInReportingUnit = UnitUtils.safeGetQtyInReportingUnit(inventoryItemEntity.physicalQuantity, checkOutEntity.qtyInDefaultUnit, inventoryItemEntity.reportingUnit); prop.reportingUnit = inventoryItemEntity.reportingUnit; prop.cost = checkOutEntity.avgPricePerDefaultUnit * checkOutEntity.qtyInDefaultUnit; prop.ccy = checkOutEntity.ccy; prop.checkInOrOut = CheckInOrOut.CHECK_OUT; prop.checkInOrOutId = checkOutEntity.checkOutId; prop.comment = checkOutEntity.comment; prop.tags = checkOutEntity.tags; stockChangeProps.add(prop); } Collections.sort(stockChangeProps); return stockChangeProps; } }