Files
svc-api-x/v1/resources/instance_operation.cfc
T

323 lines
19 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<cfcomponent extends="taffy.core.resource" taffy:uri="/instanceOperations/{instanceOperationUid}">
<cfsilent>
<cfimport prefix="m" taglib="../lib"/>
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/><!---*** странно, почему мы его видим?---><!---вынести в апп?--->
</cfsilent>
<cffunction name="get" hint="Операция над экземпляром сервиса"><!--- *** TODO проверка принадлежности тенанту --->
<cfargument name="instanceOperationUid" type="string" required=true hint="type:guid"/>
<!--- arguments.specificationId injected implicitly --->
<cftry>
<cfset this.helper.validateField(arguments, "instanceOperationUid", "guid")/>
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
</cftry>
<cfset var out={}/>
<cfquery name="local.qInstanceOperation" result="local.result">
select
<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
<m:field>o.instance_operation_uid::text as instance_operation_uid</m:field>
<m:field>o.operation</m:field>
<!--- <m:field>o.resource_realm_id</m:field>
<m:field>r.resource_realm</m:field> --->
<m:field>o.instance_uid::text as instance_uid</m:field>
<m:field>to_char(o.dt_submit, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_submit</m:field>
<m:field>o.submit_result</m:field>
<m:field>o.error_log</m:field>
<m:field>o.note</m:field>
<m:field>to_char(o.dt_start, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_start</m:field>
<m:field>to_char(o.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_finish</m:field>
<m:field title="Длит. сек">extract('epoch' from coalesce(o.dt_finish,CURRENT_TIMESTAMP)- o.dt_start) as duration</m:field>
<m:field formatter=#request.castToBool#>o.is_successful</m:field>
<m:field>e.display_name</m:field>
<m:field>e.service_id</m:field>
<m:field>e.specification_item_id</m:field>
<m:field>to_char(o.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
<m:field>to_char(o.dt_updated, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_updated</m:field>
<m:field>o.updater_id</m:field>
<m:field>u.login as updater_login</m:field>
<m:field>u.shortname as updater_shortname</m:field>
<m:field>v.svc</m:field>
<m:field>so.man</m:field>
<m:field>so.svc_operation_id</m:field>
<m:field formatter=#request.castToBool#>coalesce((o.dt_start IS NOT NULL AND o.dt_finish IS NULL AND o.submit_result='201'),false) as is_in_progress</m:field>
<m:field formatter=#request.castToBool#>coalesce((o.dt_start IS NULL AND o.submit_result='201'),false) as is_pending</m:field>
</m:field_set>
from instance_operation o
join instance e on (o.instance_uid=e.instance_uid)
join specification_item i on (e.specification_item_id=i.specification_item_id)
join specification s on (i.specification_id=s.specification_id)
join contract d on (s.contract_id=d.contract_id)
join contragent k on (d.contragent_id=k.contragent_id)
join svc v on (e.service_id=v.svc_id)
join svc_operation so on (v.svc_id=so.svc_id AND o.operation=so.operation)
<!--- left outer join resource_realm r on (o.resource_realm_id=r.resource_realm_id) --->
left outer join usr u on (o.updater_id=u.usr_id)
where <!--- u.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.usrId#/> AND--->
o.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceOperationUid#" null=#!isValid('guid',arguments.instanceOperationUid)#/>
AND s.specification_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#arguments.specificationId#/>
</cfquery>
<!--- <cfdump var=#local.qInstanceOperation#/><cfabort/> --->
<cfif local.qInstanceOperation.recordCount EQ 0>
<cfreturn representationOf(this.helper.formatMessage("Not Found")).withStatus(404)/>
</cfif>
<!--- Чтобы не нагружать второй селект проверкой принадлежности клиенту, мы берем ключ не из аргументов, а из первого запроса --->
<cfquery name="local.qStage" result="local.result">
select
<m:field_set titleMapOut="local.stageMap" lengthOut="local.fieldCount">
<m:field>ios.instance_operation_stage_uid::text as instance_operation_stage_uid</m:field>
<m:field>ios.stage</m:field>
<m:field formatter=#request.castToBool#>ios.is_successful</m:field>
<m:field>to_char(ios.dt_start, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_start</m:field>
<m:field>to_char(ios.dt_finish, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_finish</m:field>
<m:field>round(extract('epoch' from coalesce(ios.dt_finish,CURRENT_TIMESTAMP)- ios.dt_start),1) as duration</m:field>
<m:field>ios.stage_msg</m:field>
</m:field_set>
from instance_operation_stage ios
where ios.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstanceOperation.instance_operation_uid#" null=#!isValid('guid',local.qInstanceOperation.instance_operation_uid)#/>
order by dt_start asc
</cfquery>
<cfquery name="local.qState" result="local.result">
select
<m:field_set titleMapOut="local.stateTitleMap" lengthOut="local.fieldCount">
<m:field title="instance_state_uid">st.instance_state_uid::text as instance_state_uid</m:field>
<m:field title="v.">st.version</m:field>
<m:field title="creator_id">st.creator_id</m:field>
<m:field title="dt_state">to_char(st.dt_state, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_state</m:field>
<m:field formatter=#request.castToBool#>st.is_test</m:field>
<m:field title="instance_operation_uid">st.instance_operation_uid::text as instance_operation_uid</m:field>
<m:field title="instance_data" formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field>
</m:field_set>
from instance_state st
where st.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstanceOperation.instance_operation_uid#" null=#!isValid('guid',local.qInstanceOperation.instance_operation_uid)#/>
order by st.version desc limit 1
</cfquery>
<cfquery name="local.qCurrentState" result="local.result">
select
<m:field_set titleMapOut="local.stateTitleMap" lengthOut="local.fieldCount">
<m:field title="instance_state_uid">st.instance_state_uid::text as instance_state_uid</m:field>
<m:field title="v.">st.version</m:field>
<m:field title="creator_id">st.creator_id</m:field>
<m:field title="dt_state">to_char(st.dt_state, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_state</m:field>
<m:field formatter=#request.castToBool#>st.is_test</m:field>
<m:field title="instance_operation_uid">st.instance_operation_uid::text as instance_operation_uid</m:field>
<m:field title="instance_data" formatter=#function(x){return (deserializeJson(x))}#>st.instance_data::text as instance_data</m:field>
</m:field_set>
from instance_state st
where st.instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#local.qInstanceOperation.instance_uid#" null=#!isValid('guid',local.qInstanceOperation.instance_uid)#/>
order by st.version desc limit 1
</cfquery>
<cfset var currentState = {}/>
<cftry>
<cfset currentState = deserializeJson(local.qCurrentState.instance_data)/>
<cfcatch type="ANY">
<!--- тут надо логировать ошибку ---><cfdump var=#cfcatch#/><cfabort/>
</cfcatch>
</cftry>
<cfquery name="local.qCfsParam" result="local.result">
select
<m:field_set titleMapOut="local.cfsParamTitleMap" lengthOut="fieldCount">
<m:field title="instance_operation_cfs_param_uid">iop.instance_operation_cfs_param_uid::text as instance_operation_cfs_param_uid</m:field>
<!--- <m:field title="instance_operation_uid">instance_operation_uid::text as instance_operation_uid</m:field> --->
<m:field>sop.svc_operation_cfs_param_id</m:field>
<!--- <m:field>iop.param_value</m:field> --->
<m:field>case when sop.is_sensitive then '********' else iop.param_value end as param_value </m:field>
<m:field>to_char(iop.dt_created, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as dt_created</m:field>
<m:field>iop.creator_id</m:field>
<m:field>sop.svc_operation_cfs_param</m:field>
<m:field>sop.label</m:field>
<m:field>sop.data_type</m:field>
<m:field>null as data_descriptor</m:field>
<m:field formatter=#(x)=>((isArray(x) OR isEmpty(x))? x : listToArray(x))#>sop.value_list</m:field>
<m:field>sop.ref_svc_id</m:field>
<m:field>sop.func</m:field>
<m:field>sop.nested_ref</m:field>
<m:field>sop.state_path</m:field>
<m:field>sop.expression</m:field>
<m:field>sop.depends_on_cfs_params</m:field>
<m:field formatter=#request.castToBool#>sop.is_required</m:field>
<m:field>sop.default_value</m:field>
<m:field>sop.regex</m:field>
<m:field>sop.unique_scope</m:field>
<m:field>sop.maxlength</m:field>
<m:field>sop.minlength</m:field>
<m:field>sop.maxvalue</m:field>
<m:field>sop.minvalue</m:field>
<m:field>sop.descr</m:field>
<m:field>sop.man</m:field>
<m:field>sop.sort</m:field>
<m:field>iop.note</m:field>
<m:field>sop.config::text as config</m:field>
<m:field>null as nested_ref_data</m:field><!--- placeholder for future data --->
<m:field formatter=#request.castToBool#>sop.is_sensitive</m:field>
</m:field_set>
from svc_operation_cfs_param sop
left outer join instance_operation_cfs_param iop
on (sop.svc_operation_cfs_param_id=iop.svc_operation_cfs_param_id
AND iop.instance_operation_uid = <cfqueryparam cfsqltype="cf_sql_other"
value="#local.qInstanceOperation.instance_operation_uid#"
null=#!isValid('guid',local.qInstanceOperation.instance_operation_uid)#/>
)
where sop.svc_operation_id = <cfqueryparam cfsqltype="cf_sql_integer"
value=#local.qInstanceOperation.svc_operation_id#
null=#!isValid('integer',local.qInstanceOperation.svc_operation_id)#/>
order by sop.sort, sop.svc_operation_cfs_param_id
</cfquery>
<cfloop query="local.qCfsParam">
<cfif len(func)><!--- приоритет отдается функции перед списком значений--->
<cfset var args = {
"functionName":"#func#",
"svcId":#local.qInstanceOperation.service_id#,
"contractId":#arguments.contractId#
}/> <!--- если появятся новые аргументы у новых функций, будем добавлять их сюда --->
<!--- <cfset local.qCfsParam.value_list=generateValueList(argumentCollection=#args#)/> --->
<cfinvoke component="instance_operation_cfs_param" method="generateValueList" argumentCollection=#args# returnVariable="local.qCfsParam.value_list"/>
<!--- можно перечислить именованные аргументы обычным порядком, но с коллекцией потенциально более гибко --->
<cfif len(trim(local.qCfsParam.value_list)) AND listLen(local.qCfsParam.value_list) EQ 1>
<cfset local.qCfsParam.default_value=local.qCfsParam.value_list/>
</cfif><!--- преобразование к массиву выполняется при финальном форматировании резалтсета --->
<cfelseif len(local.qCfsParam.nested_ref)>
<cfset var args = {
"functionName":"#local.qCfsParam.nested_ref#",
"config":"#local.qCfsParam.config#"
}/>
<cfinvoke component="instance_operation_cfs_param" method="generateClosure" argumentCollection=#args# returnVariable="local.fNestedRef"/>
<cfset local.qCfsParam.nested_ref_data = local.fNestedRef() />
<cfelseif len(local.qCfsParam.state_path) AND isStruct(currentState)><!--- deserializeJson может вернуть пустую строку вместо структуры,
по крайней мере, если получает поле пустого резалтсета --->
<cfset var args = {
"state":#currentState#,
"path":"#local.qCfsParam.state_path#"
}/>
<cfinvoke component="instance_operation_cfs_param"
method="getListFromState"
argumentCollection=#args#
returnVariable="local.qCfsParam.value_list"
/>
<cfelseif len(local.qCfsParam.expression) AND len(local.qCfsParam.instance_operation_cfs_param_uid)>
<cfset local.qCfsParam.value_list = createObject("component","lib.expression_parser")
.eval(
local.qCfsParam.expression,
{/*context*/
component:"resources.instance_operation_cfs_param",
keys:{"instanceOperationCfsParamUid"=#local.qCfsParam.instance_operation_cfs_param_uid#},
params:{}
}
)
/>
</cfif>
<cfif (len(local.qCfsParam.func) OR len(local.qCfsParam.expression) OR len(local.qCfsParam.state_path) OR len(local.qCfsParam.nested_ref))
AND len(local.qCfsParam.value_list) EQ 0>
<cfset local.qCfsParam.value_list = []/>
</cfif>
<!--- *** боюсь, где-то тут нужно будет делать вычисления субпараметров --->
<cfif data_type EQ "map" OR data_type EQ "map-fixed" OR data_type EQ "array-map-fixed">
<cfinvoke component="instance_operation_cfs_param" method="processMap" qParam=#local.qCfsParam#/>
</cfif><!---/map --->
</cfloop>
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
<cfset "out.instanceOperation" = this.helper.appendRecord(
structNew("linked"), "", local.titleMap, local.qInstanceOperation, this.helper.snake2camel
)/>
<cfset "out.instanceOperation.cfsParams"=[]/>
<cfloop query=#local.qCfsParam#>
<cfset arrayAppend(out.instanceOperation.cfsParams, this.helper.appendRecord(structNew("linked"), "", local.cfsParamTitleMap, local.qCfsParam, this.helper.snake2camel))/>
</cfloop>
<cfset "out.instanceOperation.stages"=[]/>
<cfloop query=#local.qStage#>
<cfset arrayAppend(out.instanceOperation.stages, this.helper.appendRecord(structNew("linked"), "", local.stageMap, local.qStage, this.helper.snake2camel))/>
</cfloop>
<cfset "out.instanceOperation.state"=(local.qState.recordCount GT 0) ? this.helper.appendRecord(structNew("linked"), "", local.stateTitleMap, local.qState, this.helper.snake2camel) : {}/>
<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
<cfreturn representationOf(out) />
</cffunction>
<!--- *** копия кода из deck-tool inst_op.cfc, коллекции статических методов операции инстанса --->
<cffunction name="validateCfsParams" returntype="string">
<cfargument name="instanceOperationUid" type="guid" required="true"/>
<!--- проверить CFS параметры в совокупности. Нужно, поскольку параметры не всегда могут быть проверены по одному,
пример - проверка уникальности парамера в рамках платформы, которая сама представлена параметром операции --->
<!--- пока сюда попали только две проверки уникальности --->
<cfset var local={}/>
<cfquery name="local.qSvcOperationParam">
select sop.svc_operation_cfs_param_id, sop.svc_operation_cfs_param, sop.unique_scope
from svc_operation_cfs_param sop
join instance_operation_cfs_param iop on (sop.svc_operation_cfs_param_id=iop.svc_operation_cfs_param_id)
where iop.instance_operation_uid = <cfqueryparam cfsqltype="CF_SQL_OTHER" value=#arguments.instanceOperationUid#/>
</cfquery>
<cfquery name="local.qInstance">
select io.instance_uid
from instance_operation io
join instance_operation_cfs_param iop on (io.instance_operation_uid=iop.instance_operation_uid)
where iop.instance_operation_uid = <cfqueryparam cfsqltype="CF_SQL_OTHER" value=#arguments.instanceOperationUid#/>
</cfquery>
<cfloop query="local.qSvcOperationParam">
<cfswitch expression=#local.qSvcOperationParam.unique_scope#>
<cfcase value="parent">
<!--- сверяем параметр операции с параметрами инстансов --->
<cfquery name="local.qUnique">
SELECT p.param_value, sp.svc_operation_cfs_param, re.display_name
FROM instance_operation o
JOIN instance_operation_cfs_param p on (o.instance_operation_uid=p.instance_operation_uid)
--JOIN svc s on (e.service_id=s.svc_id)
-- svc_operation_cfs_param нужен, чтобы убрать совпадение параметра с самим собой, это не является ошибкой
-- остается непонятным, будет ли работать проверка между create/modify, у них разные id, а в параметрах инстанса одно и то же
-- возможно, следует сравнивать просто по имени
--join instance_operation_cfs_param op on (p.instance_operation_cfs_param_uid=op.instance_operation_cfs_param_uid)
join svc_operation_cfs_param sp on (p.svc_operation_cfs_param_id=sp.svc_operation_cfs_param_id)
JOIN instance_operation_cfs_param p2 on (p.instance_operation_uid=p2.instance_operation_uid) -- sibling params
JOIN instance re on (p2.param_value=re.instance_uid::text /*не проверяем тип параметра - ссылка, не проверяем соответствие теп*/) -- referenced instance
-- down back
LEFT OUTER JOIN instance_cfs_param p3 on (re.instance_uid::text=p3.param_value /*добавить селективности*/) -- referencing params from sibling instance
LEFT OUTER JOIN instance_cfs_param p4 on (p3.instance_uid=p4.instance_uid AND sp.svc_operation_cfs_param=p4.param) -- same param names from sibling instance
WHERE p.param_value=p4.param_value
AND p.instance_operation_uid=<cfqueryparam cfsqltype="cf_sql_other" value=#arguments.instanceOperationUid#/>
AND p.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value=#local.qSvcOperationParam.svc_operation_cfs_param_id#/>
AND p4.instance_uid <> <cfqueryparam cfsqltype="cf_sql_other" value=#qInstance.instance_uid# null=#!isValid("guid",qInstance.instance_uid)#/>
AND (select cast(st.instance_data->>'isDeleted' as boolean) from instance_state st where st.instance_uid=p4.instance_uid order by version desc limit 1) IS NOT TRUE
</cfquery>
<cfif qUnique.recordCount GT 0>
<cfreturn '#local.qUnique.svc_operation_cfs_param# = "#local.qUnique.param_value#" is not unique in the parent instance (#local.qUnique.display_name#) scope'/>
<!--- *** неточность: мы показываем только первую коллизию, нет полной уверенности, что рядом не окажется второй, хотя представить себе такую ситуацию достаточно сложно --->
</cfif>
<!--- <cfdump var=#qUnique#/><cfabort/> --->
</cfcase>
<cfdefaultcase></cfdefaultcase>
</cfswitch>
</cfloop>
<cfreturn ""/>
<!--- некрасиво, но просто - пустое сообщение об ошибке означает успешную проверку (изящнее не придумал) --->
</cffunction>
</cfcomponent>