def get_min_distance_house_travel_distance(task: ExternalTask) -> TaskResult: """ Gets the distance between the user's house and a travel company. :param task: the current task instance :return: the task result """ logger = get_logger() logger.info("get_min_distance_house_travel_distance") GEOGRAPHICAL_DISTACE_SERVICE = environ.get( "GEOGRAPHICAL_DISTACE_SERVICE", "http://geographical_distances:8080") travel_company = str(task.get_variable("travel_company")) travel_company_url = travel_company.split(';')[0] travel_company_address = travel_company.split(';')[1] offer_purchase_data = OfferPurchaseData.from_dict( json.loads(task.get_variable("offer_purchase_data"))) distances = json.loads(str(task.get_variable("distances"))) request = { "address_1": travel_company_address, "address_2": str(offer_purchase_data.address) } distance_request = requests.post(GEOGRAPHICAL_DISTACE_SERVICE + "/distance", json=request) distances.get("distances").append({ "company": travel_company_url, "distance": distance_request.text }) return task.complete(global_variables={"distances": json.dumps(distances)})
def rehabilitation_offer_code(task: ExternalTask) -> TaskResult: """ Compensation task: if the verification offer code and payment sub process fail, this task rehabilitates the offer code for another try. :param task: the current task instance :return: the task result """ logger = get_logger() logger.info("rehabilitation_offer_code") offer_purchase_data = OfferPurchaseData.from_dict( json.loads(task.get_variable("offer_purchase_data"))) offer_code = offer_purchase_data.offer_code # Connects to postgreSQL Session = sessionmaker(bind=create_sql_engine()) session = Session() """ Gets the offer match that has to be rehabilitated from the DB. The offer match is the one blocked and with offer_code equal to the process offer_code variable. """ affected_rows = session.query(OfferMatch).filter( OfferMatch.offer_code == offer_code).update( {"blocked": False}, synchronize_session="fetch") if affected_rows < 1: session.rollback() logger.error( f"{affected_rows} matches were found for the given offer code. The offer code will not be rehabilitated." ) return task.complete() logger.info(f"{affected_rows} match was found for the given offer code.") session.commit() return task.complete()
def verify_offer_code_validity(task: ExternalTask) -> TaskResult: """ Verifies that the offer code is valid, not expired and not already in use by another user. :param task: the current task instance :return: the task result """ logger = get_logger() logger.info("verify_offer_code_validity") offer_purchase_data = OfferPurchaseData.from_dict( json.loads(task.get_variable("offer_purchase_data"))) offer_code = offer_purchase_data.offer_code # Connects to PostgreSQL Session = sessionmaker(bind=create_sql_engine()) session = Session() user_communication_code = str(hash(offer_purchase_data)) # Checks if the offer matched is blocked matches = session.query(OfferMatch).filter( OfferMatch.offer_code == offer_code, OfferMatch.blocked == True).all() if len(matches) == 1: logger.error(f"Offer code is BLOCKED.") return task.complete( global_variables={ 'offer_code_validity': False, 'user_communication_code': user_communication_code }) # Checks if the offer match is not expired and sets it to blocked=True. affected_rows = session.query(OfferMatch).filter( OfferMatch.offer_code == offer_code, OfferMatch.creation_date >= datetime.datetime.now(tz=datetime.timezone.utc) - datetime.timedelta(hours=24)).update({"blocked": True}, synchronize_session="fetch") if affected_rows < 1: session.rollback() logger.error( f"{affected_rows} matches were found for the given offer code.") return task.complete( global_variables={ 'offer_code_validity': False, 'user_communication_code': user_communication_code }) logger.info(f"{affected_rows} match was found for the given offer code.") session.commit() return task.complete( global_variables={ 'offer_code_validity': True, 'user_communication_code': user_communication_code })
def book_transfer(task: ExternalTask) -> TaskResult: """ Contacts the chosen Travel Company and requests to book a travel using the SOAP protocol :param task: the current task instance :return: the task result """ logger = get_logger() logger.info("get_min_distance_house_travel_distance") distances = json.loads(str(task.get_variable("distances"))) distances = distances.get("distances") offer_purchase_data = OfferPurchaseData.from_dict( json.loads(task.get_variable("offer_purchase_data"))) tickets = json.loads(str(task.get_variable("tickets"))) # Connects to PostgreSQL to get the offer match information Session = sessionmaker(bind=create_sql_engine()) session = Session() offer_match: OfferMatch = session.query(OfferMatch).get( {"offer_code": offer_purchase_data.offer_code}) # Identifies the Travel Company to contact, choosing the one nearer to the user's address. travel_company_to_contact = min(distances, key=lambda tc: tc.get("distance")) # Creates the SOAP Client and the datetime when then transfer will be booked for. # We need to replace the port for accessing the web server serving the WSDL interface. wsdl_url = travel_company_to_contact.get("company").replace( ":8080", ":8000") + "/travel_company.wsdl" soap_client = Client(wsdl=wsdl_url) outbound_departure_transfer_datetime = offer_match.outbound_flight.departure_datetime - timedelta( hours=4) comeback_arrival_transfer_datetime = offer_match.comeback_flight.arrival_datetime + timedelta( minutes=10) try: soap_response = soap_client.service.buyTransfers( departure_transfer_datetime=outbound_departure_transfer_datetime. strftime("%Y-%m-%dT%H:%M:%S"), customer_address=str(offer_purchase_data.address), airport_code=offer_match.outbound_flight.departure_airport_code, customer_name= f"{offer_purchase_data.name} {offer_purchase_data.surname}", arrival_transfer_datetime=comeback_arrival_transfer_datetime. strftime("%Y-%m-%dT%H:%M:%S")) tickets["transfers"] = [soap_response] return task.complete(global_variables={"tickets": json.dumps(tickets)}) except Fault: return task.failure("Book ticket", "Failure in booking ticket from travel company", max_retries=5, retry_timeout=10)
def check_distance_house_airport(task: ExternalTask) -> TaskResult: """ Checks if the distance (that we got when contacting the Geographical Distance service) is congruent with the transfer bundle. :param task: the current task instance :return: the task result """ logger = get_logger() logger.info("check_distance_house_airport") GEOGRAPHICAL_DISTACE_SERVICE = environ.get( "GEOGRAPHICAL_DISTACE_SERVICE", "http://geographical_distances:8080") offer_purchase_data = OfferPurchaseData.from_dict( json.loads(task.get_variable("offer_purchase_data"))) # Connects to postgreSQL and get the offer purchased Session = sessionmaker(bind=create_sql_engine()) session = Session() offer_match: OfferMatch = session.query(OfferMatch).get( {"offer_code": offer_purchase_data.offer_code}) # Finds the name (used for the airport) of the departure airport airports_file = open("./camundaworkers/airports.csv", 'r') airports = csv.reader(airports_file) airport_address = None for row in airports: if row[4] == offer_match.outbound_flight.departure_airport_code: airport_address = row[1] airports_file.close() # Failure case: the airport cannot be found in the CSV. if not airport_address: logger.error( f"Cannot find airport associated with: {offer_match.outbound_flight.departure_airport_code}" ) return task.complete(global_variables={ "distance": "35" }) # 35 > 30, then the transfer won't be booked. request = { "address_1": airport_address, "address_2": str(offer_purchase_data.address) } distance_request = requests.post(GEOGRAPHICAL_DISTACE_SERVICE + "/distance", json=request) return task.complete(global_variables={"distance": distance_request.text})
def send_tickets(task: ExternalTask) -> TaskResult: """ Sends the tickets to the user :param task: the current task instance :return: the task result """ logger = get_logger() logger.info("send_tickets") user_communication_code = str(task.get_variable("user_communication_code")) tickets = str(task.get_variable("tickets")) logger.info(f"Tickets: {tickets}") # Connects to RabbitMQ and publish the ticket connection = pika.BlockingConnection( pika.ConnectionParameters(host="acmesky_mq")) channel = connection.channel() channel.queue_declare(queue=user_communication_code, durable=True) channel.basic_publish( exchange="", routing_key=user_communication_code, body=bytes(tickets, "utf-8"), properties=pika.BasicProperties(delivery_mode=2), ) connection.close() # Connects to PostgreSQL and deletes the purchased offer. Session = sessionmaker(bind=create_sql_engine()) session = Session() offer_purchase_data = OfferPurchaseData.from_dict( json.loads(task.get_variable("offer_purchase_data"))) to_delete = session.query(OfferMatch).get( {"offer_code": offer_purchase_data.offer_code}) session.delete(to_delete) # The following lines are commented (uncomment them and comment the previous two not to delete the offermatch from the database). # session.query(OfferMatch)\ # .filter(OfferMatch.offer_code == offer_purchase_data.offer_code)\ # .update({"blocked": False}, synchronize_session="fetch") session.commit() return task.complete()
def payment_request(task: ExternalTask) -> TaskResult: """ Requests for a new payment session to the Payment Provider. :param task: the current task instance :return: the task result """ logger = get_logger() logger.info("payment_request") user_communication_code = str(task.get_variable("user_communication_code")) offer_purchase_data = OfferPurchaseData.from_dict( json.loads(task.get_variable("offer_purchase_data"))) offer_code = offer_purchase_data.offer_code # Connecting to postgreSQL and getting the offer the user wants to purchase Session = sessionmaker(bind=create_sql_engine()) session = Session() offer_match = session.query(OfferMatch).filter( OfferMatch.offer_code == offer_code, OfferMatch.blocked == True).first() # affected_rows == 1 by hypothesis. outbound_flight_id = offer_match.outbound_flight_id comeback_flight_id = offer_match.comeback_flight_id outbound_flight = session.query(Flight).filter( Flight.id == outbound_flight_id).first() comeback_flight = session.query(Flight).filter( Flight.id == comeback_flight_id).first() # Sends the payment request generation to the Payment Provider and get back the URL to send to the user. payment_request_to_send = { "amount": outbound_flight.cost + comeback_flight.cost, "payment_receiver": "ACMESky", "description": f"Il costo totale dell'offerta è: € {outbound_flight.cost + comeback_flight.cost}. I biglietti verranno acquistati dalla compagnia {outbound_flight.flight_company_name}.", } payment_provider_url = environ.get("PAYMENT_PROVIDER_URL", "http://payment_provider_backend:8080") payment_creation_response = requests.post( payment_provider_url + "/payments/request", json=payment_request_to_send).json() # Creates a payment transaction payment_tx = PaymentTransaction( transaction_id=payment_creation_response.get('transaction_id')) session.add(payment_tx) session.commit() # Connects to Redis and relate the transaction id to the process instance id redis_connection = Redis(host="acmesky_redis", port=6379, db=0) redis_connection.set(payment_creation_response.get('transaction_id'), task.get_process_instance_id()) redis_connection.close() # Connects to RabbitMQ and communicate to the user the payment URL connection = pika.BlockingConnection( pika.ConnectionParameters(host="acmesky_mq")) channel = connection.channel() channel.queue_declare(queue=user_communication_code, durable=True) purchase_url = PurchaseProcessInformation( message=str(payment_creation_response.get('redirect_page')), communication_code=user_communication_code) channel.basic_publish( exchange="", routing_key=user_communication_code, body=bytes(json.dumps(purchase_url.to_dict()), "utf-8"), properties=pika.BasicProperties(delivery_mode=2), ) connection.close() return task.complete()