Exemple #1
0
def reprocess_queued_donors(payload=None):
    """Reprocess existing queued donors."""

    if payload:
        queued_donors = QueuedDonorModel.query.filter(
            QueuedDonorModel.id.in_(payload['queued_donor_ids']))
    else:
        queued_donors = QueuedDonorModel.query.all()

    jobs = []
    for queued_donor_model in queued_donors:
        queued_donor_dict = to_json(QueuedDonorSchema(),
                                    queued_donor_model).data
        queued_donor_dict['gift_id'] = queued_donor_model.gift_id
        queued_donor_dict['queued_donor_id'] = queued_donor_model.id
        queued_donor_dict['category'] = 'queued'
        queued_donor_dict.pop('id')

        # May be multiple transactions for a gift, e.g. check with a Gift and Deposit to Bank.
        transaction_models = TransactionModel.query.filter_by(
            gift_id=queued_donor_model.gift_id).all()
        transactions = []
        for transaction_model in transaction_models:
            if transaction_model.type in ['Gift', 'Deposit to Bank']:
                transaction_dict = to_json(TransactionSchema(),
                                           transaction_model).data
                transactions.append(transaction_dict)

        # Caging expects a user dictionary that has a user something like: { user_address:{}, 'billing_address':{} }.
        # Put the queued donor dictionary in this form.
        queued_donor_dict = validate_user_payload(queued_donor_dict)

        # Once on the queue it is out of our hands, but may fail on arguments to queue().
        try:
            job = redis_queue_caging.queue(queued_donor_dict, transactions,
                                           current_app.config['ENV'])
            jobs.append((queued_donor_dict['queued_donor_id'], job.get_id()))
        except:
            jobs.append((queued_donor_dict['queued_donor_id'], 'failed'))

    response = None
    if jobs:
        response = {'reprocessed_jobs': jobs}

    return response
Exemple #2
0
def resolve_user(gift_with_customer_id):
    """If the user is caged or queued then return the model.

    :param gift_with_customer_id: The gift.
    :return: The caged/queued donor models ( at least one will be None ).
    """
    donor_model = None
    try:
        if gift_with_customer_id.user_id == -1:
            caged_donor_model = CagedDonorModel.query.filter_by(
                gift_id=gift_with_customer_id.id).one()
            caged_donor_dict = to_json(CagedDonorSchema(), caged_donor_model)
            donor_model = from_json(CagedDonorSchema(), caged_donor_dict.data)
        if gift_with_customer_id.user_id == -2:
            queued_donor_model = QueuedDonorModel.query.filter_by(
                gift_id=gift_with_customer_id.id).one()
            queued_donor_dict = to_json(QueuedDonorSchema(),
                                        queued_donor_model)
            donor_model = from_json(QueuedDonorSchema(),
                                    queued_donor_dict.data)
        return donor_model
    except:  # noqa: E722
        raise BraintreeWebhooksIDPathError(
            type_id=gift_with_customer_id.customer_id)
def get_ultsys_user(search_terms):
    """The mocked function helpers.ultsys_user.get_ultsys_user.

    The function get_ultsys_user() makes a request.get() that returns user data based on search terms. The mocked
    function accepts only the search operator 'eq' and does not do chained searches. The argument to the function
    is a dictionary called search_terms:

    "search_terms": {
            "id": {"eq": "3239868"}
    }

    In the integration/unit tests the mocked functionsimple search for either last name, ID, or UID.

    :param search_terms:
    :return: A list of mocked ultsys user dictionaries matching the search criteria.
    """

    attribute = list(search_terms.copy().keys())[0]
    attribute_value = search_terms[attribute]['eq']

    # The mock function doesn't have to handle the complete functionality of the endpoint.
    # Currently for sales it needs to handle lastname for caging, ID and UID for other cases.
    users_model_data = []
    if attribute == 'lastname':
        users_model_data = UltsysUserModel.query.filter_by(
            lastname=attribute_value).all()
    elif attribute == 'ID':
        users_model_data = UltsysUserModel.query.filter_by(
            ID=attribute_value).all()
    if attribute == 'uid':
        users_model_data = UltsysUserModel.query.filter_by(
            uid=attribute_value).all()

    users_dict_data = []
    for user_model in users_model_data:
        user_dict = to_json(UltsysUserSchema(), user_model)
        users_dict_data.append(user_dict.data)

    request = Request(users_dict_data)
    return request
