package crmdna.inventory; import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; import com.google.appengine.tools.development.testing.LocalServiceTestHelper; import com.googlecode.objectify.ObjectifyFilter; import crmdna.client.Client; import crmdna.common.ICode; import crmdna.common.UnitUtils.PhysicalQuantity; import crmdna.common.UnitUtils.ReportingUnit; import crmdna.common.Utils; import crmdna.common.Utils.Currency; import crmdna.common.api.APIResponse.Status; import crmdna.group.Group; import crmdna.group.Group.GroupProp; import crmdna.hr.Department; import crmdna.hr.DepartmentProp; import crmdna.inventory.InventoryItemCore.CheckInOrOut; import crmdna.user.User; import crmdna.user.User.GroupLevelPrivilege; import crmdna.user.UserCore; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.Date; import java.util.List; import static crmdna.common.TestUtil.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class InventoryItemTest { private final LocalServiceTestHelper datastoreHelper = new LocalServiceTestHelper( new LocalDatastoreServiceTestConfig().setApplyAllHighRepJobPolicy()); private final String client = "isha"; private final String validUser = "valid@login.com"; private final String userWithPermission = "withpermission@login.com"; InventoryItemTypeProp book; InventoryItemTypeProp rudraksh; DepartmentProp nandiFoods; private GroupProp sgp; private GroupProp kl; @Before public void setUp() { datastoreHelper.setUp(); ObjectifyFilter.complete(); Client.create(client); sgp = Group.create(client, "Singapore", User.SUPER_USER); assertEquals(1, sgp.groupId); kl = Group.create(client, "KL", User.SUPER_USER); assertEquals(2, kl.groupId); book = InventoryItemType.create(client, "Book", User.SUPER_USER); assertEquals(1, book.inventoryItemTypeId); rudraksh = InventoryItemType .create(client, "Rudraksh", User.SUPER_USER); assertEquals(2, rudraksh.inventoryItemTypeId); User.create(client, validUser, sgp.groupId, User.SUPER_USER); assertEquals(1, UserCore.safeGet(client, validUser).toProp(client).userId); User.create(client, userWithPermission, sgp.groupId, User.SUPER_USER); assertEquals( 2, UserCore.safeGet(client, userWithPermission).toProp(client).userId); User.addGroupLevelPrivilege(client, sgp.groupId, userWithPermission, GroupLevelPrivilege.UPDATE_INVENTORY_ITEM, User.SUPER_USER); User.addGroupLevelPrivilege(client, sgp.groupId, userWithPermission, GroupLevelPrivilege.UPDATE_INVENTORY_QUANTITY, User.SUPER_USER); nandiFoods = Department.create(client, "Nandi Foods", User.SUPER_USER); } @After public void tearDown() { ObjectifyFilter.complete(); datastoreHelper.tearDown(); } @Test public void createTest() { InventoryItemProp forestFlower = InventoryItem.create(client, sgp.groupId, book.inventoryItemTypeId, "Forest Flower", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); assertEquals(1, forestFlower.inventoryItemId); ObjectifyFilter.complete(); forestFlower = InventoryItem.safeGet(client, forestFlower.inventoryItemId).toProp(); assertEquals(1, forestFlower.inventoryItemId); assertEquals(sgp.groupId, forestFlower.groupId); assertEquals(book.inventoryItemTypeId, forestFlower.inventoryItemTypeId); assertEquals("Forest Flower", forestFlower.displayName); // name should be populated correctly in the entity assertEquals( "forestflower", InventoryItem.safeGet(client, forestFlower.inventoryItemId).name); assertEquals(PhysicalQuantity.NUMBER, forestFlower.physicalQuantity); assertEquals(ReportingUnit.NUMBER, forestFlower.reportingUnit); // client cannot be invalid ensureResourceNotFoundException(new ICode() { @Override public void run() { InventoryItem.create("invalid", sgp.groupId, book.inventoryItemTypeId, "joy 24/7", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); } }); // inventory item type cannot be invalid ensureResourceNotFoundException(new ICode() { @Override public void run() { InventoryItem.create(client, sgp.groupId, 100, "Himalayas", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); } }); // same name cannot repeat within a group ensureAPIException(Status.ERROR_RESOURCE_ALREADY_EXISTS, new ICode() { @Override public void run() { InventoryItem.create(client, sgp.groupId, book.inventoryItemTypeId, "Forest Flower", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); } }); // but same name can repeat within another group forestFlower = InventoryItem.create(client, kl.groupId, book.inventoryItemTypeId, "Forest Flower", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); assertEquals(kl.groupId, forestFlower.groupId); // need permission ensureAPIException(Status.ERROR_INSUFFICIENT_PERMISSION, new ICode() { @Override public void run() { InventoryItem.create(client, kl.groupId, rudraksh.inventoryItemTypeId, "Gowri Shankar", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, validUser); } }); // user with permission for sgp cannot create for kl ensureAPIException(Status.ERROR_INSUFFICIENT_PERMISSION, new ICode() { @Override public void run() { InventoryItem.create(client, kl.groupId, rudraksh.inventoryItemTypeId, "Gowri Shankar", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, userWithPermission); } }); // user with permssion can create in singapore InventoryItem.create(client, sgp.groupId, rudraksh.inventoryItemTypeId, "Gowri Shankar", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, userWithPermission); // no exception } @Test public void updateTest() { InventoryItemProp forestFlower = InventoryItem.create(client, sgp.groupId, rudraksh.inventoryItemTypeId, "Flower Forest", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); // create another book - mystic eye InventoryItem.create(client, sgp.groupId, rudraksh.inventoryItemTypeId, "Mystic Eye", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); assertEquals(1, forestFlower.inventoryItemId); // change name to Flower-Forest forestFlower = InventoryItem.update(client, forestFlower.inventoryItemId, book.inventoryItemTypeId, "Flower-Forest", null, userWithPermission); assertEquals(forestFlower.inventoryItemTypeId, book.inventoryItemTypeId); assertEquals("Flower-Forest", forestFlower.displayName); // change name to forest-flower forestFlower = InventoryItem.update(client, forestFlower.inventoryItemId, book.inventoryItemTypeId, "Forest-Flower", null, userWithPermission); assertEquals(forestFlower.inventoryItemTypeId, book.inventoryItemTypeId); assertEquals("Forest-Flower", forestFlower.displayName); assertEquals("forestflower", forestFlower.name); // new reporting unit should be valid final long inventoryItemId = forestFlower.inventoryItemId; ensureResourceIncorrectException(new ICode() { @Override public void run() { InventoryItem.update(client, inventoryItemId, book.inventoryItemTypeId, null, ReportingUnit.ML, userWithPermission); } }); // need permission for updating ensureAPIException(Status.ERROR_INSUFFICIENT_PERMISSION, new ICode() { @Override public void run() { InventoryItem.update(client, inventoryItemId, book.inventoryItemTypeId, null, ReportingUnit.ML, validUser); } }); // first char of display name should be alphanumeric ensureResourceIncorrectException(new ICode() { @Override public void run() { InventoryItem.update(client, inventoryItemId, book.inventoryItemTypeId, "$Forest Flower", ReportingUnit.ML, userWithPermission); } }); // cannot change name to already existing mystic eye ensureAPIException(Status.ERROR_RESOURCE_ALREADY_EXISTS, new ICode() { @Override public void run() { InventoryItem.update(client, inventoryItemId, book.inventoryItemTypeId, "mystic-Eye", ReportingUnit.ML, userWithPermission); } }); } @Test public void queryTest() { InventoryItemProp forestFlower = InventoryItem.create(client, sgp.groupId, book.inventoryItemTypeId, "Forest Flower", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); assertEquals(1, forestFlower.inventoryItemId); InventoryItemProp mysticEye = InventoryItem.create(client, sgp.groupId, book.inventoryItemTypeId, "Mystic Eye", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); assertEquals(2, mysticEye.inventoryItemId); InventoryItemProp midnights = InventoryItem.create(client, kl.groupId, book.inventoryItemTypeId, "Midnights with Mystic", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); assertEquals(3, midnights.inventoryItemId); InventoryItemProp panchamuki = InventoryItem.create(client, sgp.groupId, rudraksh.inventoryItemTypeId, "Panchamuki", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); assertEquals(4, panchamuki.inventoryItemId); InventoryItemQueryCondition qc = new InventoryItemQueryCondition(); qc.groupId = sgp.groupId; List<InventoryItemProp> props = InventoryItem.query(client, qc, User.SUPER_USER); assertEquals(3, props.size()); // should be sorted by inventory type first and then by item name assertEquals(forestFlower.inventoryItemId, props.get(0).inventoryItemId); assertEquals(mysticEye.inventoryItemId, props.get(1).inventoryItemId); assertEquals(panchamuki.inventoryItemId, props.get(2).inventoryItemId); // query for all books qc = new InventoryItemQueryCondition(); qc.inventoryItemTypeIds = Utils.getSet(book.inventoryItemTypeId); props = InventoryItem.query(client, qc, validUser); assertEquals(3, props.size()); // should be sorted by name assertEquals(forestFlower.inventoryItemId, props.get(0).inventoryItemId); assertEquals(midnights.inventoryItemId, props.get(1).inventoryItemId); assertEquals(mysticEye.inventoryItemId, props.get(2).inventoryItemId); // query for books and rudraksh qc = new InventoryItemQueryCondition(); qc.inventoryItemTypeIds.add(book.inventoryItemTypeId); qc.inventoryItemTypeIds.add(rudraksh.inventoryItemTypeId); props = InventoryItem.query(client, qc, validUser); assertEquals(4, props.size()); // should be sorted by inventory type first and then by item name assertEquals(forestFlower.inventoryItemId, props.get(0).inventoryItemId); assertEquals(midnights.inventoryItemId, props.get(1).inventoryItemId); assertEquals(mysticEye.inventoryItemId, props.get(2).inventoryItemId); assertEquals(panchamuki.inventoryItemId, props.get(3).inventoryItemId); // query by first char qc = new InventoryItemQueryCondition(); qc.firstChars = Utils.getList("m"); qc.groupId = sgp.groupId; props = InventoryItem.query(client, qc, validUser); assertEquals(1, props.size()); // query for everything in KL qc = new InventoryItemQueryCondition(); qc.groupId = kl.groupId; props = InventoryItem.query(client, qc, validUser); assertEquals(1, props.size()); assertEquals(midnights.inventoryItemId, props.get(0).inventoryItemId); // check in 5 midnights book at 200 MYR per book InventoryItem.checkIn(client, midnights.inventoryItemId, null, 5, ReportingUnit.NUMBER, 200, Currency.MYR, "5 @ 200 MYR / Book", User.SUPER_USER); props = InventoryItem.query(client, qc, validUser); assertEquals(1, props.size()); assertEquals(midnights.inventoryItemId, props.get(0).inventoryItemId); assertTrue(5.0 == props.get(0).availableQtyInReportingUnit); assertTrue(200 == props.get(0).avgPricePerReportingUnit); assertEquals(Currency.MYR, props.get(0).ccy); // check in 5 more at 300 MYR per book InventoryItem.checkIn(client, midnights.inventoryItemId, null, 5, ReportingUnit.NUMBER, 300, Currency.MYR, "5 @ 300 MYR / Book", User.SUPER_USER); props = InventoryItem.query(client, qc, validUser); assertEquals(1, props.size()); assertEquals(midnights.inventoryItemId, props.get(0).inventoryItemId); assertTrue(10 == props.get(0).availableQtyInReportingUnit); assertTrue(250 == props.get(0).avgPricePerReportingUnit); assertEquals(Currency.MYR, props.get(0).ccy); // check out 3 books InventoryItem.checkOut(client, midnights.inventoryItemId, null, 3, ReportingUnit.NUMBER, null, null, "check out 3 books", null, User.SUPER_USER); props = InventoryItem.query(client, qc, validUser); assertEquals(1, props.size()); assertEquals(midnights.inventoryItemId, props.get(0).inventoryItemId); assertTrue(7 == props.get(0).availableQtyInReportingUnit); assertTrue(Math.abs(271.42 - props.get(0).avgPricePerReportingUnit) < 0.1); // (2 * 200 + 5 * 300)/7 = 271.42 assertEquals(Currency.MYR, props.get(0).ccy); // check out 3 more books InventoryItem.checkOut(client, midnights.inventoryItemId, null, 3, ReportingUnit.NUMBER, null, null, "check out 3 books", null, User.SUPER_USER); props = InventoryItem.query(client, qc, validUser); assertEquals(1, props.size()); assertEquals(midnights.inventoryItemId, props.get(0).inventoryItemId); assertTrue(4 == props.get(0).availableQtyInReportingUnit); assertTrue(300 == props.get(0).avgPricePerReportingUnit); assertEquals(Currency.MYR, props.get(0).ccy); } @Test public void queryStockChangesTest() { final InventoryItemProp forestFlower = InventoryItem.create(client, sgp.groupId, book.inventoryItemTypeId, "Forest Flower", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); assertEquals(1, forestFlower.inventoryItemId); InventoryItem.checkIn(client, forestFlower.inventoryItemId, null, 200, ReportingUnit.NUMBER, 1, Currency.SGD, "200 @ 1 SGD / Book", userWithPermission); StockChangeQueryCondition qc = new StockChangeQueryCondition( sgp.groupId, new Date().getTime() - 200, new Date().getTime() + 100); qc.includeCheckIn = true; List<StockChangeProp> props = InventoryItem.queryStockChanges(client, qc, userWithPermission); assertEquals(1, props.size()); assertEquals(Currency.SGD, props.get(0).ccy); assertTrue(200.0 == props.get(0).changeInReportingUnit); assertEquals(CheckInOrOut.CHECK_IN, props.get(0).checkInOrOut); assertEquals(userWithPermission, props.get(0).login); assertEquals(forestFlower.inventoryItemId, props.get(0).inventoryItemId); assertEquals("Forest Flower", props.get(0).inventoryItem); assertEquals(1, props.get(0).checkInOrOutId); // query based on login qc.logins.add(validUser); props = InventoryItem.queryStockChanges(client, qc, userWithPermission); assertEquals(0, props.size()); qc.logins.add(userWithPermission); props = InventoryItem.queryStockChanges(client, qc, userWithPermission); assertEquals(1, props.size()); // include only checkouts qc.includeCheckIn = false; qc.includeCheckOut = true; props = InventoryItem.queryStockChanges(client, qc, userWithPermission); assertEquals(0, props.size()); InventoryItem.checkOut(client, forestFlower.inventoryItemId, null, 100, ReportingUnit.NUMBER, 1.2, Currency.SGD, null, Utils.getSet("tag1"), userWithPermission); props = InventoryItem.queryStockChanges(client, qc, userWithPermission); assertEquals(1, props.size()); assertEquals(1, props.get(0).tags.size()); assertTrue(props.get(0).tags.contains("tag1")); } @Test public void tagsSavedDuringCheckout() { final InventoryItemProp forestFlower = InventoryItem.create(client, sgp.groupId, book.inventoryItemTypeId, "Forest Flower", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); assertEquals(1, forestFlower.inventoryItemId); InventoryItem.checkIn(client, forestFlower.inventoryItemId, null, 200, ReportingUnit.NUMBER, 1, Currency.SGD, "200 @ 1 SGD / Book", userWithPermission); InventoryCheckOutProp inventoryCheckOutProp = InventoryItem.checkOut( client, forestFlower.inventoryItemId, null, 100, ReportingUnit.NUMBER, 1.2, Currency.SGD, null, Utils.getSet("tag1"), userWithPermission); assertEquals(1, inventoryCheckOutProp.tags.size()); assertTrue(inventoryCheckOutProp.tags.contains("tag1")); } @Test public void checkInTest() { final InventoryItemProp forestFlower = InventoryItem.create(client, sgp.groupId, book.inventoryItemTypeId, "Forest Flower", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); assertEquals(1, forestFlower.inventoryItemId); final Date date = new Date(); // permission required for check in ensureAPIException(Status.ERROR_INSUFFICIENT_PERMISSION, new ICode() { @Override public void run() { InventoryItem.checkIn(client, forestFlower.inventoryItemId, date, 200, ReportingUnit.NUMBER, 1, Currency.SGD, "200 @ 1 SGD / Book", validUser); } }); long ms = new Date().getTime(); InventoryCheckInProp prop = InventoryItem.checkIn(client, forestFlower.inventoryItemId, date, 200, ReportingUnit.NUMBER, 1, Currency.SGD, "200 @ 1 SGD / Book", userWithPermission); assertEquals(1, prop.checkInId); assertTrue(Math.abs(prop.timestamp.getTime() - ms) < 10); assertEquals(prop.timestamp.getTime(), date.getTime()); assertEquals(true, prop.available); assertTrue(200.0 == prop.availableQtyInDefaultUnit); assertEquals(Currency.SGD, prop.ccy); assertEquals(ReportingUnit.NUMBER, prop.defaultUnit); assertTrue(1.0 == prop.pricePerDefaultUnit); assertTrue(200.0 == prop.qtyInDefaultUnit); assertEquals(prop.login, userWithPermission); InventoryItemProp inventoryItemProp = InventoryItem.safeGet(client, forestFlower.inventoryItemId).toProp(); InventoryItemProp.populateDependents(client, Utils.getList(inventoryItemProp)); assertTrue(200 == inventoryItemProp.availableQtyInReportingUnit); assertEquals(Currency.SGD, inventoryItemProp.ccy); assertTrue(1 == inventoryItemProp.avgPricePerReportingUnit); InventoryItem.checkIn(client, forestFlower.inventoryItemId, date, 200, ReportingUnit.NUMBER, 2, Currency.SGD, "200 @ 1 SGD / Book", userWithPermission); inventoryItemProp = InventoryItem.safeGet(client, forestFlower.inventoryItemId).toProp(); InventoryItemProp.populateDependents(client, Utils.getList(inventoryItemProp)); assertTrue(400 == inventoryItemProp.availableQtyInReportingUnit); assertEquals(Currency.SGD, inventoryItemProp.ccy); assertTrue(1.5 == inventoryItemProp.avgPricePerReportingUnit); } @Test public void checkOutTest() { final InventoryItemProp forestFlower = InventoryItem.create(client, sgp.groupId, book.inventoryItemTypeId, "Forest Flower", PhysicalQuantity.NUMBER, ReportingUnit.NUMBER, User.SUPER_USER); assertEquals(1, forestFlower.inventoryItemId); Date date = new Date(); // check in 200 forest flowers @ 1 SGD per book InventoryItem.checkIn(client, forestFlower.inventoryItemId, date, 200, ReportingUnit.NUMBER, 1, Currency.SGD, "200 @ 1 SGD / Book", userWithPermission); // check in 200 more @ 2 SGD per book InventoryItem.checkIn(client, forestFlower.inventoryItemId, date, 200, ReportingUnit.NUMBER, 2, Currency.SGD, "200 @ 1 SGD / Book", userWithPermission); // check out 300 without specifying price InventoryCheckOutProp checkOutProp = InventoryItem.checkOut(client, forestFlower.inventoryItemId, date, 300, ReportingUnit.NUMBER, null, Currency.SGD, "checking out 300", null, userWithPermission); assertTrue(Math.abs(1.3333 - checkOutProp.avgPricePerDefaultUnit) < 0.01); // (200 * 1 + 100 * 2)/(100 + 200) = 133.33 } }