Exemple #1
0
def delete_tag(admin, tag_id):
    """
    Delete a tag.

    :param admin:                 Is the administrator user, determined by
                                  @adminRequired.
    :param tag_id:                Is the tag id.

    :return:                      A message that the deletion was successful.

    :raises EntryNotFound:        If the user with this ID does not exist.
    :raises EntryCanNotBeDeleted: If the tag can not be deleted.
    """
    tag = Tag.query.filter_by(id=tag_id).first()
    if not tag:
        raise exc.EntryNotFound()

    # You can't delete the last remaining tag
    tags = Tag.query.all()
    if len(tags) == 1:
        raise exc.NoRemainingTag()

    # Check all product tags
    for product in tag.products:
        if len(product.tags) == 1:
            raise exc.NoRemainingTag()

    # Delete the tag.
    try:
        db.session.delete(tag)
        db.session.commit()
    except IntegrityError:
        raise exc.EntryCanNotBeDeleted()

    return jsonify({'message': 'Tag deleted.'}), 200
def get_stocktakingcollection_template():
    """
    This route can be used to retrieve a template to print out for a
    stocktaking. It lists all the products that must be included in the
    stocktaking.

    :return:      A rendered PDF file with all products for the stocktaking.
    """
    # Get a list of all products.
    products = (Product.query.filter(Product.active.is_(True)).filter(
        Product.countable.is_(True)).order_by(func.lower(Product.name)).all())

    # If no products exist that are active and countable, an exception must be
    # made.
    if not products:
        raise exc.EntryNotFound()

    # Render the template
    rendered = render_template('stocktakingcollections_template.html',
                               products=products)
    # Create a PDF file from the rendered template.
    pdf = pdfkit.from_string(rendered, False)
    response = make_response(pdf)
    response.headers['Content-Type'] = 'application/pdf'
    response.headers['Content-Disposition'] = 'inline; filename=output.pdf'

    # Return the PDF file.
    return response
Exemple #3
0
def get_stocktakingcollections(admin, collection_id):
    """
    Returns the stocktakingcollection with the requested id. In addition,
    all stocktakings that belong to this collection are returned.

    :param admin:          Is the administrator user,
                           determined by @adminRequired.
    :param collection_id:  Is the stocktakingcollection id.

    :return:               The requested stocktakingcollection and all
                           related stocktakings JSON object.

    :raises EntryNotFound: If the stocktakingcollection with this ID does
                           not exist.
    """
    # Query the stocktakingcollection.
    collection = StocktakingCollection.query.filter_by(
        id=collection_id).first()
    # If it does not exist, raise an exception.
    if not collection:
        raise exc.EntryNotFound()

    fields_collection = [
        'id', 'timestamp', 'admin_id', 'revoked', 'revokehistory'
    ]
    fields_stocktaking = ['id', 'product_id', 'count', 'collection_id']
    stocktakings = collection.stocktakings.all()

    result = convert_minimal(collection, fields_collection)[0]
    result['stocktakings'] = convert_minimal(stocktakings, fields_stocktaking)
    return jsonify(result), 200
Exemple #4
0
def get_replenishmentcollection(admin, id):
    """
    Returns the replenishmentcollection with the requested id. In addition,
    all replenishments that belong to this collection are returned.

    :param admin:          Is the administrator user,
                           determined by @adminRequired.
    :param id:             Is the replenishmentcollection id.

    :return:               The requested replenishmentcollection and all
                           related replenishments JSON object.

    :raises EntryNotFound: If the replenishmentcollection with this ID does
                           not exist.
    """
    # Query the replenishmentcollection.
    replcoll = ReplenishmentCollection.query.filter_by(id=id).first()
    # If it does not exist, raise an exception.
    if not replcoll:
        raise exc.EntryNotFound()

    fields_replcoll = [
        'id', 'timestamp', 'admin_id', 'price', 'revoked', 'revokehistory',
        'comment'
    ]
    fields_repl = [
        'id', 'replcoll_id', 'product_id', 'amount', 'total_price', 'revoked'
    ]
    repls = replcoll.replenishments.all()

    result = convert_minimal(replcoll, fields_replcoll)[0]
    result['replenishments'] = convert_minimal(repls, fields_repl)
    return jsonify(result), 200