Exemple #4
0
def handle_thank_you_letter_logic(searchable_ids, enacted_by_agent_id):
    """The code to handle the model for a thank you letter being sent.

    We have from the front-end the following searchable_ids:

    searchable_ids = [ searchable_id_1, searchable_id_2, ... ] or possible and empty list ( [] 0.

    When thank you letters are sent:
      1. Get the agent ID using the Ultsys ID from the JWT in the resource.
      2. Build out gift ID's and user for thank you letters.
      3. Create new transactions with type: Thank You Sent

      Return to resource where thank you is emailed.

    :param searchable_ids: The gift searchable IDs to build thank yous for.
    :param enacted_by_agent_id: The agent processing the batch of thank yous.
    :return: The thank you dictionaries and the URL to the CSV.
    """

    thank_you_dicts = []
    transaction_models = []

    if not searchable_ids:
        searchable_ids_tmp = database.session.query( GiftModel.searchable_id )\
            .join( GiftThankYouLetterModel, GiftModel.id == GiftThankYouLetterModel.gift_id ).all()
        searchable_ids = [
            str(searchable_id[0].hex.upper())
            for searchable_id in searchable_ids_tmp
        ]

    # 2. New transaction with Thank You Sent type
    gift_searchable_ids, gift_ids = build_out_gift_ids(searchable_ids)
    user_data = build_out_user_data(searchable_ids)

    for searchable_id in searchable_ids:
        thank_you_dict = {}
        transaction_model = TransactionModel(
            gift_id=gift_searchable_ids[searchable_id]['gift_id'],
            date_in_utc=datetime.utcnow(),
            enacted_by_agent_id=enacted_by_agent_id,
            type=gift_searchable_ids[searchable_id]['type'],
            status='Thank You Sent',
            reference_number=gift_searchable_ids[searchable_id]
            ['reference_number'],
            gross_gift_amount=gift_searchable_ids[searchable_id]
            ['gross_gift_amount'],
            fee=Decimal(0.00),
            notes='Thank you email sent.')
        transaction_models.append(transaction_model)
        thank_you_dict['transaction'] = to_json(TransactionSchema(),
                                                transaction_model).data
        thank_you_dict['transaction']['gift_id'] = gift_searchable_ids[
            searchable_id]['gift_id']

        thank_you_dict['gift'] = gift_searchable_ids[searchable_id]
        thank_you_dict['user'] = user_data[searchable_id]
        thank_you_dicts.append(thank_you_dict)
    try:
        GiftThankYouLetterModel.query\
            .filter( GiftThankYouLetterModel.gift_id.in_( gift_ids ) )\
            .delete( synchronize_session='fetch' )
        database.session.add_all(transaction_models)
        database.session.flush()
    except SQLAlchemyError as error:
        database.session.rollback()
        raise error

    try:
        database.session.commit()
    except SQLAlchemyError as error:
        database.session.rollback()
        raise error

    # 3. Create a CSV of the results.
    url = build_thank_you_letters_csv(thank_you_dicts)

    return thank_you_dicts, url
