Files
svc-api-x/v1/resources/instance_operation_default.cfc
2026-04-20 20:53:01 +03:00

329 lines
17 KiB
Plaintext
Raw Permalink 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/{instanceUid}/{svcOperationId}" hint="template for default instance operation. Legacy URL: /instanceOperations/default/{svcOperationId}">
<!--- *** вот похоже, что для deafult неправильно считается valueList (конкретно resourceRealm) --->
<!--- ну и как мне передать второй параметр для GET? --->
<cfsilent>
<cfimport prefix="m" taglib="../lib"/>
<cfset this.helper=CreateObject("component","lib.rest_api_helper")/>
</cfsilent>
<cffunction name="get" hint="Шаблон операции экземпляра"><!--- *** TODO проверка принадлежности тенанту --->
<cfargument name="instanceUid" type="string" required=true hint="type:guid|'default'"/>
<cfargument name="svcOperationId" type="string" required=true hint="type:integer"/>
<!--- <cfargument name="usrId" type="string" hint="type:integer; no need to provide this argument in request, it is injected by IAM"/> ---><!--- arguments.usrId неявно инжектируется из Application.cfm, а с ним contractId, contragentId ... --->
<cftry>
<cfif arguments.instanceUid NEQ 'default'>
<cfset this.helper.validateField(arguments, "instanceUid", "guid")/>
</cfif>
<cfset this.helper.validateField(arguments, "svcOperationId", "integer")/>
<cfcatch type="invalidParamValue">
<cfreturn representationOf(this.helper.formatBadRequestError(cfcatch)).withStatus(400)/>
</cfcatch>
</cftry>
<cfset var local={}/>
<cfquery name="local.qSvcOperation" result="local.result">
select
<m:field_set titleMapOut="local.titleMap" lengthOut="local.fieldCount">
<m:field title="ID" cfSqlType="CF_SQL_INTEGER">o.svc_operation_id</m:field>
<m:field>o.svc_id</m:field>
<m:field title="operation">o.operation</m:field>
<m:field title="URL">o.url</m:field>
<m:field title="Описание">o.descr</m:field>
<m:field title="Руководство">o.man</m:field>
</m:field_set>
from svc_operation o
where o.svc_operation_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationId#" null=#!isValid('integer',arguments.svcOperationId)#/>
</cfquery>
<cfif local.qSvcOperation.recordCount EQ 0>
<cfreturn representationOf(this.helper.formatMessage("Not Found")).withStatus(404)/>
</cfif>
<!--- получаем параметры из свежего стейта --->
<cfset local.instanceDataParams = {}/>
<cfset var currentState = {}/>
<cfquery name="local.qState" result="local.result">
select instance_data->'params' as params,
instance_data::text as instance_data
from instance_state
where instance_uid=<cfqueryparam cfsqltype="cf_sql_other" value="#arguments.instanceUid#" null=#!isValid('guid',arguments.instanceUid)#/>
order by version desc
limit 1
</cfquery>
<cfif local.qState.recordCount GT 0>
<cftry>
<cfset var currentState = deserializeJson(local.qState.instance_data)/>
<cfset local.instanceDataParams = deserializeJson(local.qState.params)/>
<cfcatch type="ANY">
<cfdump var=#cfcatch#/><cfabort/>
</cfcatch>
</cftry>
</cfif>
<cfquery name="local.qCfsParam" result="local.result">
select
<m:field_set titleMapOut="local.cfsParamMap" lengthOut="local.fieldCount">
<m:field title="ID" cfSqlType="CF_SQL_INTEGER">p.svc_operation_cfs_param_id</m:field>
<!--- <m:field cfSqlType="CF_SQL_INTEGER">p.svc_operation_id</m:field> --->
<m:field>p.svc_operation_cfs_param</m:field>
<m:field>p.label</m:field>
<m:field>p.data_type</m:field>
<m:field>null as data_descriptor</m:field>
<!--- стоит помнить, что formatter выполняется при окончательном форматировании резалтсета в json --->
<m:field formatter=#(x)=>((isArray(x) OR isEmpty(x))? x : listToArray(x))#> p.value_list</m:field><!--- ниже мы обрабатываем резалтсет и в случае, когда подразумевается список, заменяем пустое значение пустым массивом, который интерпретируется как пустой список (по крайней мере не пользуемся специальным текстовым значением) --->
<m:field <!--- formatter=#function(x){return listToArray(generateValueList(x));}# --->>p.func</m:field><!--- *** отчаявшись сделать красиво --->
<m:field>p.ref_svc_id</m:field>
<m:field formatter=#request.castToBool#>p.is_required</m:field>
<m:field formatter=#request.castToBool#>p.is_hidden</m:field>
<m:field formatter=#request.castToBool#>p.is_disabled</m:field>
<m:field>p.default_value</m:field><!--- can be overridden by default_compute --->
<m:field>p.default_compute</m:field>
<m:field>p.regex</m:field>
<m:field>p.unique_scope</m:field>
<m:field>p.maxlength</m:field>
<m:field>p.minlength</m:field>
<m:field>p.maxvalue</m:field>
<m:field>p.minvalue</m:field>
<m:field>p.descr</m:field>
<m:field>p.man</m:field>
<m:field>p.sort</m:field>
<m:field>p.nested_ref</m:field>
<!--- <m:field>p.config::text as config</m:field> --->
<m:field>p.config::text</m:field><!--- в таком виде конфиг доступен для кода, но не выводится в респонсе --->
<m:field>null as nested_ref_data</m:field>
<m:field>p.state_path</m:field>
<m:field>p.depends_on_cfs_params</m:field>
<m:field>p.expression</m:field>
<m:field formatter=#request.castToBool#>(
select 1 from svc_operation_cfs_param m
join svc_operation om on (m.svc_operation_id=om.svc_operation_id)
join svc_operation oc on (om.svc_id=oc.svc_id AND om.operation='modify' AND oc.operation='create')
join svc_operation_cfs_param c on (oc.svc_operation_id=c.svc_operation_id AND m.svc_operation_cfs_param=c.svc_operation_cfs_param)
where c.svc_operation_cfs_param_id=p.svc_operation_cfs_param_id
order by c.svc_operation_cfs_param_id
limit 1
) as is_modifiable</m:field>
<m:field formatter=#request.castToBool#>p.is_sensitive</m:field>
</m:field_set>
from svc_operation_cfs_param p
where p.svc_operation_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.svcOperationId#" null=#!isValid('integer',arguments.svcOperationId)#/>
AND p.is_actual
order by p.sort, p.svc_operation_cfs_param_id
</cfquery>
<!--- тут разорвана обработка, ниже есть повторный проход по параметрам --->
<cfloop query="local.qCfsParam">
<cfif len(local.qCfsParam.func)><!--- приоритет отдается функции перед списком значений--->
<cfset var args = {
"functionName":"#local.qCfsParam.func#",
"svcId":#local.qSvcOperation.svc_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/><!--- *** не очень красиво перетирать query, но делаем это довольно часто --->
</cfif>
<!--- <cfdump var=#local.qCfsParam.value_list# abort=true/> --->
<cfelseif len(local.qCfsParam.nested_ref)><!--- *** спрашивается, зачем нам изгаляться и возвращать замыкание, когда нам нужен json --->
<!--- <cfset var fNestedRef = generateClosure(local.qCfsParam.nested_ref, local.qCfsParam.config)/> --->
<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"
/>
</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"><!--- *** частичное дублирование с instance_operation_cfs_param.processMap --->
<!--- без полей инстанса, только шаблон --->
<cfquery name="local.qSubparam">
select
sp.svc_operation_cfs_param_id
,sp.svc_operation_cfs_subparam_id
,sp.svc_operation_cfs_subparam
,sp.label
,sp.data_type
,sp.value_list
,sp.depends_on_cfs_params
,sp.expression
,sp.is_required
,sp.is_hidden
,sp.is_disabled
,sp.descr
,sp.man
,sp.default_value
,sp.minlength
,sp.maxlength
,sp.regex
,sp.sort
,sp.is_sensitive
,sp.ref_svc_id
,sp.unique_scope
,sp.default_compute
,sp.func
from svc_operation_cfs_subparam sp
where sp.svc_operation_cfs_param_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#svc_operation_cfs_param_id#"/>
order by sp.sort
</cfquery>
<cfset var mapDescriptor=structNew("linked")/>
<!--- *** выдавать на фронт expression нам ни к чему, это временное явление --->
<cfloop query="local.qSubparam">
<cfif len(local.qSubparam.func)><!--- приоритет отдается функции перед списком значений--->
<cfset var args = {
"functionName":"#local.qSubparam.func#",
"svcId":#local.qSvcOperation.svc_id#,
"contractId":#arguments.contractId#
}/>
<cfinvoke component="instance_operation_cfs_param" method="generateValueList" argumentCollection=#args# returnVariable="local.qSubparam.value_list"/>
<cfif len(trim(local.qSubparam.value_list)) AND listLen(local.qSubparam.value_list) EQ 1>
<cfset local.qSubparam.default_value=local.qSubparam.value_list/><!--- *** не очень красиво перетирать query, но делаем это довольно часто --->
</cfif>
</cfif>
<cfif (len(local.qSubparam.func) OR len(local.qSubparam.expression) )
AND len(local.qSubparam.value_list) EQ 0>
<cfset local.qSubparam.value_list = []/>
</cfif>
<cfset var def = computeDefault(local.qSubparam.default_compute, local.qSubparam.default_value, qSvcOperation.svc_id, arguments.usrId)/>
<!--- *** пока не пытаемся восстановить значения из стейта
<cfif len(local.qSubparam.default_value) EQ 0>
<!--- и вот наконец мы извлекаем старое значение из стейта --->
<cfset var paramName = this.helper.snake2camel(local.qSubparam.svc_operation_cfs_subparam)/>
<cfif structKeyExists(local.instanceDataParams, paramName)>
<cfset var oldValue = structFind(local.instanceDataParams, paramName)/>
<cfset def = isStruct(oldValue) ? serializeJson(oldValue) : oldValue/> <!--- потому что передаем строку, а не объект --->
</cfif>
</cfif> --->
<cfset structInsert(mapDescriptor, local.qSubparam.svc_operation_cfs_subparam,
{
"svcOperationCfsSubparamId":"#local.qSubparam.svc_operation_cfs_subparam_id#",
"svcOperationCfsSubparam":"#local.qSubparam.svc_operation_cfs_subparam#",
"label":"#local.qSubparam.label#",
"dataType":"#local.qSubparam.data_type#",
"valueList":"#local.qSubparam.value_list#",
"dependsOnCfsParams":"#local.qSubparam.depends_on_cfs_params#",
"expression":"#local.qSubparam.expression#",
"minlength":"#local.qSubparam.minlength#",
"maxlength":"#local.qSubparam.maxlength#",
"regex":"#local.qSubparam.regex#",
"man":"#local.qSubparam.man#",
"descr":"#local.qSubparam.descr#",
"defaultValue":"#def#",
"isRequired":#request.castToBool(local.qSubparam.is_required)#,
"isHidden":#request.castToBool(local.qSubparam.is_hidden)#,
"isHidden":#request.castToBool(local.qSubparam.is_disabled)#,
"isSensitive":#request.castToBool(local.qSubparam.is_sensitive)#,
"refSvcId":#local.qSubparam.ref_svc_id#,
"uniqueScope":"#local.qSubparam.unique_scope#",
<!--- "defaultCompute":"#local.qSubparam.func#",
"func":"#local.qSubparam.default_compute#", --->
"isModifiableDefinition":false
}, true
)
/><!--- *** вообще-то передавать в дескриптор func и default_compute не нужно, это для не забыть --->
</cfloop>
<cfset local.qCfsParam.data_descriptor = mapDescriptor/>
</cfif><!---/map --->
</cfloop>
<!--- <cfdump var=#local.qCfsParam# abort=true/> --->
<!--- <cfset queryDeleteColumn(local.qCfsParam, "func")/> --->
<cfset var out=structNew("linked")/>
<cfset "out.queryDurationMs"=getTickCount() - request.startTickCount/>
<cfset "out.svcOperation" = this.helper.appendRecord(
structNew("linked"), "", local.titleMap, local.qSvcOperation, this.helper.snake2camel
)/>
<cfset "out.svcOperation.cfsParams"=[]/>
<!--- подставляем значение из стейта, если оно там есть (некорректно, потому что в стейте RFS параметры) --->
<cfloop query=#local.qCfsParam#>
<cfset local.qCfsParam.default_value = computeDefault(local.qCfsParam.default_compute, local.qCfsParam.default_value, qSvcOperation.svc_id, arguments.usrId)/><!--- *** не очень хорошо модифицировать запрос --->
<cfif len(local.qCfsParam.default_value) EQ 0>
<!--- и вот наконец мы извлекаем старое значение из стейта --->
<cfset var paramName = this.helper.snake2camel(local.qCfsParam.svc_operation_cfs_param)/>
<cfif structKeyExists(local.instanceDataParams, paramName)>
<cfset var oldValue = structFind(local.instanceDataParams, paramName)/>
<cfset local.qCfsParam.default_value = isStruct(oldValue) ? serializeJson(oldValue) : oldValue/> <!--- потому что передаем строку, а не объект --->
</cfif>
</cfif>
<cfset var rec = this.helper.appendRecord(structNew("linked"), "", local.cfsParamMap, local.qCfsParam, this.helper.snake2camel)/>
<!--- <cfdump var=#rec# abort=true/> --->
<cfset arrayAppend(out.svcOperation.cfsParams, rec)/>
</cfloop>
<cfset "out.runDurationMs"=getTickCount() - request.startTickCount/>
<cfreturn representationOf(out) />
</cffunction>
<!--- *** выглядит ужасно, но красивый вариант не придумывается ряд месяцев --->
<!--- *** Внимание! нужно учитывать скоуп уникальности --->
<cffunction name="computeDefault">
<cfargument name="defaultCompute"/>
<cfargument name="original"/>
<cfargument name="service_id" type="numeric"/>
<cfargument name="usr_id" type="numeric"/>
<cfswitch expression=#arguments.defaultCompute#>
<cfcase value="display_name">
<cfset var dnGenerator = CreateObject("component", "instance_ls")/>
<cfreturn dnGenerator.generateDefaultName(arguments.service_id,arguments.usr_id)/>
</cfcase>
<cfcase value="append_pseudorandom">
<cfset var tGenerator = new lib.TokenGenerator()/>
<cfreturn "#arguments.original##lcase(tGenerator.nextToken(6))#"/><!--- почему-то длина отличается, чтобы получить 8 знаков, пишу 6 --->
</cfcase>
<cfcase value="display_name_pseudorandom">
<cfset var tGenerator = new lib.TokenGenerator()/>
<cfset var dnGenerator = CreateObject("component", "instance_ls")/>
<cfreturn "#dnGenerator.generateDefaultName(arguments.service_id,arguments.usr_id)##lcase(tGenerator.nextToken(6))#"/>
</cfcase>
<cfdefaultcase>
<cfreturn #arguments.original#/>
</cfdefaultcase>
</cfswitch>
</cffunction>
</cfcomponent>