Exemple #5
0
def delete_user(admin, user_id):
    """
    Delete a user. This is only possible if the user has not yet been verified.

    :param admin:                 Is the administrator user, determined by
                                  @adminRequired.
    :param user_id:               Is the user id.

    :return:                      A message that the deletion was successful.

    :raises EntryNotFound:        If the user with this ID does not exist.
    :raises EntryCanNotBeDeleted: If the user has already been verified or the
                                  deletion cannot take place for any other
                                  reason.
    """
    # Check if the user exists
    user = User.query.filter_by(id=user_id).first()
    if not user:
        raise exc.EntryNotFound()

    # Check if the user has been verified
    if user.is_verified:
        raise exc.EntryCanNotBeDeleted()

    # Delete the user
    try:
        db.session.delete(user)
        db.session.commit()
    except IntegrityError:
        raise exc.EntryCanNotBeDeleted()

    return jsonify({'message': 'User deleted.'}), 200
Exemple #6
0
def get_user(admin, user_id):
    """
    Returns the user with the requested id.

    :param admin: Is the administrator user, determined by @adminOptional.
    :param user_id:            Is the user id.

    :return:                   The requested user as JSON object.
    """
    # Query user
    user = User.query.filter(User.id == user_id).first()
    if not user:
        raise exc.EntryNotFound()

    if admin is None:
        # 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()

    fields = ['id', 'firstname', 'lastname', 'fullname', 'credit', 'rank_id', 'imagename', 'active',
              'is_admin', 'creation_date', 'verification_date', 'is_verified']
    user = convert_minimal(user, fields)[0]
    return jsonify(user), 200
Exemple #7
0
def get_product_stock(product_id):
    """
    Returns the theoretical stock level of a product.

    The theoretical stock level of a product is the result of the number
    determined in the last stocktaking minus the number of purchases
    that were not revoked since then.

    If the requested product is not countable, None will be returned.

    :param product_id:          Is the product id.

    :return:                    The theoretical stock level

    :raises EntryNotFound:      If the product with this ID does not exist.
    """

    # Check, whether the requested product exists
    product = Product.query.filter(Product.id == product_id).first()
    if not product:
        raise exc.EntryNotFound()

    # If the product is not countable, return None
    if not product.countable:
        return jsonify(None), 200

    # Get the theoretical stock level
    theoretical_stock = product_helpers.get_theoretical_stock_of_product(
        product_id)

    return jsonify(theoretical_stock), 200
Exemple #8
0
def update_purchase(id):
    """
    Update the purchase with the given id.

    :param id:                   Is the purchase id.

    :return:                     A message that the update was
                                 successful and a list of all updated fields.

    :raises EntryNotFound:       If the purchase with this ID does not exist.
    :raises EntryNotRevocable:   An attempt is made to revoked a purchase
                                 whose product is not revocable.
    :raises ForbiddenField:      If a forbidden field is in the request data.
    :raises UnknownField:        If an unknown parameter exists in the request
                                 data.
    :raises InvalidType:         If one or more parameters have an invalid
                                 type.
    :raises NothingHasChanged:   If no change occurred after the update.
    :raises CouldNotUpdateEntry: If any other error occurs.
    """
    # Check purchase
    purchase = Purchase.query.filter_by(id=id).first()
    if not purchase:
        raise exc.EntryNotFound()

    # Query the product
    product = Product.query.filter_by(id=purchase.product_id).first()

    data = json_body()
    updateable = {'revoked': bool, 'amount': int}
    check_forbidden(data, updateable, purchase)
    check_fields_and_types(data, None, updateable)

    updated_fields = []

    # Handle purchase revoke
    if 'revoked' in data:
        # In case that the product is not revocable, an exception must be made.
        if not product.revocable:
            raise exc.EntryNotRevocable()
        if purchase.revoked == data['revoked']:
            raise exc.NothingHasChanged()
        purchase.toggle_revoke(revoked=data['revoked'])
        updated_fields.append('revoked')
        del data['revoked']

    # Handle all other fields
    updated_fields = update_fields(data, purchase, updated=updated_fields)

    # Apply changes
    try:
        db.session.commit()
    except IntegrityError:
        raise exc.CouldNotUpdateEntry()

    return jsonify({
        'message': 'Updated purchase.',
        'updated_fields': updated_fields
    }), 201