Exemple #5
0
def ultsys_user_create( payload ):
    """Function to create an Ultsys user from a caged donor.

    payload = {
        "caged_donor_id": 1234,
        "ultsys_user_id": Null,
        "user_first_name": "Ralph",
        "user_last_name": "Kramden",
        "user_address": "328 Chauncey St",
        "user_state": "NY",
        "user_city": "Bensonhurst",
        "user_zipcode": "11214",
        "user_email_address": "*****@*****.**",
        "user_phone_number": "9172307441"
    }

    :param dict payload: The required payload
    :return: Ultsys user.
    """

    # Find the gift ID from the caged donor.
    caged_donor_model = CagedDonorModel.query.filter_by( id=payload[ 'caged_donor_id' ] ).one_or_none()
    if not caged_donor_model:
        raise ModelCagedDonorNotFoundError

    caged_donor_json = to_json( CagedDonorSchema(), caged_donor_model ).data
    caged_donor_json.pop( 'id' )

    # Retrieve the completed gift from the transactions to get the gross_gift_amount.
    gift = GiftModel.query.filter_by( id=caged_donor_model.gift_id ).one_or_none()
    if not gift:
        raise ModelGiftNotFoundError

    # The gift will have at least one transaction and may also have multiple transactions.
    # Get the most recent transaction which holds the current gross gift amount.
    transaction = TransactionModel.query.filter_by( gift_id=caged_donor_model.gift_id )\
        .order_by( TransactionModel.date_in_utc )\
        .first()
    if not transaction:
        raise ModelTransactionNotFoundError
    gross_gift_amount = transaction.gross_gift_amount

    # The updating of the caged donor fields does not expect the 2 IDs in the dictionary.

    # Build the payload for the Drupal user.
    caged_donor = {
        'action': 'create',
        'firstname': caged_donor_json[ 'user_first_name' ],
        'lastname': caged_donor_json[ 'user_last_name' ],
        'zip': caged_donor_json[ 'user_zipcode' ],
        'city': caged_donor_json[ 'user_city' ],
        'state': caged_donor_json[ 'user_state' ],
        'email': caged_donor_json[ 'user_email_address' ],
        'phone': str( caged_donor_json[ 'user_phone_number' ] )
    }

    drupal_user_uid = create_user( caged_donor )

    # Use the Drupal ID to retrieve the Ultsys user.
    ultsys_user = find_ultsys_user( get_ultsys_user_query( { 'drupal_user_uid': drupal_user_uid } ) )
    ultsys_user_id = ultsys_user[ 0 ][ 'ID' ]

    # Update the gift with the new Ultsys user ID and the Ultsys user with the gross gift amount.
    gift.user_id = ultsys_user_id
    update_ultsys_user( { 'id': ultsys_user_id }, gross_gift_amount )

    database.session.delete( caged_donor_model )
    database.session.commit()

    return ultsys_user[ 0 ]
Exemple #6
0
def void_transaction(payload):
    """A function for voiding a Braintree transaction on a gift.

    Find the transaction in the database and get the Braintree transaction number. Configure the Braintree API and
    void the transaction through Braintree.

    payload = {
        "transaction_id": 1,
        "user_id": "1234",
        "transaction_notes": "Some transaction notes."
    }

    :param dict payload: A dictionary that provides information to void the transaction.
    :return:
    """

    # Retrieve the transaction that is to be voided.
    try:
        transaction_model = TransactionModel.query.filter_by(
            id=payload['transaction_id']).one()
    except:
        raise AdminTransactionModelPathError('voided')

    try:
        braintree_id = transaction_model.reference_number
        # Need dictionary for schema, and schema.load will not include the gift_id by design.
        transaction_data = to_json(TransactionSchema(), transaction_model)
        transaction_json = transaction_data[0]
        transaction_json['gift_id'] = transaction_model.gift_id
        transaction_json['notes'] = payload['transaction_notes']
    except:
        raise AdminBuildModelsPathError()

    # Generate Braintree void, where make_braintree_void() returns a Braintree voided transaction.
    init_braintree_credentials(current_app)

    transaction_void = make_braintree_void(braintree_id)

    # Need to attach the user who is doing the void.
    enacted_by_agent = AgentModel.get_agent('Staff Member', 'user_id',
                                            payload['user_id'])
    transaction_json['enacted_by_agent_id'] = enacted_by_agent.id

    try:
        # Use BraintreeSaleSchema to populate gift and transaction dictionaries.
        braintree_schema = BraintreeSaleSchema()
        braintree_schema.context = {
            'gift': {},
            'transaction': transaction_json
        }
        braintree_sale = braintree_schema.dump(transaction_void.transaction)
        transaction_json = braintree_sale.data['transaction']

        gift_model = GiftModel.query.get(transaction_json['gift_id'])
        gross_amount = gift_model.transactions[0].gross_gift_amount
        transaction_json['gross_gift_amount'] += gross_amount

        transaction_void_model = from_json(TransactionSchema(),
                                           transaction_json)
        database.session.add(transaction_void_model.data)
        database.session.commit()
        database.session.flush()
        transaction_json['id'] = transaction_void_model.data.id
    except:
        raise AdminBuildModelsPathError()

    return transaction_json
