import json import os from dataclasses import dataclass from pathlib import Path from temporalio.exceptions import WorkflowAlreadyStartedError from shared.config import get_temporal_client # enums for the java enum # class ExecutionScenarios(Enum): # HAPPY_PATH = 0 # ADVANCED_VISIBILITY = auto() # 1 # HUMAN_IN_LOOP = auto() # 2 # API_DOWNTIME = auto() # 3 # BUG_IN_WORKFLOW = auto() # 4 # INVALID_ACCOUNT = auto() # 5 # these dataclasses are for calling the Temporal Workflow # Python equivalent of the workflow we're calling's Java WorkflowParameterObj @dataclass class MoneyMovementWorkflowParameterObj: amount: int # Using snake_case as per Python conventions scenario: str # this is made to demonstrate functionality but it could just as durably be an API call # this assumes it's a valid account - use check_account_valid() to verify that first async def move_money(args: dict) -> dict: account_key = args.get("email_address_or_account_ID") account_type: str = args.get("accounttype") amount = args.get("amount") destinationaccount = args.get("destinationaccount") file_path = ( Path(__file__).resolve().parent.parent / "data" / "customer_account_data.json" ) if not file_path.exists(): return {"error": "Data file not found."} with open(file_path, "r") as file: data = json.load(file) account_list = data["accounts"] for account in account_list: if account["email"] == account_key or account["account_id"] == account_key: amount_str: str = str(amount) from_account_combo = account_key + account_type transfer_workflow_id = await start_workflow( amount_cents=str_dollars_to_cents(amount_str), from_account_name=from_account_combo, to_account_name=destinationaccount, ) if account_type.casefold() == "checking": from_key = "checking_balance" elif account_type.casefold() == "savings": from_key = "savings_balance" else: return_msg = "Money order for account types other than checking or savings is not implemented." return {"error": return_msg} to_key = ( "savings_balance" if destinationaccount.casefold() == "savings" else "checking_balance" ) # Update from-account balance from_balance = float(str_dollars_to_cents(str(account[from_key]))) from_balance -= float(str_dollars_to_cents(amount_str)) account[from_key] = str(from_balance / 100) # Update destination-account balance to_balance = float(str_dollars_to_cents(str(account[to_key]))) to_balance += float(str_dollars_to_cents(amount_str)) account[to_key] = str(to_balance / 100) with open(file_path, "w") as file: json.dump(data, file, indent=4) return { "status": "money movement complete", "confirmation id": transfer_workflow_id, "new_balance": account[from_key], "destination_balance": account[to_key], } return_msg = "Account not found with for " + account_key return {"error": return_msg} # Async function to start workflow async def start_workflow( amount_cents: int, from_account_name: str, to_account_name: str ) -> str: start_real_workflow = os.getenv("FIN_START_REAL_WORKFLOW") if start_real_workflow is not None and start_real_workflow.lower() == "false": START_REAL_WORKFLOW = False else: START_REAL_WORKFLOW = True if START_REAL_WORKFLOW: # Connect to Temporal client = await get_temporal_client() # Create the parameter object params = MoneyMovementWorkflowParameterObj( amount=amount_cents, scenario="HAPPY_PATH" ) workflow_id = ( "TRANSFER-ACCT-" + from_account_name + "-TO-" + to_account_name ) # business-relevant workflow ID try: handle = await client.start_workflow( "moneyTransferWorkflow", # Workflow name params, # Workflow parameters id=workflow_id, task_queue="MoneyTransferJava", # Task queue name ) return handle.id except WorkflowAlreadyStartedError: existing_handle = client.get_workflow_handle(workflow_id=workflow_id) return existing_handle.id else: return ( "TRANSFER-ACCT-" + from_account_name + "-TO-" + to_account_name + "not-real" ) # cleans a string dollar amount description to cents value def str_dollars_to_cents(dollar_str: str) -> int: try: # Remove '$' and any whitespace cleaned_str = dollar_str.replace("$", "").strip() # Handle empty string or invalid input if not cleaned_str: raise ValueError("Empty amount provided") # Convert to float and then to cents amount = float(cleaned_str) if amount < 0: raise ValueError("Negative amounts not allowed") return int(amount * 100) except ValueError as e: raise ValueError(f"Invalid dollar amount format: {dollar_str}") from e