<cfcomponent displayname="EVV Visit Automation" hint="Automates visit EVV transmit and status polling per tickets 10426-10429" output="false">

	<cffunction name="init" access="public" returntype="EVVVisitAutomation" output="false">
		<cfreturn this>
	</cffunction>

	<cffunction name="getApplicationEncKey" access="private" returntype="string" output="false">
		<cfif structKeyExists(application, "enckey") AND len(trim(application.enckey))>
			<cfreturn application.enckey>
		</cfif>
		<cfset application.enckey = "54kczhzgHTj4a8n+SRZPyg==">
		<cfreturn application.enckey>
	</cffunction>

	<cffunction name="ensureApplicationContext" access="private" returntype="void" output="false">
		<cfset getApplicationEncKey()>
		<cfif NOT structKeyExists(application, "DEV")>
			<cfif CGI.SERVER_NAME EQ "127.0.0.1" OR CGI.SERVER_NAME EQ "localhost"
				OR CGI.SERVER_NAME EQ "52.23.186.30" OR CGI.SERVER_NAME EQ "dev.myhomecarebiz.com"
				OR CGI.SERVER_NAME EQ "98.87.35.139" OR CGI.SERVER_NAME EQ "devsecure.myhomecarebiz.com"
				OR CGI.SERVER_NAME EQ "54.175.136.41" OR CGI.SERVER_NAME EQ "stagesecure.myhomecarebiz.com">
				<cfset application.DEV = true>
			<cfelse>
				<cfset application.DEV = false>
			</cfif>
		</cfif>
		<cfif NOT structKeyExists(application, "ServerName") OR NOT len(trim(application.ServerName))>
			<cfset application.ServerName = CGI.SERVER_NAME>
		</cfif>
		<cfif NOT structKeyExists(application, "DataSrc") OR NOT len(trim(application.DataSrc))>
			<cfset application.DataSrc = "hhapowerpath">
		</cfif>
	</cffunction>

	<cffunction name="getAgencyDbPrefix" access="private" returntype="string" output="false">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfif isDefined("Request.prefix_db_agency")
			AND len(trim(Request.prefix_db_agency))
			AND Request.prefix_db_agency NEQ "prefix_db_agency_invalid">
			<cfreturn Request.prefix_db_agency>
		</cfif>
		<cfreturn "agency_" & arguments.agencyID>
	</cffunction>

	<cffunction name="getSandataComponentName" access="private" returntype="string" output="false">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfif arguments.agencyID EQ 1184>
			<cfreturn "AHCCCS">
		<cfelseif arguments.agencyID EQ 1195>
			<cfreturn "hhaexchange">
		</cfif>
		<cfreturn "sandata">
	</cffunction>

	<cffunction name="agencyUsesEVVAutomation" access="public" returntype="boolean" output="false">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfreturn createObject("component", "components.evv.EVVStatusManager").init().agencyUsesEVVAutomation(arguments.agencyID)>
	</cffunction>

	<cffunction name="agencySupportsVisitEVV" access="public" returntype="boolean" output="false">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfreturn agencyUsesEVVAutomation(arguments.agencyID)>
	</cffunction>

	<cffunction name="scheduleRequiresEVVAutomation" access="public" returntype="boolean" output="false">
		<cfargument name="scheduleID" type="numeric" required="true">
		<cfargument name="agencyID" type="numeric" required="true">

		<cfset var agencyDb = getAgencyDbPrefix(arguments.agencyID)>
		<cfset var qPayer = "">

		<cfif NOT agencySupportsVisitEVV(arguments.agencyID)>
			<cfreturn false>
		</cfif>

		<cftry>
			<cfquery name="qPayer" datasource="#Application.DataSrc#">
				SELECT pPayer.evv_payer_required, pPayer.Pay_ID, pPayer.Pay_Name
				FROM #agencyDb#.pSchedules s
				LEFT JOIN #agencyDb#.pPtPayer pt ON s.PtPayer_ID = pt.PtPayer_ID AND pt.Status = 0
				LEFT JOIN #agencyDb#.pPayer ON pPayer.Status = 0
					AND (
						(pt.PtPayer_ID IS NOT NULL AND pPayer.Pay_ID = pt.Payer_ID)
						OR (s.Pay_ID IS NOT NULL AND s.Pay_ID > 0 AND pPayer.Pay_ID = s.Pay_ID)
					)
				WHERE s.Schedule_ID = <cfqueryparam value="#arguments.scheduleID#" cfsqltype="cf_sql_integer">
				AND s.Status = 0
				AND pPayer.evv_payer_required IN (1)
				LIMIT 1
			</cfquery>

			<cfif qPayer.recordCount GT 0>
				<cflog file="EVVVisitAutomation" text="scheduleRequiresEVVAutomation YES schedule #arguments.scheduleID# agency #arguments.agencyID# payer #qPayer.Pay_ID# (#qPayer.Pay_Name#)">
				<cfreturn true>
			</cfif>

			<cflog file="EVVVisitAutomation" text="scheduleRequiresEVVAutomation NO schedule #arguments.scheduleID# agency #arguments.agencyID# - payer evv_payer_required not set">
			<cfcatch type="any">
				<cflog file="EVVVisitAutomation" text="scheduleRequiresEVVAutomation query failed schedule #arguments.scheduleID# agency #arguments.agencyID#: #cfcatch.message#">
			</cfcatch>
		</cftry>

		<cfreturn false>
	</cffunction>

	<cffunction name="normalizeEvvStatusText" access="private" returntype="string" output="false">
		<cfargument name="statusText" type="string" required="true">
		<cfset var s = trim(arguments.statusText)>
		<cfset var upperStatus = uCase(s)>
		<cfif len(s) AND right(s, 1) EQ ".">
			<cfset s = trim(left(s, len(s) - 1))>
			<cfset upperStatus = uCase(s)>
		</cfif>
		<cfif findNoCase("Transaction Received", s) GT 0>
			<cfreturn "Transaction Received">
		</cfif>
		<cfif listFindNoCase("COMPLETE,COMPLETED,SUCCESS,SUCCESSFUL,ACCEPTED,PROCESSED,APPROVED", upperStatus) GT 0>
			<cfreturn "Transaction Received">
		</cfif>
		<cfif listFindNoCase("PENDING,PROCESSING,IN PROGRESS,SUBMITTED,QUEUED", upperStatus) GT 0>
			<cfreturn "Transmitted - awaiting status check">
		</cfif>
		<cfreturn s>
	</cffunction>

	<cffunction name="isFinalEVVStatus" access="private" returntype="boolean" output="false">
		<cfargument name="statusText" type="string" required="true">
		<cfset var s = uCase(normalizeEvvStatusText(arguments.statusText))>
		<cfreturn (
			find("TRANSACTION RECEIVED", s) GT 0
			OR find("SUCCESS", s) GT 0
			OR find("COMPLETE", s) GT 0
			OR find("PROCESSED", s) GT 0
			OR find("ACCEPTED", s) GT 0
			OR find("FAILED", s) GT 0
			OR find("REJECTED", s) GT 0
			OR find("ERROR", s) GT 0
			OR find("DELETED", s) GT 0
		)>
	</cffunction>

	<cffunction name="setupAgencyContext" access="private" returntype="void" output="false">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfargument name="employeeID" type="numeric" required="false" default="0">

		<cfset ensureApplicationContext()>
		<cfset session.agencyId = arguments.agencyID>
		<cfset session.agencyid = arguments.agencyID>
		<cfif arguments.employeeID GT 0>
			<cfset session.employeeid = arguments.employeeID>
		</cfif>
		<cfset Request.prefix_db_agency = getAgencyDbPrefix(arguments.agencyID)>
		<cfif NOT isDefined("Request.prefix_db_lookup") OR NOT len(trim(Request.prefix_db_lookup))>
			<cfset Request.prefix_db_lookup = "hhapowerpath">
		</cfif>
		<cfset Request.sandatacfc = getSandataComponentName(arguments.agencyID) & ".cfc">
	</cffunction>

	<cffunction name="resolveAgencyID" access="private" returntype="numeric" output="false">
		<cfargument name="agencyID" type="numeric" required="false" default="0">
		<cfset var sessionKeys = ["agencyid", "agencyId", "AgencyId", "AGENCYID", "AgencyID"]>
		<cfset var key = "">

		<cfif arguments.agencyID GT 0>
			<cfreturn int(arguments.agencyID)>
		</cfif>

		<cfloop array="#sessionKeys#" index="key">
			<cfif structKeyExists(session, key) AND val(session[key]) GT 0>
				<cfreturn int(val(session[key]))>
			</cfif>
		</cfloop>

		<cfreturn 0>
	</cffunction>

	<cffunction name="transmitVisit" access="public" returntype="struct" output="false">
		<cfargument name="scheduleID" type="numeric" required="true">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfargument name="employeeID" type="numeric" required="false" default="0">
		<cfargument name="reasonCodeID" type="numeric" required="false" default="0">
		<cfargument name="changeMemo" type="string" required="false" default="">
		<cfargument name="reasonCodeDescription" type="string" required="false" default="">

		<cfset var result = { "success" = false, "message" = "", "transactionId" = "" }>
		<cfset var encScheduleID = "">
		<cfset var transmitResult = "">
		<cfset var qTxn = "">
		<cfset var componentName = "">

		<cftry>
			<cfset setupAgencyContext(arguments.agencyID, arguments.employeeID)>
			<cfset encScheduleID = encrypt(arguments.scheduleID, getApplicationEncKey(), "AES", "Hex")>
			<cfset componentName = getSandataComponentName(arguments.agencyID)>
			<cfif componentName EQ "hhaexchange">
				<cfset transmitResult = createObject("component", "components.sandata.hhaexchange").create_visit(
					Schedule_ID = encScheduleID,
					Agency_ID = arguments.agencyID
				)>
				<cfif isSimpleValue(transmitResult) AND findNoCase("successfully", transmitResult) GT 0>
					<cfset result.success = true>
					<cfset result.message = transmitResult>
				<cfelseif isSimpleValue(transmitResult)>
					<cfset result.message = transmitResult>
				</cfif>
			<cfelse>
				<cfset transmitResult = createObject("component", "components.sandata.#componentName#").visit_creation(
					Schedule_ID = encScheduleID,
					Agency_ID = arguments.agencyID,
					confirmed = true,
					evv_reason_code = arguments.reasonCodeID GT 0 ? arguments.reasonCodeID : "",
					evv_change_memo = trim(arguments.changeMemo),
					evv_reason_description = trim(arguments.reasonCodeDescription)
				)>
				<cfif isSimpleValue(transmitResult) AND (
					findNoCase("success", transmitResult) GT 0
					OR findNoCase("transmitted", transmitResult) GT 0
					OR findNoCase("uploaded", transmitResult) GT 0
				)>
					<cfset result.success = true>
					<cfset result.message = transmitResult>
				<cfelseif isSimpleValue(transmitResult)>
					<cfset result.message = transmitResult>
				</cfif>
			</cfif>

			<cfif result.success>
				<cfquery name="qTxn" datasource="#Application.DataSrc#">
					SELECT UUID
					FROM #Request.prefix_db_lookup#.EVV
					WHERE LOWER(API_Type) = <cfqueryparam value="visits" cfsqltype="cf_sql_varchar">
					AND Agency_ID = <cfqueryparam value="#arguments.agencyID#" cfsqltype="cf_sql_integer">
					AND Schedule_ID = <cfqueryparam value="#arguments.scheduleID#" cfsqltype="cf_sql_integer">
					AND status = <cfqueryparam value="0" cfsqltype="cf_sql_integer">
					ORDER BY EVV_ID DESC
					LIMIT 1
				</cfquery>
				<cfif qTxn.recordCount GT 0>
					<cfset result.transactionId = trim(qTxn.UUID)>
				</cfif>
			<cfelseif NOT len(trim(result.message))>
				<cfset result.message = "Unexpected response from EVV visit transmit">
			</cfif>

			<cfcatch type="any">
				<cfset result.message = cfcatch.message>
			</cfcatch>
		</cftry>

		<cfif NOT result.success>
			<cfset sendTransmitErrorEmail(
				scheduleID = arguments.scheduleID,
				agencyID = arguments.agencyID,
				employeeID = arguments.employeeID,
				errorMessage = result.message
			)>
		</cfif>

		<cfreturn result>
	</cffunction>

	<cffunction name="sendTransmitErrorEmail" access="private" returntype="void" output="false">
		<cfargument name="scheduleID" type="numeric" required="true">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfargument name="errorMessage" type="string" required="true">
		<cfargument name="employeeID" type="numeric" required="false" default="0">

		<cfset var mailTo = "velmurugan@myhomecarebiz.com">
		<cfif isDefined("request.send_errors_to") AND len(trim(request.send_errors_to))>
			<cfset mailTo = trim(request.send_errors_to)>
		</cfif>
		<cfset var envLabel = isDefined("Application.ServerName") ? Application.ServerName : CGI.SERVER_NAME>

		<cftry>
			<cfmail to="#mailTo#" from="error@myhomecarebiz.com" subject="#envLabel# EVV Visit Transmit Error - Agency #arguments.agencyID# Schedule #arguments.scheduleID#" type="html">
				<h3>EVV Visit Transmit Error</h3>
				<p><strong>Time:</strong> #dateTimeFormat(now(), "yyyy-mm-dd HH:nn:ss")#</p>
				<p><strong>Agency ID:</strong> #arguments.agencyID#</p>
				<p><strong>Schedule ID:</strong> #arguments.scheduleID#</p>
				<p><strong>Employee ID:</strong> #arguments.employeeID#</p>
				<p style="color: ##c00;"><strong>Error:</strong> #htmlEditFormat(arguments.errorMessage)#</p>
			</cfmail>
			<cfcatch type="any">
				<cflog file="EVVVisitAutomation" text="sendTransmitErrorEmail failed schedule #arguments.scheduleID#: #cfcatch.message#">
			</cfcatch>
		</cftry>
	</cffunction>

	<cffunction name="pollVisitStatus" access="public" returntype="struct" output="false">
		<cfargument name="scheduleID" type="numeric" required="true">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfargument name="employeeID" type="numeric" required="false" default="0">

		<cfset var result = { "success" = false, "status" = "", "message" = "" }>
		<cfset var encScheduleID = "">
		<cfset var statusResult = "">
		<cfset var componentName = "">

		<cftry>
			<cfset setupAgencyContext(arguments.agencyID, arguments.employeeID)>
			<cfset encScheduleID = encrypt(arguments.scheduleID, getApplicationEncKey(), "AES", "Hex")>
			<cfset componentName = getSandataComponentName(arguments.agencyID)>
			<cfif componentName EQ "hhaexchange">
				<cfset statusResult = createObject("component", "components.sandata.hhaexchange").check_visit_status(
					Schedule_ID = encScheduleID,
					Agency_ID = arguments.agencyID,
					sendNotification = false
				)>
			<cfelse>
				<cfset statusResult = createObject("component", "components.sandata.#componentName#").check_visit_status(
					Schedule_ID = encScheduleID,
					Agency_ID = arguments.agencyID
				)>
			</cfif>

			<cfif isStruct(statusResult)>
				<cfset result.success = structKeyExists(statusResult, "success") ? statusResult.success : false>
				<cfif structKeyExists(statusResult, "status")>
					<cfset result.status = statusResult.status>
				<cfelseif structKeyExists(statusResult, "data") AND isStruct(statusResult.data) AND structKeyExists(statusResult.data, "reason")>
					<cfset result.status = statusResult.data.reason>
				</cfif>
				<cfif structKeyExists(statusResult, "message")>
					<cfset result.message = statusResult.message>
				</cfif>
			</cfif>

			<cfcatch type="any">
				<cfset result.message = cfcatch.message>
			</cfcatch>
		</cftry>

		<cfreturn result>
	</cffunction>

	<cffunction name="updateVisitEvvSanStatus" access="private" returntype="boolean" output="false">
		<cfargument name="scheduleID" type="numeric" required="true">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfargument name="sanStatus" type="string" required="true">

		<cfset var lookupDb = isDefined("Request.prefix_db_lookup") AND len(trim(Request.prefix_db_lookup)) ? Request.prefix_db_lookup : "hhapowerpath">
		<cfset var statusText = normalizeEvvStatusText(arguments.sanStatus)>
		<cfset var qEvv = "">

		<cfif NOT len(statusText)>
			<cfreturn false>
		</cfif>

		<cfquery name="qEvv" datasource="#Application.DataSrc#">
			SELECT EVV_ID
			FROM #lookupDb#.EVV
			WHERE LOWER(API_Type) = <cfqueryparam value="visits" cfsqltype="cf_sql_varchar">
			AND Agency_ID = <cfqueryparam value="#arguments.agencyID#" cfsqltype="cf_sql_integer">
			AND Schedule_ID = <cfqueryparam value="#arguments.scheduleID#" cfsqltype="cf_sql_integer">
			AND status = <cfqueryparam value="0" cfsqltype="cf_sql_integer">
			ORDER BY EVV_ID DESC
			LIMIT 1
		</cfquery>

		<cfif qEvv.recordCount EQ 0>
			<cfreturn false>
		</cfif>

		<cfquery datasource="#Application.DataSrc#">
			UPDATE #lookupDb#.EVV
			SET San_status = <cfqueryparam value="#left(statusText, 255)#" cfsqltype="cf_sql_varchar">,
				Date_Update = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
			WHERE EVV_ID = <cfqueryparam value="#qEvv.EVV_ID#" cfsqltype="cf_sql_integer">
		</cfquery>

		<cfreturn true>
	</cffunction>

	<cffunction name="queueVisitAutomation" access="public" returntype="struct" output="false">
		<cfargument name="scheduleID" type="numeric" required="true">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfargument name="triggerSource" type="string" required="true">
		<cfargument name="patientID" type="numeric" required="false" default="0">
		<cfargument name="createdBy" type="numeric" required="false" default="0">
		<cfargument name="reasonCodeID" type="numeric" required="false" default="0">
		<cfargument name="changeMemo" type="string" required="false" default="">
		<cfargument name="reasonCodeDescription" type="string" required="false" default="">

		<cfset var result = { "queued" = false, "jobID" = 0, "message" = "" }>

		<cfset setupAgencyContext(arguments.agencyID, arguments.createdBy)>

		<cfif NOT agencySupportsVisitEVV(arguments.agencyID)>
			<cfset result.message = "Agency #arguments.agencyID# is not configured for EVV automation (Session.EVVagencies)">
			<cfreturn result>
		</cfif>

		<cfif NOT scheduleRequiresEVVAutomation(arguments.scheduleID, arguments.agencyID)>
			<cfset result.message = "Schedule payer does not have evv_payer_required = 1">
			<cflog file="EVVVisitAutomation" text="queueVisitAutomation skipped schedule #arguments.scheduleID# agency #arguments.agencyID#: #result.message#">
			<cfreturn result>
		</cfif>

		<cfif NOT isDefined("Request.prefix_db_lookup") OR NOT len(trim(Request.prefix_db_lookup))>
			<cfset Request.prefix_db_lookup = "hhapowerpath">
		</cfif>

		<cftry>
			<cfquery datasource="#Application.DataSrc#">
				UPDATE #Request.prefix_db_lookup#.EVV_Visit_Automation
				SET Job_Status = <cfqueryparam value="cancelled" cfsqltype="cf_sql_varchar">,
					Date_Update = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
				WHERE Agency_ID = <cfqueryparam value="#arguments.agencyID#" cfsqltype="cf_sql_integer">
				AND Schedule_ID = <cfqueryparam value="#arguments.scheduleID#" cfsqltype="cf_sql_integer">
				AND Job_Status IN (<cfqueryparam value="pending_transmit,waiting_status,polling_status" cfsqltype="cf_sql_varchar" list="true">)
			</cfquery>

			<cfquery name="insJob" datasource="#Application.DataSrc#" result="insResult">
				INSERT INTO #Request.prefix_db_lookup#.EVV_Visit_Automation (
					Agency_ID, Schedule_ID, Patient_ID, Trigger_Source, Job_Status,
					Transmit_At, Status_Check_Start, Status_Check_End,
					Created_By, Date_Create, Date_Update
				) VALUES (
					<cfqueryparam value="#arguments.agencyID#" cfsqltype="cf_sql_integer">,
					<cfqueryparam value="#arguments.scheduleID#" cfsqltype="cf_sql_integer">,
					<cfqueryparam value="#arguments.patientID#" cfsqltype="cf_sql_integer">,
					<cfqueryparam value="#arguments.triggerSource#" cfsqltype="cf_sql_varchar">,
					<cfqueryparam value="pending_transmit" cfsqltype="cf_sql_varchar">,
					<cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
					<cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
					<cfqueryparam value="#DateAdd('n', 80, now())#" cfsqltype="cf_sql_timestamp">,
					<cfqueryparam value="#arguments.createdBy#" cfsqltype="cf_sql_integer">,
					<cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
					<cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
				)
			</cfquery>

			<cfset result.queued = true>
			<cfset result.jobID = insResult.GENERATED_KEY ?: 0>
			<cfset result.message = "EVV visit automation queued">

			<cfset var transmit = transmitVisit(
				arguments.scheduleID,
				arguments.agencyID,
				arguments.createdBy,
				arguments.reasonCodeID,
				arguments.changeMemo,
				arguments.reasonCodeDescription
			)>
			<cfif transmit.success>
				<cfquery datasource="#Application.DataSrc#">
					UPDATE #Request.prefix_db_lookup#.EVV_Visit_Automation
					SET Job_Status = <cfqueryparam value="waiting_status" cfsqltype="cf_sql_varchar">,
						Transmit_Result = <cfqueryparam value="#left(transmit.message, 500)#" cfsqltype="cf_sql_varchar">,
						Transaction_ID = <cfqueryparam value="#left(transmit.transactionId, 100)#" cfsqltype="cf_sql_varchar" null="#NOT len(trim(transmit.transactionId))#">,
						Status_Check_Start = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
						Status_Check_End = <cfqueryparam value="#DateAdd('n', 80, now())#" cfsqltype="cf_sql_timestamp">,
						Date_Update = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
					WHERE Automation_ID = <cfqueryparam value="#result.jobID#" cfsqltype="cf_sql_integer">
				</cfquery>
				<cfset result.message = "EVV visit transmitted; status polling scheduled">
			<cfelse>
				<cfquery datasource="#Application.DataSrc#">
					UPDATE #Request.prefix_db_lookup#.EVV_Visit_Automation
					SET Job_Status = <cfqueryparam value="failed" cfsqltype="cf_sql_varchar">,
						Last_Error = <cfqueryparam value="#left(transmit.message, 1000)#" cfsqltype="cf_sql_varchar">,
						Date_Update = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
					WHERE Automation_ID = <cfqueryparam value="#result.jobID#" cfsqltype="cf_sql_integer">
				</cfquery>
				<cfset result.message = transmit.message>
			</cfif>

			<cfcatch type="any">
				<cfset result.message = cfcatch.message>
				<cflog file="EVVVisitAutomation" text="queueVisitAutomation failed schedule #arguments.scheduleID#: #cfcatch.message#">
			</cfcatch>
		</cftry>

		<cfreturn result>
	</cffunction>

	<cffunction name="queueStatusPollingOnly" access="public" returntype="struct" output="false">
		<cfargument name="scheduleID" type="numeric" required="true">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfargument name="transactionId" type="string" required="false" default="">
		<cfargument name="triggerSource" type="string" required="false" default="manual_send_evv">
		<cfargument name="patientID" type="numeric" required="false" default="0">
		<cfargument name="createdBy" type="numeric" required="false" default="0">

		<cfset var result = { "queued" = false, "jobID" = 0, "message" = "" }>

		<cfif NOT agencySupportsVisitEVV(arguments.agencyID)>
			<cfreturn result>
		</cfif>

		<cfset setupAgencyContext(arguments.agencyID, arguments.createdBy)>
		<cfif NOT isDefined("Request.prefix_db_lookup") OR NOT len(trim(Request.prefix_db_lookup))>
			<cfset Request.prefix_db_lookup = "hhapowerpath">
		</cfif>

		<cftry>
			<cfquery datasource="#Application.DataSrc#">
				UPDATE #Request.prefix_db_lookup#.EVV_Visit_Automation
				SET Job_Status = <cfqueryparam value="cancelled" cfsqltype="cf_sql_varchar">,
					Date_Update = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
				WHERE Agency_ID = <cfqueryparam value="#arguments.agencyID#" cfsqltype="cf_sql_integer">
				AND Schedule_ID = <cfqueryparam value="#arguments.scheduleID#" cfsqltype="cf_sql_integer">
				AND Job_Status IN (<cfqueryparam value="pending_transmit,waiting_status,polling_status" cfsqltype="cf_sql_varchar" list="true">)
			</cfquery>

			<cfquery name="insJob" datasource="#Application.DataSrc#" result="insResult">
				INSERT INTO #Request.prefix_db_lookup#.EVV_Visit_Automation (
					Agency_ID, Schedule_ID, Patient_ID, Trigger_Source, Job_Status,
					Transmit_At, Transmit_Result, Transaction_ID,
					Status_Check_Start, Status_Check_End,
					Created_By, Date_Create, Date_Update
				) VALUES (
					<cfqueryparam value="#arguments.agencyID#" cfsqltype="cf_sql_integer">,
					<cfqueryparam value="#arguments.scheduleID#" cfsqltype="cf_sql_integer">,
					<cfqueryparam value="#arguments.patientID#" cfsqltype="cf_sql_integer">,
					<cfqueryparam value="#arguments.triggerSource#" cfsqltype="cf_sql_varchar">,
					<cfqueryparam value="waiting_status" cfsqltype="cf_sql_varchar">,
					<cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
					<cfqueryparam value="already_transmitted" cfsqltype="cf_sql_varchar">,
					<cfqueryparam value="#left(trim(arguments.transactionId), 100)#" cfsqltype="cf_sql_varchar" null="#NOT len(trim(arguments.transactionId))#">,
					<cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
					<cfqueryparam value="#DateAdd('n', 80, now())#" cfsqltype="cf_sql_timestamp">,
					<cfqueryparam value="#arguments.createdBy#" cfsqltype="cf_sql_integer">,
					<cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
					<cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
				)
			</cfquery>

			<cfset result.queued = true>
			<cfset result.jobID = insResult.GENERATED_KEY ?: 0>
			<cfset result.message = "EVV status polling scheduled">

			<cfcatch type="any">
				<cfset result.message = cfcatch.message>
				<cflog file="EVVVisitAutomation" text="queueStatusPollingOnly failed schedule #arguments.scheduleID#: #cfcatch.message#">
			</cfcatch>
		</cftry>

		<cfreturn result>
	</cffunction>

	<cffunction name="processPendingJobs" access="public" returntype="struct" output="false">
		<cfargument name="agencyID" type="numeric" required="false" default="0">
		<cfargument name="forcePoll" type="boolean" required="false" default="false">

		<cfset var summary = {
			"success" = true,
			"transmitted" = 0,
			"polled" = 0,
			"completed" = 0,
			"failed" = 0,
			"jobsFound" = 0,
			"orphanPolled" = 0,
			"errors" = [],
			"details" = [],
			"ranAt" = now()
		}>
		<cfset var qJobs = "">
		<cfset var transmit = "">
		<cfset var poll = "">
		<cfset var currentStatus = "">
		<cfset var newJobStatus = "">
		<cfset var detailLine = "">

		<cfset ensureApplicationContext()>
		<cfif NOT isDefined("Request.prefix_db_lookup") OR NOT len(trim(Request.prefix_db_lookup))>
			<cfset Request.prefix_db_lookup = "hhapowerpath">
		</cfif>

		<cfquery name="qJobs" datasource="#Application.DataSrc#">
			SELECT *
			FROM #Request.prefix_db_lookup#.EVV_Visit_Automation
			WHERE Job_Status IN (<cfqueryparam value="pending_transmit,waiting_status,polling_status" cfsqltype="cf_sql_varchar" list="true">)
			<cfif arguments.agencyID GT 0>
				AND Agency_ID = <cfqueryparam value="#arguments.agencyID#" cfsqltype="cf_sql_integer">
			</cfif>
			ORDER BY Automation_ID ASC
			LIMIT 100
		</cfquery>
 		<cfset summary.jobsFound = qJobs.recordCount>

		<cfloop query="qJobs">
			<!--- <cfif NOT createObject("component", "components.evv.EVVStatusManager").init().agencyUsesEVVAutomation(qJobs.Agency_ID)>
				<cfcontinue>
			</cfif> --->
			<cftry>
				<cfif qJobs.Job_Status EQ "pending_transmit" AND qJobs.Transmit_At LTE now()>
					<cfset transmit = transmitVisit(qJobs.Schedule_ID, qJobs.Agency_ID, qJobs.Created_By)>
					<cfif transmit.success>
						<cfquery datasource="#Application.DataSrc#">
							UPDATE #Request.prefix_db_lookup#.EVV_Visit_Automation
							SET Job_Status = <cfqueryparam value="waiting_status" cfsqltype="cf_sql_varchar">,
								Transmit_Result = <cfqueryparam value="#left(transmit.message, 500)#" cfsqltype="cf_sql_varchar">,
								Transaction_ID = <cfqueryparam value="#left(transmit.transactionId, 100)#" cfsqltype="cf_sql_varchar" null="#NOT len(trim(transmit.transactionId))#">,
								Status_Check_Start = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
								Status_Check_End = <cfqueryparam value="#DateAdd('n', 80, now())#" cfsqltype="cf_sql_timestamp">,
								Last_Error = NULL,
								Date_Update = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
							WHERE Automation_ID = <cfqueryparam value="#qJobs.Automation_ID#" cfsqltype="cf_sql_integer">
						</cfquery>
						<cfset summary.transmitted++>
						<cfset arrayAppend(summary.details, "Job #qJobs.Automation_ID# (Agency #qJobs.Agency_ID#, Schedule #qJobs.Schedule_ID#): transmitted successfully")>
					<cfelse>
						<cfquery datasource="#Application.DataSrc#">
							UPDATE #Request.prefix_db_lookup#.EVV_Visit_Automation
							SET Job_Status = <cfqueryparam value="failed" cfsqltype="cf_sql_varchar">,
								Last_Error = <cfqueryparam value="#left(transmit.message, 1000)#" cfsqltype="cf_sql_varchar">,
								Date_Update = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
							WHERE Automation_ID = <cfqueryparam value="#qJobs.Automation_ID#" cfsqltype="cf_sql_integer">
						</cfquery>
						<cfset summary.failed++>
						<cfset summary.success = false>
						<cfset arrayAppend(summary.errors, "Job #qJobs.Automation_ID# (Agency #qJobs.Agency_ID#, Schedule #qJobs.Schedule_ID#): transmit failed - #transmit.message#")>
					</cfif>
				<cfelseif listFind("waiting_status,polling_status", qJobs.Job_Status) AND (arguments.forcePoll OR qJobs.Status_Check_Start LTE now())>
					<cfif qJobs.Status_Check_End LTE now()>
						<cfif len(trim(qJobs.EVV_Status))>
							<cfset updateVisitEvvSanStatus(qJobs.Schedule_ID, qJobs.Agency_ID, qJobs.EVV_Status)>
						</cfif>
						<cfquery datasource="#Application.DataSrc#">
							UPDATE #Request.prefix_db_lookup#.EVV_Visit_Automation
							SET Job_Status = <cfqueryparam value="complete" cfsqltype="cf_sql_varchar">,
								Date_Update = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
							WHERE Automation_ID = <cfqueryparam value="#qJobs.Automation_ID#" cfsqltype="cf_sql_integer">
						</cfquery>
						<cfset summary.completed++>
						<cfset arrayAppend(summary.details, "Job #qJobs.Automation_ID# (Agency #qJobs.Agency_ID#, Schedule #qJobs.Schedule_ID#): polling window ended, marked complete")>
					<cfelse>
						<cfset poll = pollVisitStatus(qJobs.Schedule_ID, qJobs.Agency_ID, qJobs.Created_By)>
						<cfset currentStatus = len(trim(poll.status)) ? normalizeEvvStatusText(poll.status) : normalizeEvvStatusText(poll.message)>
						<cfif len(trim(currentStatus))>
							<cfset updateVisitEvvSanStatus(qJobs.Schedule_ID, qJobs.Agency_ID, currentStatus)>
						</cfif>
						<cfset newJobStatus = isFinalEVVStatus(currentStatus) ? "complete" : "polling_status">
						<cfquery datasource="#Application.DataSrc#">
							UPDATE #Request.prefix_db_lookup#.EVV_Visit_Automation
							SET Job_Status = <cfqueryparam value="#newJobStatus#" cfsqltype="cf_sql_varchar">,
								EVV_Status = <cfqueryparam value="#left(currentStatus, 255)#" cfsqltype="cf_sql_varchar">,
								Status_Check_Count = Status_Check_Count + 1,
								Last_Status_Check = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
								Last_Error = <cfqueryparam value="#left(poll.message, 1000)#" cfsqltype="cf_sql_varchar" null="#NOT len(trim(poll.message))#">,
								Date_Update = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
							WHERE Automation_ID = <cfqueryparam value="#qJobs.Automation_ID#" cfsqltype="cf_sql_integer">
						</cfquery>
						<cfset summary.polled++>
						<cfset detailLine = "Job #qJobs.Automation_ID# (Agency #qJobs.Agency_ID#, Schedule #qJobs.Schedule_ID#): status poll returned '#currentStatus#'">
						<cfif isFinalEVVStatus(currentStatus)>
							<cfset summary.completed++>
							<cfset detailLine &= " - marked complete">
						</cfif>
						<cfif NOT poll.success>
							<cfset summary.success = false>
							<cfset summary.failed++>
							<cfset arrayAppend(summary.errors, detailLine & " - #poll.message#")>
						<cfelse>
							<cfset arrayAppend(summary.details, detailLine)>
						</cfif>
					</cfif>
				</cfif>

				<cfcatch type="any">
					<cfset summary.success = false>
					<cfset summary.failed++>
					<cfquery datasource="#Application.DataSrc#">
						UPDATE #Request.prefix_db_lookup#.EVV_Visit_Automation
						SET Last_Error = <cfqueryparam value="#left(cfcatch.message, 1000)#" cfsqltype="cf_sql_varchar">,
							Date_Update = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
						WHERE Automation_ID = <cfqueryparam value="#qJobs.Automation_ID#" cfsqltype="cf_sql_integer">
					</cfquery>
					<cfset arrayAppend(summary.errors, "Job #qJobs.Automation_ID# (Agency #qJobs.Agency_ID#, Schedule #qJobs.Schedule_ID#): #cfcatch.message#")>
					<cflog file="EVVVisitAutomation" text="processPendingJobs error for Automation_ID #qJobs.Automation_ID#: #cfcatch.message#">
				</cfcatch>
			</cftry>
		</cfloop>

		<cfset var orphanSummary = processOrphanTransmittedVisits(agencyID = arguments.agencyID, forcePoll = arguments.forcePoll)>
		<cfset summary.orphanPolled = orphanSummary.polled>
		<cfset summary.polled += orphanSummary.polled>
		<cfset summary.completed += orphanSummary.completed>
		<cfset summary.failed += orphanSummary.failed>
		<cfif NOT orphanSummary.success>
			<cfset summary.success = false>
		</cfif>
		<cfloop array="#orphanSummary.details#" index="detailLine">
			<cfset arrayAppend(summary.details, detailLine)>
		</cfloop>
		<cfloop array="#orphanSummary.errors#" index="detailLine">
			<cfset arrayAppend(summary.errors, detailLine)>
		</cfloop>

		<cfreturn summary>
	</cffunction>

	<cffunction name="processOrphanTransmittedVisits" access="private" returntype="struct" output="false">
		<cfargument name="agencyID" type="numeric" required="false" default="0">
		<cfargument name="forcePoll" type="boolean" required="false" default="false">

		<cfset var summary = {
			"success" = true,
			"polled" = 0,
			"completed" = 0,
			"failed" = 0,
			"errors" = [],
			"details" = []
		}>
		<cfset var evvAgencies = createObject("component", "components.evv.EVVStatusManager").init().getEVVAgenciesList()>
		<cfset var qVisits = "">
		<cfset var poll = "">
		<cfset var currentStatus = "">
		<cfset var detailLine = "">
		<cfif NOT len(evvAgencies)>
			<cfreturn summary>
		</cfif>

		<cfquery name="qVisits" datasource="#Application.DataSrc#">
			SELECT e.Agency_ID, e.Schedule_ID, e.San_status, e.Date_Create
			FROM #Request.prefix_db_lookup#.EVV e
			WHERE LOWER(e.API_Type) = <cfqueryparam value="visits" cfsqltype="cf_sql_varchar">
			AND e.status = <cfqueryparam value="0" cfsqltype="cf_sql_integer">
			AND e.San_status = <cfqueryparam value="Transmitted - Initial submission" cfsqltype="cf_sql_varchar">
			AND e.UUID IS NOT NULL
			AND TRIM(e.UUID) <> ''
			AND e.Agency_ID IN (<cfqueryparam value="#evvAgencies#" cfsqltype="cf_sql_integer" list="true">)
			<cfif arguments.agencyID GT 0>
				AND e.Agency_ID = <cfqueryparam value="#arguments.agencyID#" cfsqltype="cf_sql_integer">
			</cfif>
			AND NOT EXISTS (
				SELECT 1
				FROM #Request.prefix_db_lookup#.EVV_Visit_Automation a
				WHERE a.Agency_ID = e.Agency_ID
				AND a.Schedule_ID = e.Schedule_ID
				AND a.Job_Status IN (<cfqueryparam value="pending_transmit,waiting_status,polling_status" cfsqltype="cf_sql_varchar" list="true">)
			)
			ORDER BY e.EVV_ID ASC
			LIMIT 50
		</cfquery>

		<cfloop query="qVisits">
			<cftry>
				<cfset poll = pollVisitStatus(qVisits.Schedule_ID, qVisits.Agency_ID, 0)>
				<cfset currentStatus = len(trim(poll.status)) ? normalizeEvvStatusText(poll.status) : normalizeEvvStatusText(poll.message)>
				<cfif len(trim(currentStatus)) AND currentStatus NEQ "Transmitted - Initial submission">
					<cfset updateVisitEvvSanStatus(qVisits.Schedule_ID, qVisits.Agency_ID, currentStatus)>
					<cfset summary.polled++>
					<cfset detailLine = "Orphan visit poll (Agency #qVisits.Agency_ID#, Schedule #qVisits.Schedule_ID#): status '#currentStatus#'">
					<cfif isFinalEVVStatus(currentStatus)>
						<cfset summary.completed++>
						<cfset detailLine &= " - final status stored">
					</cfif>
					<cfif poll.success>
						<cfset arrayAppend(summary.details, detailLine)>
					<cfelse>
						<cfset summary.success = false>
						<cfset summary.failed++>
						<cfset arrayAppend(summary.errors, detailLine & " - #poll.message#")>
					</cfif>
				<cfelseif NOT poll.success>
					<cfset summary.success = false>
					<cfset summary.failed++>
					<cfset arrayAppend(summary.errors, "Orphan visit poll failed (Agency #qVisits.Agency_ID#, Schedule #qVisits.Schedule_ID#): #poll.message#")>
				</cfif>

				<cfcatch type="any">
					<cfset summary.success = false>
					<cfset summary.failed++>
					<cfset arrayAppend(summary.errors, "Orphan visit poll error (Agency #qVisits.Agency_ID#, Schedule #qVisits.Schedule_ID#): #cfcatch.message#")>
				</cfcatch>
			</cftry>
		</cfloop>

		<cfreturn summary>
	</cffunction>

	<cffunction name="cronRunHasErrors" access="private" returntype="boolean" output="false">
		<cfargument name="summary" type="struct" required="true">
		<cfargument name="fatalError" type="struct" required="false" default="#structNew()#">
		<cfreturn (
			structCount(arguments.fatalError) GT 0
			OR (structKeyExists(arguments.summary, "errors") AND arrayLen(arguments.summary.errors) GT 0)
			OR (structKeyExists(arguments.summary, "failed") AND arguments.summary.failed GT 0)
			OR (structKeyExists(arguments.summary, "success") AND NOT arguments.summary.success)
		)>
	</cffunction>

	<cffunction name="getActiveJobForSchedule" access="public" returntype="struct" output="false">
		<cfargument name="scheduleID" type="numeric" required="true">
		<cfargument name="agencyID" type="numeric" required="true">

		<cfset var result = { "hasActiveJob" = false, "jobStatus" = "", "evvStatus" = "", "automationID" = 0 }>
		<cfset var qJob = "">

		<cfif NOT isDefined("Request.prefix_db_lookup") OR NOT len(trim(Request.prefix_db_lookup))>
			<cfset Request.prefix_db_lookup = "hhapowerpath">
		</cfif>

		<cfquery name="qJob" datasource="#Application.DataSrc#">
			SELECT Automation_ID, Job_Status, EVV_Status
			FROM #Request.prefix_db_lookup#.EVV_Visit_Automation
			WHERE Agency_ID = <cfqueryparam value="#arguments.agencyID#" cfsqltype="cf_sql_integer">
			AND Schedule_ID = <cfqueryparam value="#arguments.scheduleID#" cfsqltype="cf_sql_integer">
			AND Job_Status IN (<cfqueryparam value="pending_transmit,waiting_status,polling_status" cfsqltype="cf_sql_varchar" list="true">)
			ORDER BY Automation_ID DESC
			LIMIT 1
		</cfquery>

		<cfif qJob.recordCount GT 0>
			<cfset result.hasActiveJob = true>
			<cfset result.jobStatus = qJob.Job_Status>
			<cfset result.evvStatus = qJob.EVV_Status>
			<cfset result.automationID = qJob.Automation_ID>
		</cfif>

		<cfreturn result>
	</cffunction>

	<cffunction name="updateAutomationJobFromStatusCheck" access="public" returntype="struct" output="false">
		<cfargument name="scheduleID" type="numeric" required="true">
		<cfargument name="agencyID" type="numeric" required="true">
		<cfargument name="evvStatus" type="string" required="true">

		<cfset var result = { "updated" = false, "automationID" = 0, "jobStatus" = "", "hasActiveJob" = false }>
		<cfset var qJob = "">
		<cfset var statusText = trim(arguments.evvStatus)>
		<cfset var newJobStatus = "">

		<cfif NOT len(statusText)>
			<cfreturn result>
		</cfif>

		<cfif NOT isDefined("Request.prefix_db_lookup") OR NOT len(trim(Request.prefix_db_lookup))>
			<cfset Request.prefix_db_lookup = "hhapowerpath">
		</cfif>

		<cfquery name="qJob" datasource="#Application.DataSrc#">
			SELECT Automation_ID, Job_Status
			FROM #Request.prefix_db_lookup#.EVV_Visit_Automation
			WHERE Agency_ID = <cfqueryparam value="#arguments.agencyID#" cfsqltype="cf_sql_integer">
			AND Schedule_ID = <cfqueryparam value="#arguments.scheduleID#" cfsqltype="cf_sql_integer">
			AND Job_Status IN (<cfqueryparam value="pending_transmit,waiting_status,polling_status" cfsqltype="cf_sql_varchar" list="true">)
			ORDER BY Automation_ID DESC
			LIMIT 1
		</cfquery>

		<cfif qJob.recordCount EQ 0>
			<cfreturn result>
		</cfif>

		<cfset newJobStatus = isFinalEVVStatus(statusText) ? "complete" : qJob.Job_Status>
		<cfif listFind("waiting_status,polling_status", qJob.Job_Status) AND NOT isFinalEVVStatus(statusText)>
			<cfset newJobStatus = "polling_status">
		</cfif>

		<cfquery datasource="#Application.DataSrc#">
			UPDATE #Request.prefix_db_lookup#.EVV_Visit_Automation
			SET EVV_Status = <cfqueryparam value="#left(statusText, 255)#" cfsqltype="cf_sql_varchar">,
				Job_Status = <cfqueryparam value="#newJobStatus#" cfsqltype="cf_sql_varchar">,
				Status_Check_Count = Status_Check_Count + 1,
				Last_Status_Check = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
				Last_Error = NULL,
				Date_Update = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">
			WHERE Automation_ID = <cfqueryparam value="#qJob.Automation_ID#" cfsqltype="cf_sql_integer">
		</cfquery>

		<cfset result.updated = true>
		<cfset result.automationID = qJob.Automation_ID>
		<cfset result.jobStatus = newJobStatus>
		<cfset result.hasActiveJob = listFind("pending_transmit,waiting_status,polling_status", newJobStatus) GT 0>

		<cfreturn result>
	</cffunction>

	<cffunction name="getVisitAutomationStatus" access="remote" returnformat="json" output="false">
		<cfargument name="Schedule_ID" required="yes">
		<cfargument name="Agency_ID" required="false" default="0">

		<cfset var scheduleID = decrypt(arguments.Schedule_ID, getApplicationEncKey(), "AES", "Hex")>
		<cfset var agencyID = resolveAgencyID(val(arguments.Agency_ID))>
		<cfset var evvManager = "">
		<cfset var evvStatus = "">
		<cfset var activeJob = "">
		<cfset var displayStatus = "">

		<cfif agencyID LTE 0>
			<cfreturn { "success" = false, "message" = "Agency ID is required" }>
		</cfif>

		<cfset setupAgencyContext(agencyID)>
		<cfset evvManager = createObject("component", "components.evv.EVVStatusManager").init()>
		<cfset evvStatus = evvManager.getEVVStatusWithChangeDetection("visits", scheduleID, agencyID)>
		<cfset activeJob = getActiveJobForSchedule(scheduleID, agencyID)>
		<cfset displayStatus = evvStatus.EVV_Status>

		<cfif len(trim(activeJob.evvStatus))>
			<cfset displayStatus = activeJob.evvStatus>
		<cfelseif activeJob.hasActiveJob AND activeJob.jobStatus EQ "waiting_status">
			<cfset displayStatus = "Transmitted - awaiting status check">
		</cfif>

		<cfreturn {
			"success" = true,
			"status" = displayStatus,
			"hasChanges" = evvStatus.HasChanges,
			"hasActiveJob" = activeJob.hasActiveJob,
			"jobStatus" = activeJob.jobStatus
		}>
	</cffunction>

	<cffunction name="sendCronRunEmail" access="public" returntype="void" output="false">
		<cfargument name="summary" type="struct" required="true">
		<cfargument name="fatalError" type="struct" required="false" default="#structNew()#">

		<cfif NOT cronRunHasErrors(arguments.summary, arguments.fatalError)>
			<cfreturn>
		</cfif>

		<cfset var mailTo = "jasmin.s@myhomecarebiz.com,velmurugan@myhomecarebiz.com">
		<cfif isDefined("request.send_errors_to") AND len(trim(request.send_errors_to))>
			<cfset mailTo = reReplace(trim(request.send_errors_to), "\s+", " ", "all")>
		</cfif>

		<cfset var hasFatal = structCount(arguments.fatalError) GT 0>
		<cfset var hasJobErrors = structKeyExists(arguments.summary, "errors") AND arrayLen(arguments.summary.errors) GT 0>
		<cfset var hasActivity = (
			arguments.summary.transmitted GT 0
			OR arguments.summary.polled GT 0
			OR arguments.summary.completed GT 0
			OR arguments.summary.failed GT 0
		)>
		<cfset var envLabel = isDefined("Application.ServerName") ? Application.ServerName : CGI.SERVER_NAME>
		<cfset var subjectPrefix = (hasFatal OR hasJobErrors OR arguments.summary.failed GT 0) ? "ERROR" : "SUCCESS">
		<cfset var mailSubject = "#envLabel# EVV Visit Automation Cron - #subjectPrefix#">

		<cfsavecontent variable="local.mailBody">
			<cfoutput>
			<h3>EVV Visit Automation Cron Run</h3>
			<p><strong>Run time:</strong> #dateTimeFormat(arguments.summary.ranAt, "yyyy-mm-dd HH:nn:ss")#</p>
			<p><strong>Server:</strong> #envLabel#</p>
			<cfif hasFatal>
				<p style="color: ##c00;"><strong>Fatal error:</strong> #arguments.fatalError.message#</p>
				<cfif structKeyExists(arguments.fatalError, "detail") AND len(arguments.fatalError.detail)>
					<p><strong>Detail:</strong> #arguments.fatalError.detail#</p>
				</cfif>
			</cfif>
			<table border="1" cellpadding="6" cellspacing="0" style="border-collapse: collapse;">
				<tr><td>Pending jobs found</td><td>#arguments.summary.jobsFound#</td></tr>
				<tr><td>Transmitted</td><td>#arguments.summary.transmitted#</td></tr>
				<tr><td>Status polls</td><td>#arguments.summary.polled#</td></tr>
				<tr><td>Completed</td><td>#arguments.summary.completed#</td></tr>
				<tr><td>Failed</td><td>#arguments.summary.failed#</td></tr>
			</table>
			<cfif structKeyExists(arguments.summary, "details") AND arrayLen(arguments.summary.details)>
				<h4>Details</h4>
				<ul>
					<cfloop array="#arguments.summary.details#" index="local.detailItem">
						<li>#htmlEditFormat(local.detailItem)#</li>
					</cfloop>
				</ul>
			</cfif>
			<cfif hasJobErrors>
				<h4 style="color: ##c00;">Errors</h4>
				<ul>
					<cfloop array="#arguments.summary.errors#" index="local.errorItem">
						<li>#htmlEditFormat(local.errorItem)#</li>
					</cfloop>
				</ul>
			</cfif>
			<cfif NOT hasActivity AND NOT hasFatal AND NOT hasJobErrors>
				<p>No pending EVV visit automation jobs required processing on this run.</p>
			</cfif>
			</cfoutput>
		</cfsavecontent>

		<cftry>
			<cfmail to="#mailTo#" from="error@myhomecarebiz.com" subject="#mailSubject#" type="html">
				<cfoutput>#local.mailBody#</cfoutput>
			</cfmail>
			<cfcatch type="any">
				<cflog file="EVVVisitAutomation" text="sendCronRunEmail failed: #cfcatch.message#">
			</cfcatch>
		</cftry>
	</cffunction>

</cfcomponent>