def update_stocktakingcollection(admin, id):
    """
    Update the stocktakingcollection with the given id.

    :param admin:                Is the administrator user, determined by
                                 @adminRequired.
    :param id:                   Is the stocktakingcollection id.

    :return:                     A message that the update was successful.

    :raises EntryNotFound:       If the stocktakingcollection with this ID
                                 does not exist.
    :raises ForbiddenField:      If a forbidden field is in the request data.
    :raises UnknownField:        If an unknown parameter exists in the request
                                 data.
    :raises InvalidType:         If one or more parameters have an invalid type.
    :raises NothingHasChanged:   If no change occurred after the update.
    :raises CouldNotUpdateEntry: If any other error occurs.
    """
    # Check StocktakingCollection
    collection = (StocktakingCollection.query.filter_by(id=id).first())
    if not collection:
        raise exc.EntryNotFound()

    data = json_body()

    if data == {}:
        raise exc.NothingHasChanged()

    updateable = {'revoked': bool}
    check_forbidden(data, updateable, collection)
    check_fields_and_types(data, None, updateable)

    updated_fields = []
    # Handle revoke
    if 'revoked' in data:
        if collection.revoked == data['revoked']:
            raise exc.NothingHasChanged()
        collection.toggle_revoke(revoked=data['revoked'], admin_id=admin.id)
        del data['revoked']
        updated_fields.append('revoked')

    # Handle all other fields
    updated_fields = update_fields(data, collection, updated_fields)

    # Apply changes
    try:
        db.session.commit()
    except IntegrityError:
        raise exc.CouldNotUpdateEntry()

    return jsonify({
        'message': 'Updated stocktakingcollection.',
        'updated_fields': updated_fields
    }), 201
def update_stocktaking(admin, id):
    """
    Update the stocktaking with the given id.

    :param admin:                Is the administrator user, determined by
                                 @adminRequired.
    :param id:                   Is the stocktaking id.

    :return:                     A message that the update was successful
                                 and a list of all updated fields.

    :raises EntryNotFound:       If the stocktaking with this ID does not
                                 exist.
    :raises ForbiddenField:      If a forbidden field is in the request data.
    :raises UnknownField:        If an unknown parameter exists in the
                                 request data.
    :raises InvalidType:         If one or more parameters have an invalid
                                 type.
    :raises NothingHasChanged:   If no change occurred after the update.
    :raises CouldNotUpdateEntry: If any other error occurs.
    """
    # Check Stocktaking
    stocktaking = Stocktaking.query.filter_by(id=id).first()
    if not stocktaking:
        raise exc.EntryNotFound()

    # Data validation
    data = json_body()
    updateable = {'count': int}
    check_forbidden(data, updateable, stocktaking)
    check_fields_and_types(data, None, updateable)

    updated_fields = []
    message = 'Updated stocktaking.'

    # Check count
    if 'count' in data:
        if data['count'] < 0:
            raise exc.InvalidAmount()

        if data['count'] == stocktaking.count:
            raise exc.NothingHasChanged()

    # Handle all other fields
    updated_fields = update_fields(data, stocktaking, updated_fields)

    # Apply changes
    try:
        db.session.commit()
    except IntegrityError:
        raise exc.CouldNotUpdateEntry()

    return jsonify({'message': message, 'updated_fields': updated_fields}), 201
Exemple #11
0
def verify_user(admin, id):
    """
    Verify a user.

    :param admin:                Is the administrator user, determined by
                                 @adminRequired.
    :param id:                   Is the user id.

    :return:                     A message that the verification was successful.

    :raises UserAlreadyVerified: If the user already has been verified.
    :raises DataIsMissing:       If the rank_id is not included in the request.
    :raises UnknownField:        If an unknown parameter exists in the request
                                 data.
    :raises InvalidType:         If one or more parameters have an invalid
                                 type.
    :raises EntryNotFound:       If the rank to be assigned to the user does
                                 not exist.
    """
    user = User.query.filter_by(id=id).first()
    if not user:
        raise exc.EntryNotFound()
    if user.is_verified:
        raise exc.UserAlreadyVerified()

    data = json_body()
    # Check all items in the json body.
    required = {'rank_id': int}
    check_fields_and_types(data, required)

    rank_id = data['rank_id']
    rank = Rank.query.filter_by(id=rank_id).first()
    if not rank:
        raise exc.EntryNotFound()

    user.verify(admin_id=admin.id, rank_id=rank_id)
    db.session.commit()
    return jsonify({'message': 'Verified user.'}), 201
Exemple #12
0
def update_refund(admin, id):
    """
    Update the refund with the given id.

    :param admin:                Is the administrator user, determined by
                                 @adminRequired.
    :param id:                   Is the refund id.

    :return:                     A message that the update was
                                 successful and a list of all updated fields.

    :raises EntryNotFound:       If the refund with this ID does not exist.
    :raises ForbiddenField:      If a forbidden field is in the request data.
    :raises UnknownField:        If an unknown parameter exists in the request
                                 data.
    :raises InvalidType:         If one or more parameters have an invalid
                                 type.
    :raises NothingHasChanged:   If no change occurred after the update.
    :raises CouldNotUpdateEntry: If any other error occurs.
    """
    # Check refund
    refund = Refund.query.filter_by(id=id).first()
    if not refund:
        raise exc.EntryNotFound()

    data = json_body()

    if not data:
        raise exc.NothingHasChanged()

    updateable = {'revoked': bool}
    check_forbidden(data, updateable, refund)
    check_fields_and_types(data, None, updateable)

    # Handle refund revoke
    if 'revoked' in data:
        if refund.revoked == data['revoked']:
            raise exc.NothingHasChanged()
        refund.toggle_revoke(revoked=data['revoked'], admin_id=admin.id)

    # Apply changes
    try:
        db.session.commit()
    except IntegrityError:
        raise exc.CouldNotUpdateEntry()

    return jsonify({
        'message': 'Updated refund.',
    }), 201