def refund_transaction(payload):
    """A function for refunding a Braintree transaction on a gift.

    Find the transaction in the database and get the Braintree transaction number. Configure the Braintree API and
    make the refund through Braintree.

    payload = {
        "transaction_id": 1,
        "amount": "0.01",
        "transaction_notes": "Some transaction notes."
    }

    :param dict payload: A dictionary that provides information to make the refund.
    :return:
    :raises MarshmallowValidationError: If Marshmallow throws a validation error.
    :raises SQLAlchemyError: General SQLAlchemy error.
    :raises SQLAlchemyORMNoResultFoundError: The ORM didn't find the table row.
    """

    # Retrieve the transaction that is to be refunded and raise an exception if not found.
    # Don't try to continue to Braintree without a valid reference number.
    try:
        transaction_model = TransactionModel.query.filter_by(
            id=payload['transaction_id']).one()
    except:
        raise AdminTransactionModelPathError(where='parent')

    transactions = TransactionModel.query.filter_by(
        gift_id=transaction_model.gift_id).all()
    current_balance = transactions[-1].gross_gift_amount

    # If the model cannot be built do not continue to Braintree.
    # Raise an exception.
    try:
        # Build transaction dictionary for schema.
        braintree_id = transaction_model.reference_number
        transaction_data = to_json(TransactionSchema(), transaction_model)
        transaction_json = transaction_data.data
        transaction_json['gift_id'] = transaction_model.gift_id
        transaction_json['notes'] = payload['transaction_notes']
    except:
        raise AdminBuildModelsPathError()

    # Configure Braintree so we can generate a refund.
    init_braintree_credentials(current_app)

    # Function make_braintree_refund() returns: a Braintree refund transaction.
    transaction_refund = make_braintree_refund(braintree_id, payload['amount'],
                                               current_balance)

    # Need to attach the user who is doing the reallocation.
    enacted_by_agent = AgentModel.get_agent('Staff Member', 'user_id',
                                            payload['user_id'])
    transaction_json['enacted_by_agent_id'] = enacted_by_agent.id

    try:
        # Use BraintreeSaleSchema to populate gift and transaction dictionaries.
        braintree_schema = BraintreeSaleSchema()
        braintree_schema.context = {
            'gift': {},
            'transaction': transaction_json
        }
        braintree_sale = braintree_schema.dump(transaction_refund.transaction)
        transaction_json = braintree_sale.data['transaction']
        transaction_json.pop('id')

        gift_model = GiftModel.query.get(transaction_json['gift_id'])
        gross_amount = gift_model.transactions[0].gross_gift_amount
        transaction_json['gross_gift_amount'] += gross_amount

        transaction_refund_model = from_json(TransactionSchema(),
                                             transaction_json)
        database.session.add(transaction_refund_model.data)
        database.session.commit()
        database.session.flush()
        transaction_json['id'] = transaction_refund_model.data.id
    except:
        raise AdminBuildModelsPathError()

    return transaction_json