예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
    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)
예제 #4
0
 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()
예제 #5
0
    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)
예제 #6
0
 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()
예제 #8
0
 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())
예제 #9
0
    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)
예제 #10
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)
예제 #11
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)
예제 #12
0
 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])
예제 #13
0
    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)
예제 #14
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)
예제 #16
0
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
예제 #17
0
    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)
예제 #18
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)
예제 #19
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()