Exemple #13
0
    def decorator(*args, **kwargs):
        user = User.query.filter_by(id=kwargs['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()

        # If all criteria are met, the requested function can be executed.
        return f(user, *args, **kwargs)
Exemple #14
0
def update_user(admin, user_id):
    """
    Update the user with the given id.

    :param admin:   Is the administrator user, determined by @adminRequired.
    :param user_id: Is the user id.

    :return:        A message that the update was successful and a list of all updated fields.
    """
    # Get the update data
    data = json_body()

    # Query the user. If he/she is not verified yet, there *must* be a
    # rank_id given in the update data.
    user = User.query.filter(User.id == user_id).first()
    if not user:
        raise exc.EntryNotFound()

    if not user.is_verified and 'rank_id' not in data:
        raise exc.UserIsNotVerified()

    # The password pre-check must be done here...
    if 'password' in data:
        # The repeat password must be there, too!
        if 'password_repeat' not in data:
            raise exc.DataIsMissing()

        # Both must be strings
        if not all([isinstance(x, str) for x in [data['password'], data['password_repeat']]]):
            raise exc.WrongType()

        # Passwords must match
        if data['password'] != data['password_repeat']:
            raise exc.PasswordsDoNotMatch()

        # Minimum password length
        if len(data['password']) < app.config['MINIMUM_PASSWORD_LENGTH']:
            raise exc.PasswordTooShort()

        # Convert the password into a salted hash
        # DONT YOU DARE TO REMOVE THIS LINE
        data['password'] = bcrypt.generate_password_hash(data['password'])
        # DONT YOU DARE TO REMOVE THIS LINE

        # All fine, delete repeat_password from the dict and do the rest of the update
        del data['password_repeat']

    return generic_update(User, user_id, data, admin)
Exemple #15
0
def get_rank(rank_id):
    """
    Returns the rank with the requested id.

    :param rank_id:        Is the rank id.

    :return:               The requested rank as JSON object.

    :raises EntryNotFound: If the rank with this ID does not exist.
    """
    result = Rank.query.filter_by(id=rank_id).first()
    if not result:
        raise exc.EntryNotFound()

    rank = convert_minimal(result, ['id', 'name', 'debt_limit', 'is_system_user'])[0]
    return jsonify(rank), 200
Exemple #16
0
def get_tag(tag_id):
    """
    Returns the tag with the requested id.

    :param tag_id:         Is the tag id.

    :return:               The requested tag as JSON object.

    :raises EntryNotFound: If the tag with this ID does not exist.
    """
    result = Tag.query.filter_by(id=tag_id).first()
    if not result:
        raise exc.EntryNotFound()

    tag = convert_minimal(result, ['id', 'name', 'created_by', 'is_for_sale'])[0]
    return jsonify(tag), 200
Exemple #17
0
def get_image(imagename):
    """
    A picture can be requested via this route. If the image is not found or if
    the image name is empty, a default image will be returned.

    :param imagename: Is the name of the requested image.

    :return:          The requested image or the default image, if applicable.
    """
    if not imagename:
        return send_from_directory(app.config['UPLOAD_FOLDER'], 'default.png')
    else:
        if os.path.isfile(app.config['UPLOAD_FOLDER'] + imagename):
            return send_from_directory(app.config['UPLOAD_FOLDER'], imagename)
        else:
            raise exc.EntryNotFound()
Exemple #18
0
def update_tag(admin, id):
    """
    Update the tag with the given id.

    :param admin:                Is the administrator user, determined by
                                 @adminRequired.
    :param id:                   Is the tag id.

    :return:                     A message that the update was
                                 successful and a list of all updated fields.

    :raises EntryNotFound:       If the tag with this ID does not exist.
    :raises ForbiddenField:      If a forbidden field is in the request data.
    :raises UnknownField:        If an unknown parameter exists in the request
                                 data.
    :raises InvalidType:         If one or more parameters have an invalid type.
    :raises CouldNotUpdateEntry: If any other error occurs.
    """
    data = json_body()

    # Check, if the product exists.
    tag = Tag.query.filter_by(id=id).first()
    if not tag:
        raise exc.EntryNotFound()

    updateable = {'name': str}

    # Check forbidden fields
    check_forbidden(data, updateable, tag)
    # Check types
    check_fields_and_types(data, None, updateable)

    updated_fields = update_fields(data, tag)
    tag.created_by = admin.id

    # Apply changes
    try:
        db.session.commit()
    except IntegrityError:
        raise exc.CouldNotUpdateEntry()

    return jsonify({
        'message': 'Updated tag.',
        'updated_fields': updated_fields
    }), 201
Exemple #19
0
def create_refund(admin):
    """
    Insert a new refund.

    :param admin:                Is the administrator user, determined by @adminRequired.

    :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 InvalidAmount:       If amount is equal to zero.
    :raises CouldNotCreateEntry: If any other error occurs.
    """
    data = json_body()
    required = {'user_id': int, 'total_price': int, 'comment': str}
    check_fields_and_types(data, required)

    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 amount
    if data['total_price'] <= 0:
        raise exc.InvalidAmount()

    # Create and insert refund
    try:
        refund = Refund(**data)
        refund.admin_id = admin.id
        db.session.add(refund)
        db.session.commit()
    except IntegrityError:
        raise exc.CouldNotCreateEntry()

    return jsonify({'message': 'Created refund.'}), 200
Exemple #20
0
def get_purchase(purchase_id):
    """
    Returns the purchase with the requested id.

    :param purchase_id:    Is the purchase id.

    :return:               The requested purchase as JSON object.

    :raises EntryNotFound: If the purchase with this ID does not exist.
    """
    purchase = Purchase.query.filter_by(id=purchase_id).first()
    if not purchase:
        raise exc.EntryNotFound()
    fields = [
        'id', 'timestamp', 'user_id', 'admin_id', 'product_id', 'amount',
        'price', 'productprice', 'revoked', 'revokehistory'
    ]
    return jsonify(convert_minimal(purchase, fields)[0]), 200
Exemple #21
0
def get_turnover(id):
    """
    Returns the turnover with the requested id.

    :param id:             Is the turnover id.

    :return:               The requested turnover as JSON object.

    :raises EntryNotFound: If the turnover with this ID does not exist.
    """
    # Query the turnover
    res = Turnover.query.filter_by(id=id).first()
    # If it not exists, return an error
    if not res:
        raise exc.EntryNotFound()
    # Convert the turnover to a JSON friendly format
    fields = [
        'id', 'timestamp', 'amount', 'comment', 'revoked', 'revokehistory'
    ]
    return jsonify(convert_minimal(res, fields)[0]), 200
Exemple #22
0
def get_product_pricehistory(admin, product_id):
    """
    Returns the pricehistory of the product with the given id. If only want to
    query a part of the history in a range there are optional request arguments:
    - start_date:          Is the unix timestamp of the start date.
    - end_date:            Is the unix timestamp of the end date.

    :param admin:          Is the administrator user, determined by
                           @adminRequired.
    :param product_id:     Is the product id.

    :raises EntryNotFound: If the product does not exist.
    :raises WrongType:     If the request args are invalid.

    :return:               The pricehistory of the product.
    """

    # Check, whether the product exists.
    product = Product.query.filter(Product.id == product_id).first()
    if not product:
        raise exc.EntryNotFound()

    # Get the (optional) time range parameters
    try:
        start = request.args.get('start_date')
        if start:
            start = int(start)
        end = request.args.get('end_date')
        if end:
            end = int(end)
    except (TypeError, ValueError):
        raise exc.WrongType()

    # Check whether start lies before end date
    if start and end:
        if not start <= end:
            raise exc.InvalidData()

    history = product.get_pricehistory(start, end)

    return jsonify(history), 200
Exemple #23
0
def get_stocktakingcollection_template():
    """
    This route can be used to retrieve a template to print out for a
    stocktaking. It lists all the products that must be included in the stocktaking.

    :return:      A rendered PDF file with all products for the stocktaking.
    """
    # Check if pdfkit is available
    if pdfkit is None:
        return jsonify({'message': 'Not available'}), 503

    # Get a list of all products.
    products = (Product.query.filter(Product.active.is_(True)).filter(
        Product.countable.is_(True)).order_by(func.lower(Product.name)).all())

    # If no products exist that are active and countable, an exception must be
    # made.
    if not products:
        raise exc.EntryNotFound()

    rows = []
    for product in products:
        product_name = product.name
        theoretical_stock = product_helpers.get_theoretical_stock_of_product(
            product.id)
        rows.append({
            'product_name': product_name,
            'theoretical_stock': theoretical_stock
        })

    # Render the template
    rendered = render_template('stocktakingcollections_template.html',
                               rows=rows)
    # Create a PDF file from the rendered template.
    pdf = pdfkit.from_string(rendered, False)
    response = make_response(pdf)
    response.headers['Content-Type'] = 'application/pdf'
    response.headers['Content-Disposition'] = 'inline; filename=output.pdf'

    # Return the PDF file.
    return response
Exemple #24
0
def get_payoff(admin, id):
    """
    Returns the payoff with the requested id.

    :param admin:          Is the administrator user, determined by
                           @adminRequired.
    :param id:             Is the payoff id.

    :return:               The requested payoff as JSON object.

    :raises EntryNotFound: If the payoff with this ID does not exist.
    """
    # Query the payoff
    res = Payoff.query.filter_by(id=id).first()
    # If it not exists, return an error
    if not res:
        raise exc.EntryNotFound()
    # Convert the payoff to a JSON friendly format
    fields = [
        'id', 'timestamp', 'amount', 'comment', 'revoked', 'revokehistory'
    ]
    return jsonify(convert_minimal(res, fields)[0]), 200
Exemple #25
0
def insert_deposit(data, admin):
    """
    This help function creates a new deposit with the given data.

    :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 InvalidAmount:       If amount is equal to zero.
    :raises CouldNotCreateEntry: If any other error occurs.
    """

    required = {'user_id': int, 'amount': int, 'comment': str}
    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 amount
    if data['amount'] == 0:
        raise exc.InvalidAmount()

    # Create and insert deposit
    try:
        deposit = Deposit(**data)
        deposit.admin_id = admin.id
        db.session.add(deposit)
    except IntegrityError:
        raise exc.CouldNotCreateEntry()
Exemple #26
0
def get_product(admin, product_id):
    """
    Returns the product with the requested id.

    :param admin:               Is the administrator user, determined by
                                @adminOptional.
    :param product_id:          Is the product id.

    :return:                    The requested product as JSON object.

    :raises EntryNotFound:      If the product with this ID does not exist.
    :raises UnauthorizedAccess: If the product is inactive and the request
                                does not come from an administrator.
    """
    product = Product.query.filter(Product.id == product_id).first()
    if not product:
        raise exc.EntryNotFound()

    if not product.active and not admin:
        fields = [
            'id', 'name', 'barcode', 'active', 'imagename', 'tags',
            'creation_date'
        ]
    else:
        fields = [
            'id', 'name', 'price', 'barcode', 'active', 'countable',
            'purchase_sum', 'replenishment_sum', 'balance_score', 'revocable',
            'imagename', 'tags', 'creation_date'
        ]

    # Convert the product to a dictionary
    product = convert_minimal(product, fields)[0]

    # Convert the product tags
    product['tags'] = [t.id for t in product['tags']]

    return jsonify(product), 200
Exemple #27
0
def create_replenishmentcollection(admin):
    """
    Insert a new replenishmentcollection.

    :param admin:                Is the administrator user, determined by
                                 @adminRequired.

    :return:                     A message that the creation was successful.

    :raises DataIsMissing:       If not all required data is available.
    :raises ForbiddenField :     If a forbidden field is in the data.
    :raises WrongType:           If one or more data is of the wrong type.
    :raises EntryNotFound:       If the product with with the id of any
                                 replenishment does not exist.
    :raises InvalidAmount:       If amount of any replenishment is less than
                                 or equal to zero.
    :raises CouldNotCreateEntry: If any other error occurs.
    """
    data = json_body()
    required = {'replenishments': list, 'comment': str}
    required_repl = {'product_id': int, 'amount': int, 'total_price': int}

    # Check all required fields
    check_fields_and_types(data, required)

    replenishments = data['replenishments']
    # Check for the replenishments in the collection
    if not replenishments:
        raise exc.DataIsMissing()

    for repl in replenishments:

        # Check all required fields
        check_fields_and_types(repl, required_repl)

        product_id = repl.get('product_id')
        amount = repl.get('amount')

        # Check amount
        if amount <= 0:
            raise exc.InvalidAmount()
        # Check product
        product = Product.query.filter_by(id=product_id).first()
        if not product:
            raise exc.EntryNotFound()

        # If the product has been marked as inactive, it will now be marked as
        # active again.
        if not product.active:
            product.active = True

    # Create and insert replenishmentcollection
    try:
        collection = ReplenishmentCollection(admin_id=admin.id,
                                             comment=data['comment'],
                                             revoked=False)
        db.session.add(collection)
        db.session.flush()

        for repl in replenishments:
            rep = Replenishment(replcoll_id=collection.id, **repl)
            db.session.add(rep)
        db.session.commit()

    except IntegrityError:
        raise exc.CouldNotCreateEntry()

    return jsonify({'message': 'Created replenishmentcollection.'}), 201
Exemple #28
0
def update_replenishment(admin, id):
    """
    Update the replenishment with the given id.

    :param admin:                Is the administrator user, determined by
                                 @adminRequired.
    :param id:                   Is the replenishment id.

    :return:                     A message that the update was successful
                                 and a list of all updated fields.

    :raises EntryNotFound:       If the replenishment with this ID does not
                                 exist.
    :raises ForbiddenField:      If a forbidden field is in the request data.
    :raises UnknownField:        If an unknown parameter exists in the
                                 request data.
    :raises InvalidType:         If one or more parameters have an invalid
                                 type.
    :raises NothingHasChanged:   If no change occurred after the update.
    :raises CouldNotUpdateEntry: If any other error occurs.
    """
    # Check Replenishment
    repl = Replenishment.query.filter_by(id=id).first()
    if not repl:
        raise exc.EntryNotFound()

    # Get the corresponding ReplenishmentCollection
    replcoll = (ReplenishmentCollection.query.filter_by(
        id=repl.replcoll_id).first())
    # Get all not revoked replenishments corresponding to the
    # replenishmentcollection before changes are made
    repls_nr = replcoll.replenishments.filter_by(revoked=False).all()

    # Data validation
    data = json_body()
    updateable = {'revoked': bool, 'amount': int, 'total_price': int}
    check_forbidden(data, updateable, repl)
    check_fields_and_types(data, None, updateable)

    updated_fields = []
    message = 'Updated replenishment.'

    # Handle replenishment revoke
    if 'revoked' in data:
        if repl.revoked == data['revoked']:
            raise exc.NothingHasChanged()
        if not data['revoked'] and not repls_nr:
            replcoll.toggle_revoke(revoked=False, admin_id=admin.id)
            message = message + (
                ' Rerevoked ReplenishmentCollection ID: {}'.format(
                    replcoll.id))
        repl.toggle_revoke(revoked=data['revoked'], admin_id=admin.id)
        del data['revoked']
        updated_fields.append('revoked')

    # Handle all other fields
    updated_fields = update_fields(data, repl, updated_fields)

    # Check if ReplenishmentCollection still has unrevoked Replenishments
    repls = replcoll.replenishments.filter_by(revoked=False).all()
    if not repls and not replcoll.revoked:
        message = message + (' Revoked ReplenishmentCollection ID: {}'.format(
            replcoll.id))
        replcoll.toggle_revoke(revoked=True, admin_id=admin.id)

    # Apply changes
    try:
        db.session.commit()
    except IntegrityError:
        raise exc.CouldNotUpdateEntry()

    return jsonify({'message': message, 'updated_fields': updated_fields}), 201
Exemple #29
0
def update_replenishmentcollection(admin, id):
    """
    Update the replenishmentcollection with the given id.

    :param admin:                Is the administrator user, determined by
                                 @adminRequired.
    :param id:                   Is the replenishmentcollection id.

    :return:                     A message that the update was successful.

    :raises EntryNotFound:       If the replenishmentcollection with this ID
                                 does not exist.
    :raises ForbiddenField:      If a forbidden field is in the request data.
    :raises UnknownField:        If an unknown parameter exists in the request
                                 data.
    :raises InvalidType:         If one or more parameters have an invalid type.
    :raises NothingHasChanged:   If no change occurred after the update.
    :raises CouldNotUpdateEntry: If any other error occurs.
    :raises EntryNotRevocable:   If the replenishmentcollections was revoked by
                                 by replenishment_update, because all
                                 replenishments are revoked, the revoked field
                                 can not be set to true.
    """
    # Check ReplenishmentCollection
    replcoll = (ReplenishmentCollection.query.filter_by(id=id).first())
    if not replcoll:
        raise exc.EntryNotFound()
    # Which replenishments are not revoked?
    repls = replcoll.replenishments.filter_by(revoked=False).all()

    data = json_body()

    if data == {}:
        raise exc.NothingHasChanged()

    updateable = {'revoked': bool, 'comment': str, 'timestamp': int}
    check_forbidden(data, updateable, replcoll)
    check_fields_and_types(data, None, updateable)

    updated_fields = []
    # Handle replenishmentcollection revoke
    if 'revoked' in data:
        if replcoll.revoked == data['revoked']:
            raise exc.NothingHasChanged()
        # Check if the revoke was caused through the replenishment_update and
        # therefor cant be changed
        if not data['revoked'] and not repls:
            raise exc.EntryNotRevocable()
        replcoll.toggle_revoke(revoked=data['revoked'], admin_id=admin.id)
        del data['revoked']
        updated_fields.append('revoked')

    # Handle new timestamp
    if 'timestamp' in data:
        try:
            timestamp = datetime.datetime.fromtimestamp(data['timestamp'])
            assert timestamp <= datetime.datetime.now()
            replcoll.timestamp = timestamp
            updated_fields.append('revoked')
        except (AssertionError, TypeError, ValueError, OSError, OverflowError):
            """
            AssertionError: The timestamp lies in the future.
            TypeError:      Invalid type for conversion.
            ValueError:     Timestamp is out of valid range.
            OSError:        Value exceeds the data type.
            OverflowError:  Timestamp out of range for platform time_t.
            """
            raise exc.InvalidData()
        del data['timestamp']

    # Handle all other fields
    updated_fields = update_fields(data, replcoll, updated_fields)

    # Apply changes
    try:
        db.session.commit()
    except IntegrityError:
        raise exc.CouldNotUpdateEntry()

    return jsonify({
        'message': 'Updated replenishmentcollection.',
        'updated_fields': updated_fields
    }), 201
Exemple #30
0
def create_stocktakingcollections(admin):
    """
    Insert a new stocktakingcollection.

    :param admin:                Is the administrator user, determined by
                                 @adminRequired.

    :return:                     A message that the creation was successful.

    :raises DataIsMissing:       If not all required data is available.
    :raises ForbiddenField :     If a forbidden field is in the data.
    :raises WrongType:           If one or more data is of the wrong type.
    :raises EntryNotFound:       If the product with with the id of any
                                 replenishment does not exist.
    :raises InvalidAmount:       If amount of any replenishment is less than
                                 or equal to zero.
    :raises CouldNotCreateEntry: If any other error occurs.
    """
    data = json_body()
    required = {'stocktakings': list, 'timestamp': int}
    required_s = {'product_id': int, 'count': int}
    optional_s = {'keep_active': bool}

    # Check all required fields
    check_fields_and_types(data, required)

    stocktakings = data['stocktakings']
    # Check for stocktakings in the collection
    if not stocktakings:
        raise exc.DataIsMissing()

    for stocktaking in stocktakings:
        product_id = stocktaking.get('product_id')
        product = Product.query.filter_by(id=product_id).first()
        if not product:
            raise exc.EntryNotFound()
        if not product.countable:
            raise exc.InvalidData()

    # Get all active product ids
    products = (Product.query.filter(Product.active.is_(True)).filter(
        Product.countable.is_(True)).all())
    active_ids = list(map(lambda p: p.id, products))
    data_product_ids = list(map(lambda d: d['product_id'], stocktakings))

    # Compare function
    def compare(x, y):
        return collections.Counter(x) == collections.Counter(y)

    # We need an entry for all active products. If some data is missing,
    # raise an exception
    if not compare(active_ids, data_product_ids):
        raise exc.DataIsMissing()

    # Check the timestamp
    try:
        timestamp = datetime.datetime.fromtimestamp(data['timestamp'])
        assert timestamp <= datetime.datetime.now()
    except (AssertionError, TypeError, ValueError, OSError, OverflowError):
        # AssertionError: The timestamp is after the current time.
        # TypeError:      Invalid type for conversion.
        # ValueError:     Timestamp is out of valid range.
        # OSError:        Value exceeds the data type.
        # OverflowError:  Timestamp out of range for platform time_t.
        raise exc.InvalidData()
    # Create stocktakingcollection
    collection = StocktakingCollection(admin_id=admin.id, timestamp=timestamp)
    db.session.add(collection)
    db.session.flush()

    # Check for all required data and types
    for stocktaking in stocktakings:

        # Check all required fields
        check_fields_and_types(stocktaking, required_s, optional_s)

        # Get all fields
        product_id = stocktaking.get('product_id')
        count = stocktaking.get('count')
        keep_active = stocktaking.get('keep_active', False)

        # Check amount
        if count < 0:
            raise exc.InvalidAmount()

        # Does the product changes its active state?
        product = Product.query.filter_by(id=product_id).first()
        if count == 0 and keep_active is False:
            product.active = False

    # Create and insert stocktakingcollection
    try:
        for stocktaking in stocktakings:
            s = Stocktaking(collection_id=collection.id,
                            product_id=stocktaking.get('product_id'),
                            count=stocktaking.get('count'))
            db.session.add(s)
        db.session.commit()

    except IntegrityError:
        raise exc.CouldNotCreateEntry()

    return jsonify({'message': 'Created stocktakingcollection.'}), 201