def test_get_purchase_amount_in_interval(self): """ This test checks the "get_purchase_amount_in_interval" helper function. """ # Insert some purchases t1 = datetime.strptime('2018-02-01 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add( Purchase(user_id=1, product_id=1, amount=1, timestamp=t1)) t2 = datetime.strptime('2018-02-02 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add( Purchase(user_id=1, product_id=1, amount=5, timestamp=t2, revoked=True)) t3 = datetime.strptime('2018-02-03 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add( Purchase(user_id=2, product_id=1, amount=8, timestamp=t3)) t4 = datetime.strptime('2018-02-04 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add( Purchase(user_id=3, product_id=2, amount=2, timestamp=t4)) db.session.commit() # Get purchases in interval (second is revoked!) purchase_amount = purchase_helpers.get_purchase_amount_in_interval( product_id=1, start=t1, end=t4) self.assertEqual(9, purchase_amount)
def test_get_favorite_product_ids(self): """ This test ensures that the ids of purchased products are returned in descending order with respect to the frequency with which they were purchased by the user. """ # Insert user 1 purchases. p1 = Purchase(user_id=1, product_id=1, amount=4) p2 = Purchase(user_id=1, product_id=2, amount=4) p3 = Purchase(user_id=1, product_id=3, amount=5) p4 = Purchase(user_id=1, product_id=4, amount=1) p5 = Purchase(user_id=1, product_id=3, amount=5) p6 = Purchase(user_id=1, product_id=2, amount=4) # Insert other users purchases. p7 = Purchase(user_id=2, product_id=4, amount=30) p8 = Purchase(user_id=3, product_id=3, amount=4) p9 = Purchase(user_id=3, product_id=1, amount=12) p10 = Purchase(user_id=2, product_id=2, amount=8) for p in [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10]: db.session.add(p) db.session.commit() favorites = User.query.filter_by(id=1).first().favorites self.assertEqual([3, 2, 1, 4], favorites)
def test_multiple_purchases_update_product_price(self): """This test is designed to ensure that purchases still show the correct price even after price changes of products.""" # Generate timestamps for correct timing of purchases and updates t1 = datetime.datetime.now() - datetime.timedelta(seconds=30) t2 = datetime.datetime.now() - datetime.timedelta(seconds=25) t3 = datetime.datetime.now() - datetime.timedelta(seconds=20) t4 = datetime.datetime.now() - datetime.timedelta(seconds=15) t5 = datetime.datetime.now() - datetime.timedelta(seconds=10) t6 = datetime.datetime.now() - datetime.timedelta(seconds=5) # Update product price pp = ProductPrice(product_id=1, price=300, admin_id=1, timestamp=t1) db.session.add(pp) db.session.commit() # Get the first product price product = Product.query.filter_by(id=1).first() pr_1 = copy(product.price) # Do first purchase purchase = Purchase(user_id=1, product_id=1, amount=1, timestamp=t2) db.session.add(purchase) db.session.commit() # Update product price pp = ProductPrice(product_id=1, price=100, admin_id=1, timestamp=t3) db.session.add(pp) db.session.commit() # Get the second product price product = Product.query.filter_by(id=1).first() pr_2 = copy(product.price) # Do second purchase purchase = Purchase(user_id=1, product_id=1, amount=1, timestamp=t4) db.session.add(purchase) # Update product price pp = ProductPrice(product_id=1, price=600, admin_id=1, timestamp=t5) db.session.add(pp) db.session.commit() # Get the third product price product = Product.query.filter_by(id=1).first() pr_3 = copy(product.price) # Do third purchase purchase = Purchase(user_id=1, product_id=1, amount=1, timestamp=t6) db.session.add(purchase) db.session.commit() # Check the product prices self.assertEqual(pr_1, 300) self.assertEqual(pr_2, 100) self.assertEqual(pr_3, 600) # Check user credit user = User.query.filter_by(id=1).first() self.assertEqual(len(user.purchases.all()), 3) self.assertEqual(user.credit, -(pr_1 + pr_2 + pr_3)) # Check purchase prices purchases = Purchase.query.all() self.assertEqual(purchases[0].price, 300) self.assertEqual(purchases[1].price, 100) self.assertEqual(purchases[2].price, 600)
def insert_default_purchases(): p1 = Purchase(user_id=1, product_id=1, amount=1) p2 = Purchase(user_id=2, product_id=3, amount=2) p3 = Purchase(user_id=2, product_id=2, amount=4) p4 = Purchase(user_id=3, product_id=1, amount=6) p5 = Purchase(user_id=1, product_id=3, amount=8) for p in [p1, p2, p3, p4, p5]: db.session.add(p) db.session.commit()
def test_insert_multiple_purchases(self): """Testing multiple purchases""" user = User.query.filter_by(id=1).first() self.assertEqual(len(user.purchases.all()), 0) self.assertEqual(user.credit, 0) purchase_data = [ {'product_id': 1, 'amount': 1}, {'product_id': 2, 'amount': 5}, {'product_id': 4, 'amount': 5}, {'product_id': 1, 'amount': 2}, {'product_id': 3, 'amount': 4}, {'product_id': 1, 'amount': 10}, ] for data in purchase_data: purchase = Purchase(user_id=1, **data) db.session.add(purchase) db.session.commit() user = User.query.filter_by(id=1).first() self.assertEqual(len(user.purchases.all()), 6) for index, data in enumerate(purchase_data): self.assertEqual(user.purchases.all()[index].amount, data['amount']) c = 0 for data in purchase_data: c -= data['amount'] * Product.query.filter_by(id=data['product_id']).first().price self.assertEqual(user.credit, c)
def test_purchase_revokes(self): """This unittest is designed to ensure, that purchase revokes will be applied to the user credit""" # Insert some purchases for _ in range(1, 11): purchase = Purchase(user_id=1, product_id=1, amount=1) db.session.add(purchase) db.session.commit() user = User.query.filter(User.id == 1).first() self.assertEqual(user.credit, -3000) # Revoke some purchases purchases = Purchase.query.all() purchases[0].set_revoked(revoked=True) purchases[4].set_revoked(revoked=True) purchases[6].set_revoked(revoked=True) db.session.commit() # Check user credit user = User.query.filter(User.id == 1).first() self.assertEqual(user.credit, -2100) # Un-Revoke one purchase purchases = Purchase.query.all() purchases[4].set_revoked(revoked=False) db.session.commit() # Check user credit user = User.query.filter(User.id == 1).first() self.assertEqual(user.credit, -2400)
def _insert_purchases(): """Helper function to insert some test purchases.""" # Insert user 1 purchases. p1 = Purchase(user_id=1, product_id=1, amount=4) p2 = Purchase(user_id=1, product_id=2, amount=4) p3 = Purchase(user_id=1, product_id=3, amount=5) p4 = Purchase(user_id=1, product_id=4, amount=1) p5 = Purchase(user_id=1, product_id=3, amount=5) p6 = Purchase(user_id=1, product_id=2, amount=4) # Insert other users purchases. p7 = Purchase(user_id=2, product_id=4, amount=30) p8 = Purchase(user_id=3, product_id=3, amount=4) p9 = Purchase(user_id=3, product_id=1, amount=12) p10 = Purchase(user_id=2, product_id=2, amount=8) for p in [p1, p2, p3, p4, p5, p6, p7, p8, p9, p10]: db.session.add(p) db.session.commit()
def test_purchase_link_to_its_user(self): """ This test checks whether the reference to the user of a purchase is working correctly. """ db.session.add(Purchase(user_id=1, product_id=1, amount=1)) db.session.commit() purchase = Purchase.query.filter_by(id=1).first() self.assertEqual(purchase.user, User.query.filter_by(id=1).first())
def test_multi_user_purchases(self): """Testing purchases done by multiple users""" users = User.query.all() for user in users: self.assertEqual(user.credit, 0) # Insert purchases p1 = Purchase(user_id=1, product_id=1, amount=5) p2 = Purchase(user_id=2, product_id=2, amount=2) p3 = Purchase(user_id=3, product_id=4, amount=1) p4 = Purchase(user_id=3, product_id=3, amount=6) for p in [p1, p2, p3, p4]: db.session.add(p) db.session.commit() # Check purchases purchases = Purchase.query.all() products = Product.query.all() self.assertEqual(len(purchases), 4) self.assertEqual(purchases[0].amount, 5) self.assertEqual(purchases[0].product_id, 1) self.assertEqual(purchases[0].price, 5 * products[0].price) self.assertEqual(purchases[1].amount, 2) self.assertEqual(purchases[1].product_id, 2) self.assertEqual(purchases[1].price, 2 * products[1].price) self.assertEqual(purchases[2].amount, 1) self.assertEqual(purchases[2].product_id, 4) self.assertEqual(purchases[2].price, 1 * products[3].price) self.assertEqual(purchases[3].amount, 6) self.assertEqual(purchases[3].product_id, 3) self.assertEqual(purchases[3].price, 6 * products[2].price) # Check users users = User.query.all() self.assertEqual(users[0].credit, -5 * products[0].price) self.assertEqual(len(users[0].purchases.all()), 1) self.assertEqual(users[1].credit, -2 * products[1].price) self.assertEqual(len(users[1].purchases.all()), 1) credit = 1 * products[3].price + 6 * products[2].price self.assertEqual(users[2].credit, -credit) self.assertEqual(len(users[2].purchases.all()), 2) self.assertEqual(users[3].credit, 0) self.assertEqual(len(users[3].purchases.all()), 0)
def test_insert_purchase_as_non_verified_user(self): """It must be ensured that non-verified users cannot make purchases.""" user = User.query.filter_by(id=4).first() self.assertFalse(user.is_verified) with self.assertRaises(exc.UserIsNotVerified): Purchase(user_id=4, product_id=1) db.session.rollback() # No purchase may have been made at this point purchases = Purchase.query.all() self.assertEqual(len(purchases), 0)
def test_insert_simple_purchase(self): """Testing a simple purchase""" user = User.query.filter_by(id=1).first() self.assertEqual(len(user.purchases.all()), 0) self.assertEqual(user.credit, 0) product = Product.query.filter_by(id=1).first() purchase = Purchase(user_id=user.id, product_id=product.id, amount=1) db.session.add(purchase) db.session.commit() user = User.query.first() self.assertEqual(len(user.purchases.all()), 1) self.assertEqual(user.credit, -product.price)
def test_get_user_purchases(self): """Testing get user purchase list""" user = User.query.filter_by(id=1).first() self.assertEqual(len(user.purchases.all()), 0) amounts = [1, 5, 6, 8] ids = [1, 2, 4, 1] for i in range(0, 4): p = Purchase(user_id=1, product_id=ids[i], amount=amounts[i]) db.session.add(p) db.session.commit() user = User.query.filter_by(id=1).first() self.assertEqual(len(user.purchases.all()), 4) for i in range(0, 4): self.assertEqual(user.purchases.all()[i].amount, amounts[i]) self.assertEqual(user.purchases.all()[i].product_id, ids[i])
def test_balance_between_stocktakings_forgotten_purchases_inactive_products( self): """ TODO """ # Since the start of this test is the same as the previous one, it # can be run again to generate the required data. self.test_balance_between_stocktakings_multiple_stocktakings() # Ooops. We forgot to insert some two purchases (inactive products). Lets do it now (AFTER THIRD STOCKTAKING!) self.assertFalse(Product.query.filter_by(id=4).first().active) t = datetime.strptime('2018-04-02 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add(Purchase(user_id=3, product_id=4, amount=2, timestamp=t)) # Insert the fourth stocktaking. t = datetime.strptime('2018-04-05 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add(StocktakingCollection(admin_id=1, timestamp=t)) db.session.flush() stocktakings = [{ 'product_id': 1, 'count': 40 }, { 'product_id': 2, 'count': 5 }, { 'product_id': 3, 'count': 5 }, { 'product_id': 4, 'count': 0 }] for s in stocktakings: db.session.add(Stocktaking(collection_id=4, **s)) db.session.commit() # Query the stocktakings. start = StocktakingCollection.query.filter_by(id=1).first() end = StocktakingCollection.query.filter_by(id=4).first() result = stocktaking_helpers._get_balance_between_stocktakings( start, end) self.assertTrue('products' in result) products = result['products'] # Check if all products are in the balance self.assertEqual({1, 2, 3, 4}, set(products.keys())) # Check purchase count self.assertEqual(products[1]['purchase_count'], 9) self.assertEqual(products[2]['purchase_count'], 15) self.assertEqual(products[3]['purchase_count'], 13) self.assertEqual(products[4]['purchase_count'], 2) # Check purchase sum price self.assertEqual(products[1]['purchase_sum_price'], 2700) self.assertEqual(products[2]['purchase_sum_price'], 750) self.assertEqual(products[3]['purchase_sum_price'], 1300) self.assertEqual(products[4]['purchase_sum_price'], 400) # Check replenish count self.assertEqual(products[1]['replenish_count'], 10) self.assertEqual(products[2]['replenish_count'], 0) self.assertEqual(products[3]['replenish_count'], 5) self.assertEqual(products[4]['replenish_count'], 0) # Check differences self.assertEqual(products[1]['difference'], -61) self.assertEqual(products[2]['difference'], -30) self.assertEqual(products[3]['difference'], -12) self.assertEqual(products[4]['difference'], -31) # Check balance self.assertEqual(products[1]['balance'], -61 * 300) self.assertEqual(products[2]['balance'], -30 * 50) self.assertEqual(products[3]['balance'], -12 * 100) self.assertEqual(products[4]['balance'], -31 * 200) # Check overall balance self.assertEqual(result['balance'], -27200) self.assertEqual(result['loss'], 27200) self.assertEqual(result['profit'], 0)
def test_balance_between_stocktakings_multiple_stocktakings(self): """ This test checks whether the calculation of the balance works correctly over several stocktakings. """ # Since the start of this test is the same as the previous one, it # can be run again to generate the required data. self.test_balance_between_stocktakings_two_stocktakings() # Insert some purchases between the second and the third stocktaking t = datetime.strptime('2018-03-05 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add(Purchase(user_id=1, product_id=1, amount=2, timestamp=t)) t = datetime.strptime('2018-03-06 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add( Purchase(user_id=1, product_id=2, amount=10, timestamp=t)) t = datetime.strptime('2018-03-07 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add(Purchase(user_id=2, product_id=3, amount=5, timestamp=t)) t = datetime.strptime('2018-03-08 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add(Purchase(user_id=3, product_id=1, amount=4, timestamp=t)) # Insert the third stocktaking. t = datetime.strptime('2018-04-01 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add(StocktakingCollection(admin_id=1, timestamp=t)) db.session.flush() stocktakings = [{ 'product_id': 1, 'count': 40 }, { 'product_id': 2, 'count': 5 }, { 'product_id': 3, 'count': 5 }, { 'product_id': 4, 'count': 0 }] for s in stocktakings: db.session.add(Stocktaking(collection_id=3, **s)) # The count of product 4 is 0 so the product will be set inactive! Product.query.filter_by(id=4).first().active = False db.session.commit() # Query the stocktakings. start = StocktakingCollection.query.filter_by(id=1).first() end = StocktakingCollection.query.filter_by(id=3).first() # Timeline # Date Event # # 01.01.2017 Purchase: 100 x Product 1 # 01.01.2017 Replenishment 1 # (Both are not taken into account for the calculation) # ----------------------------------- # 01.01.2018 Stocktaking 1 # - Product 1: 100 # - Product 2: 50 # - Product 3: 25 # - Product 4: 33 # # 01.02.2018 Purchase: 1 x Product 1 # 02.02.2018 Purchase: 5 x Product 2 # 03.02.2018 Purchase: 8 x Product 3 # 04.02.2018 Purchase: 2 x Product 1 # ----------------------------------- # 05.02.2018 Replenishment 2 # - Product 1: 10 # - Product 3: 5 # # ----------------------------------- # 05.03.2018 Purchase: 2 x Product 1 # 06.03.2018 Purchase: 10 x Product 2 # 07.03.2018 Purchase: 5 x Product 3 # 08.03.2018 Purchase: 4 x Product 1 # ----------------------------------- # 01.04.2018 Stocktaking 3 # - Product 1: 40 # - Product 2: 5 # - Product 3: 5 # - Product 4: 0 # ----------------------------------- # Balance: # Product 1: 100 (start) - 9 (purchase) + 10 (replenishment) = 101 # Product 2: 50 (start) - 15 (purchase) + 0 (replenishment) = 35 # Product 3: 25 (start) - 13 (purchase) + 5 (replenishment) = 17 # Product 4: 33 (start) - 0 (purchase) + 0 (replenishment) = 33 # ----------------------------------- # Loss between first and third stocktaking # Product 1: 101 - 40 = 61 # Product 2: 35 - 5 = 30 # Product 3: 17 - 5 = 12 # Product 4: 33 - 0 = 33 result = stocktaking_helpers._get_balance_between_stocktakings( start, end) self.assertTrue('products' in result) products = result['products'] # Check if all products are in the balance self.assertEqual({1, 2, 3, 4}, set(products.keys())) # Check purchase count self.assertEqual(products[1]['purchase_count'], 9) self.assertEqual(products[2]['purchase_count'], 15) self.assertEqual(products[3]['purchase_count'], 13) self.assertEqual(products[4]['purchase_count'], 0) # Check purchase sum price self.assertEqual(products[1]['purchase_sum_price'], 2700) self.assertEqual(products[2]['purchase_sum_price'], 750) self.assertEqual(products[3]['purchase_sum_price'], 1300) self.assertEqual(products[4]['purchase_sum_price'], 0) # Check replenish count self.assertEqual(products[1]['replenish_count'], 10) self.assertEqual(products[2]['replenish_count'], 0) self.assertEqual(products[3]['replenish_count'], 5) self.assertEqual(products[4]['replenish_count'], 0) # Check differences self.assertEqual(products[1]['difference'], -61) self.assertEqual(products[2]['difference'], -30) self.assertEqual(products[3]['difference'], -12) self.assertEqual(products[4]['difference'], -33) # Check balance self.assertEqual(products[1]['balance'], -61 * 300) self.assertEqual(products[2]['balance'], -30 * 50) self.assertEqual(products[3]['balance'], -12 * 100) self.assertEqual(products[4]['balance'], -33 * 200) # Check overall balance self.assertEqual(result['balance'], -27600) self.assertEqual(result['loss'], 27600) self.assertEqual(result['profit'], 0)
def test_get_financial_overview(self): """ This test ensures that the entire financial overview is calculated correctly. To do this, some test entries are entered into the database, some of which are revoked. Then the amount is manually calculated which should come out at the end of the calculation and compared with the amount calculated by the API. """ # Add a product with negative price p_data = {'name': 'Negative', 'price': -100, 'tags': [1]} self.post(url='/products', role='admin', data=p_data) # Manipulate the product price timestamps ts = datetime.strptime('2017-01-01 09:00:00', '%Y-%m-%d %H:%M:%S') for product_price_id in [1, 2, 3, 4, 5]: ProductPrice.query.filter_by( id=product_price_id).first().timestamp = ts db.session.commit() # Insert the first stocktaking stocktakings = [{ 'product_id': 1, 'count': 100 }, { 'product_id': 2, 'count': 100 }, { 'product_id': 3, 'count': 100 }, { 'product_id': 4, 'count': 100 }, { 'product_id': 5, 'count': 100 }] t = datetime.strptime('2018-01-01 09:00:00', '%Y-%m-%d %H:%M:%S') data = {'stocktakings': stocktakings, 'timestamp': int(t.timestamp())} self.post(url='/stocktakingcollections', data=data, role='admin') # Insert some purchases (some are revoked) t = datetime.strptime('2018-01-01 10:00:00', '%Y-%m-%d %H:%M:%S') p1 = Purchase(user_id=1, product_id=3, amount=4, revoked=True, timestamp=t) p2 = Purchase(user_id=2, product_id=2, amount=3, revoked=False, timestamp=t) # <- p3 = Purchase(user_id=3, product_id=1, amount=2, revoked=False, timestamp=t) # <- p4 = Purchase(user_id=1, product_id=2, amount=1, revoked=True, timestamp=t) p5 = Purchase(user_id=1, product_id=5, amount=1, revoked=False, timestamp=t) # <- for p in [p1, p2, p3, p4, p5]: db.session.add(p) # Purchase amount should be 3 * 50 + 2 * 300 - 1 * 100 psum = p2.price + p3.price + p5.price self.assertEqual(psum, 650) # Insert some deposits (some are revoked) d1 = Deposit(user_id=1, admin_id=1, comment='Foo', amount=100, revoked=False) # <- d2 = Deposit(user_id=2, admin_id=1, comment='Foo', amount=500, revoked=True) d3 = Deposit(user_id=3, admin_id=1, comment='Foo', amount=300, revoked=False) # <- d4 = Deposit(user_id=2, admin_id=1, comment='Foo', amount=200, revoked=True) d5 = Deposit(user_id=2, admin_id=1, comment='Negative', amount=-100, revoked=False) for d in [d1, d2, d3, d4, d5]: db.session.add(d) # Insert the replenishmentcollections and revoke the first one. self.insert_default_replenishmentcollections() rc = ReplenishmentCollection.query.filter_by(id=1).first() rc.set_revoked(admin_id=1, revoked=True) db.session.commit() # Manipulate the replenishment timestamps # First replenishment: 01.01.2017 (Before the first stocktaking!) ts = datetime.strptime('2017-01-01 09:00:00', '%Y-%m-%d %H:%M:%S') ReplenishmentCollection.query.filter_by(id=1).first().timestamp = ts # Second replenishment: 01.02.2019 (Between the stocktakings) ts = datetime.strptime('2018-02-01 09:00:00', '%Y-%m-%d %H:%M:%S') ReplenishmentCollection.query.filter_by(id=2).first().timestamp = ts # Insert the second stocktaking stocktakings = [ { 'product_id': 1, 'count': 110 }, # Products have been added! { 'product_id': 2, 'count': 90 }, # Products have been lost! { 'product_id': 3, 'count': 100 }, { 'product_id': 4, 'count': 100 }, { 'product_id': 5, 'count': 100 } ] t = datetime.strptime('2018-03-01 09:00:00', '%Y-%m-%d %H:%M:%S') data = {'stocktakings': stocktakings, 'timestamp': int(t.timestamp())} self.post(url='/stocktakingcollections', data=data, role='admin') # Calculate the total balance, incomes and expenses. # Incomes are: # - Purchases with a positive price # - Deposits with a positive amount # - Replenishmentcollections with a negative price # - Profits between stocktakings positive_purchase_amount = 750 positive_deposits_amount = 400 negative_replenishmentcollections_price = 0 profit_between_stocktakings = 600 incomes = sum([ positive_purchase_amount, positive_deposits_amount, negative_replenishmentcollections_price, profit_between_stocktakings ]) # Expenses are: # - Purchases with a negative price # - Deposits with a negative amount # - Replenishmentcollections with a positive price # - Losses between stocktakings negative_purchase_amount = 100 negative_deposits_amount = 100 positive_replenishmentcollections_price = 3500 loss_between_stocktakings = 950 expenses = sum([ negative_purchase_amount, negative_deposits_amount, positive_replenishmentcollections_price, loss_between_stocktakings ]) total_balance = incomes - expenses res = self.get(url='/financial_overview', role='admin') self.assertEqual(res.status_code, 200) overview = json.loads(res.data) self.assertEqual(overview['total_balance'], total_balance) self.assertEqual(overview['incomes']['amount'], incomes) self.assertEqual(overview['expenses']['amount'], expenses) # Check the incomes api_incomes = overview['incomes']['items'] self.assertEqual(api_incomes[0]['name'], 'Purchases') self.assertEqual(api_incomes[0]['amount'], positive_purchase_amount) self.assertEqual(api_incomes[1]['name'], 'Deposits') self.assertEqual(api_incomes[1]['amount'], positive_deposits_amount) self.assertEqual(api_incomes[2]['name'], 'Replenishments') self.assertEqual(api_incomes[2]['amount'], negative_replenishmentcollections_price) self.assertEqual(api_incomes[3]['name'], 'Stocktakings') self.assertEqual(api_incomes[3]['amount'], profit_between_stocktakings) # Check the expenses api_incomes = overview['expenses']['items'] self.assertEqual(api_incomes[0]['name'], 'Purchases') self.assertEqual(api_incomes[0]['amount'], negative_purchase_amount) self.assertEqual(api_incomes[1]['name'], 'Deposits') self.assertEqual(api_incomes[1]['amount'], negative_deposits_amount) self.assertEqual(api_incomes[2]['name'], 'Replenishments') self.assertEqual(api_incomes[2]['amount'], positive_replenishmentcollections_price) self.assertEqual(api_incomes[3]['name'], 'Stocktakings') self.assertEqual(api_incomes[3]['amount'], loss_between_stocktakings)
def create_purchase(admin): """ Insert a new purchase. :param admin: Is the administrator user, determined by @adminOptional. :return: A message that the creation was successful. :raises DataIsMissing: If not all required data is available. :raises WrongType: If one or more data is of the wrong type. :raises EntryNotFound: If the user with this ID does not exist. :raises UserIsNotVerified: If the user has not yet been verified. :raises EntryNotFound: If the product with this ID does not exist. :raises EntryIsInactive: If the product is inactive. :raises InvalidAmount: If amount is less than or equal to zero. :raises InsufficientCredit: If the credit balance of the user is not sufficient. :raises CouldNotCreateEntry: If any other error occurs. """ data = json_body() required = {'user_id': int, 'product_id': int, 'amount': int} check_fields_and_types(data, required) # Check user user = User.query.filter_by(id=data['user_id']).first() if not user: raise exc.EntryNotFound() # Check if the user has been verified. if not user.is_verified: raise exc.UserIsNotVerified() # Check if the user is inactive if not user.active: raise exc.UserIsInactive() # Check product product = Product.query.filter_by(id=data['product_id']).first() if not product: raise exc.EntryNotFound() if not product.active: raise exc.EntryIsInactive() # Check amount if data['amount'] <= 0: raise exc.InvalidAmount() # If the purchase is made by an administrator, the credit limit # may be exceeded. if not admin: limit = Rank.query.filter_by(id=user.rank_id).first().debt_limit current_credit = user.credit future_credit = current_credit - (product.price * data['amount']) if future_credit < limit: raise exc.InsufficientCredit() try: purchase = Purchase(**data) db.session.add(purchase) db.session.commit() except IntegrityError: raise exc.CouldNotCreateEntry() return jsonify({'message': 'Purchase created.'}), 200
def test_balance_between_stocktakings_product_set_to_inactive(self): """ This test checks whether the calculation of the balance works correctly if a product has been set to inactive since the first stocktaking. """ # Manipulate the product price timestamps ts = datetime.strptime('2017-01-01 09:00:00', '%Y-%m-%d %H:%M:%S') for product_price_id in [1, 2, 3, 4]: ProductPrice.query.filter_by( id=product_price_id).first().timestamp = ts db.session.commit() # Insert the first stocktaking db.session.add(StocktakingCollection(admin_id=1)) db.session.flush() stocktakings = [{ 'product_id': 1, 'count': 100 }, { 'product_id': 2, 'count': 50 }, { 'product_id': 3, 'count': 25 }, { 'product_id': 4, 'count': 33 }] for s in stocktakings: db.session.add(Stocktaking(**s, collection_id=1)) # Manipulate first stocktaking timestamp # First stocktaking: 01.01.2018 ts = datetime.strptime('2018-01-01 09:00:00', '%Y-%m-%d %H:%M:%S') StocktakingCollection.query.filter_by(id=1).first().timestamp = ts # Insert a purchase. t = datetime.strptime('2018-03-05 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add( Purchase(user_id=1, product_id=1, amount=90, timestamp=t)) # Insert the second stocktaking. stocktakings = [{ 'product_id': 1, 'count': 0 }, { 'product_id': 2, 'count': 50 }, { 'product_id': 3, 'count': 25 }, { 'product_id': 4, 'count': 33 }] t = datetime.strptime('2018-04-01 09:00:00', '%Y-%m-%d %H:%M:%S') data = {'stocktakings': stocktakings, 'timestamp': int(t.timestamp())} self.post(url='/stocktakingcollections', data=data, role='admin') # Insert the third stocktaking. (Product 1 is not included) stocktakings = [{ 'product_id': 2, 'count': 50 }, { 'product_id': 3, 'count': 25 }, { 'product_id': 4, 'count': 33 }] t = datetime.strptime('2018-04-02 09:00:00', '%Y-%m-%d %H:%M:%S') data = {'stocktakings': stocktakings, 'timestamp': int(t.timestamp())} self.post(url='/stocktakingcollections', data=data, role='admin') # Query the stocktakings. start = StocktakingCollection.query.filter_by(id=1).first() end = StocktakingCollection.query.filter_by(id=3).first() self.assertTrue(1 in [s.product_id for s in start.stocktakings]) self.assertFalse(1 in [s.product_id for s in end.stocktakings]) result = stocktaking_helpers._get_balance_between_stocktakings( start, end) self.assertTrue('products' in result) products = result['products'] # Check if all products are in the balance self.assertEqual({1, 2, 3, 4}, set(products.keys())) # Check purchase count self.assertEqual(products[1]['purchase_count'], 90) self.assertEqual(products[2]['purchase_count'], 0) self.assertEqual(products[3]['purchase_count'], 0) self.assertEqual(products[4]['purchase_count'], 0) # Check purchase sum price self.assertEqual(products[1]['purchase_sum_price'], 27000) self.assertEqual(products[2]['purchase_sum_price'], 0) self.assertEqual(products[3]['purchase_sum_price'], 0) self.assertEqual(products[4]['purchase_sum_price'], 0) # Check replenish count self.assertEqual(products[1]['replenish_count'], 0) self.assertEqual(products[2]['replenish_count'], 0) self.assertEqual(products[3]['replenish_count'], 0) self.assertEqual(products[4]['replenish_count'], 0) # Check differences self.assertEqual(products[1]['difference'], -10) self.assertEqual(products[2]['difference'], 0) self.assertEqual(products[3]['difference'], 0) self.assertEqual(products[4]['difference'], 0) # Check balance self.assertEqual(products[1]['balance'], -10 * 300) self.assertEqual(products[2]['balance'], 0) self.assertEqual(products[3]['balance'], 0) self.assertEqual(products[4]['balance'], 0) # Check overall balance self.assertEqual(result['balance'], -3000) self.assertEqual(result['loss'], 3000) self.assertEqual(result['profit'], 0)
def test_balance_between_stocktakings_two_stocktakings(self): """ This test checks whether the calculation of the balance works correctly between two stocktakings. """ # Insert a purchase which lies before the first stocktaking. t = datetime.strptime('2017-01-01 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add( Purchase(user_id=1, product_id=1, amount=100, timestamp=t)) # Manipulate the product price timestamps ts = datetime.strptime('2017-01-01 09:00:00', '%Y-%m-%d %H:%M:%S') for product_price_id in [1, 2, 3, 4]: ProductPrice.query.filter_by( id=product_price_id).first().timestamp = ts db.session.commit() # Insert the default stocktaking collections. self.insert_default_stocktakingcollections() # Manipulate the stocktaking timestamps # First stocktaking: 01.01.2018 ts = datetime.strptime('2018-01-01 09:00:00', '%Y-%m-%d %H:%M:%S') StocktakingCollection.query.filter_by(id=1).first().timestamp = ts # Second stocktaking: 01.03.2018 ts = datetime.strptime('2018-03-01 09:00:00', '%Y-%m-%d %H:%M:%S') StocktakingCollection.query.filter_by(id=2).first().timestamp = ts # Insert the default replenishment collections. self.insert_default_replenishmentcollections() # Manipulate the replenishment timestamps # First replenishment: 01.01.2017 (Before the first stocktaking!) ts = datetime.strptime('2017-01-01 09:00:00', '%Y-%m-%d %H:%M:%S') ReplenishmentCollection.query.filter_by(id=1).first().timestamp = ts # Second replenishment: 05.02.2019 (Between the stocktakings) ts = datetime.strptime('2018-02-05 09:00:00', '%Y-%m-%d %H:%M:%S') ReplenishmentCollection.query.filter_by(id=2).first().timestamp = ts # Insert some purchases t = datetime.strptime('2018-02-01 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add(Purchase(user_id=1, product_id=1, amount=1, timestamp=t)) t = datetime.strptime('2018-02-02 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add(Purchase(user_id=1, product_id=2, amount=5, timestamp=t)) t = datetime.strptime('2018-02-03 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add(Purchase(user_id=2, product_id=3, amount=8, timestamp=t)) t = datetime.strptime('2018-02-04 09:00:00', '%Y-%m-%d %H:%M:%S') db.session.add(Purchase(user_id=3, product_id=1, amount=2, timestamp=t)) # Query the stocktakings. start = StocktakingCollection.query.filter_by(id=1).first() end = StocktakingCollection.query.filter_by(id=2).first() # Timeline # Date Event # # 01.01.2017 Purchase: 100 x Product 1 # 01.01.2017 Replenishment 1 # (Both are not taken into account for the calculation) # ----------------------------------- # 01.01.2018 Stocktaking 1 # - Product 1: 100 # - Product 2: 50 # - Product 3: 25 # - Product 4: 33 # # 01.02.2018 Purchase: 1 x Product 1 # 02.02.2018 Purchase: 5 x Product 2 # 03.02.2018 Purchase: 8 x Product 3 # 04.02.2018 Purchase: 2 x Product 1 # 05.02.2018 Replenishment 2 # - Product 1: 10 # - Product 3: 5 # # ----------------------------------- # Balance: # Product 1: 100 (start) - 3 (purchase) + 10 (replenishment) = 107 # Product 2: 50 (start) - 5 (purchase) + 0 (replenishment) = 45 # Product 3: 25 (start) - 8 (purchase) + 5 (replenishment) = 22 # Product 4: 33 (start) - 0 (purchase) + 0 (replenishment) = 33 # ----------------------------------- # 01.03.2018 Stocktaking 2 # - Product 1: 50 # - Product 2: 25 # - Product 3: 12 # - Product 4: 3 # ----------------------------------- # Loss # Product 1: 107 - 50 = 57 # Product 2: 45 - 25 = 20 # Product 3: 22 - 12 = 10 # Product 4: 33 - 3 = 30 result = stocktaking_helpers._get_balance_between_stocktakings( start, end) self.assertTrue('products' in result) products = result['products'] # Check if all products are in the balance self.assertEqual({1, 2, 3, 4}, set(products.keys())) # Check purchase count self.assertEqual(products[1]['purchase_count'], 3) self.assertEqual(products[2]['purchase_count'], 5) self.assertEqual(products[3]['purchase_count'], 8) self.assertEqual(products[4]['purchase_count'], 0) # Check purchase sum price self.assertEqual(products[1]['purchase_sum_price'], 900) self.assertEqual(products[2]['purchase_sum_price'], 250) self.assertEqual(products[3]['purchase_sum_price'], 800) self.assertEqual(products[4]['purchase_sum_price'], 0) # Check replenish count self.assertEqual(products[1]['replenish_count'], 10) self.assertEqual(products[2]['replenish_count'], 0) self.assertEqual(products[3]['replenish_count'], 5) self.assertEqual(products[4]['replenish_count'], 0) # Check differences self.assertEqual(products[1]['difference'], -57) self.assertEqual(products[2]['difference'], -20) self.assertEqual(products[3]['difference'], -10) self.assertEqual(products[4]['difference'], -30) # Check balance self.assertEqual(products[1]['balance'], -57 * 300) self.assertEqual(products[2]['balance'], -20 * 50) self.assertEqual(products[3]['balance'], -10 * 100) self.assertEqual(products[4]['balance'], -30 * 200) # Check overall balance self.assertEqual(result['balance'], -25100) self.assertEqual(result['loss'], 25100) self.assertEqual(result['profit'], 0)
def insert_purchase(admin: Optional[User], data: dict) -> None: """ This helper function creates a single purchase without doing a commit. :param admin: Is the administrator user, determined by @adminOptional. :param data: Is the purchase data. :return: None """ required = {'user_id': int, 'product_id': int, 'amount': int} optional = {'timestamp': str} # If the request is not made by an administrator, the timestamp can't be set if admin is None and 'timestamp' in data: raise exc.ForbiddenField() check_fields_and_types(data, required, optional) # Check user user = User.query.filter_by(id=data['user_id']).first() if not user: raise exc.EntryNotFound() # Check if the user has been verified. if not user.is_verified: raise exc.UserIsNotVerified() # Check if the user is inactive if not user.active: raise exc.UserIsInactive() # Check the user rank. If it is a system user, only administrators are allowed to insert purchases if user.rank.is_system_user and admin is None: raise exc.UnauthorizedAccess() # Parse the timestamp data = parse_timestamp(data, required=False) # Check product product = Product.query.filter_by(id=data['product_id']).first() if not product: raise exc.EntryNotFound() if not admin and not product.active: raise exc.EntryIsInactive() # Check weather the product is for sale if the request is not made by an administrator if not admin and any(map(lambda tag: not tag.is_for_sale, product.tags)): raise exc.EntryIsNotForSale() # Check amount if data['amount'] <= 0: raise exc.InvalidAmount() # If the purchase is made by an administrator, the credit limit # may be exceeded. if not admin: limit = Rank.query.filter_by(id=user.rank_id).first().debt_limit current_credit = user.credit future_credit = current_credit - (product.price * data['amount']) if future_credit < limit: raise exc.InsufficientCredit() try: if admin: purchase = Purchase(admin_id=admin.id, **data) else: purchase = Purchase(**data) db.session.add(purchase) except IntegrityError: raise exc.CouldNotCreateEntry()