initial
This commit is contained in:
+18
-15
@@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
<cfset this.defaultdatasource = this.datasource/>
|
<cfset this.defaultdatasource = this.datasource/>
|
||||||
<cfset request.DS = "#this.datasource#">
|
<cfset request.DS = "#this.datasource#">
|
||||||
|
<cfset request.language="">
|
||||||
<!--- кажется, нужно инициализировать датасорцы в псевдоконструкторе - onRequest не получается --->
|
<!--- кажется, нужно инициализировать датасорцы в псевдоконструкторе - onRequest не получается --->
|
||||||
<!--- <cfset this.datasources["#this.datasource#"]=getDS("#this.datasource#","ds_#this.datasource#")/> --->
|
<!--- <cfset this.datasources["#this.datasource#"]=getDS("#this.datasource#","ds_#this.datasource#")/> --->
|
||||||
|
|
||||||
@@ -127,20 +128,20 @@
|
|||||||
Вообще не следует ничего блокировать на время сетевого взаимодействия
|
Вообще не следует ничего блокировать на время сетевого взаимодействия
|
||||||
Нижеследующая, на первый взгляд, странная конструкция ровно для этого придумана
|
Нижеследующая, на первый взгляд, странная конструкция ровно для этого придумана
|
||||||
--->
|
--->
|
||||||
<cfoutput>onRequest:check for cert started #(getTickCount()-request.startTickCount)#</cfoutput>
|
<!--- <cfoutput>onRequest:check for cert started #(getTickCount()-request.startTickCount)#</cfoutput> --->
|
||||||
<cfset var idpCertificateExists = false/>
|
<cfset var idpCertificateExists = false/>
|
||||||
<cflock scope="application" type="readonly" timeout=1>
|
<cflock scope="application" type="readonly" timeout=1>
|
||||||
<cfset request.APP_NAME=this.Name/>
|
<cfset request.APP_NAME=this.Name/>
|
||||||
<cfset idpCertificateExists = structKeyExists(application,"idpCertificate")/>
|
<cfset idpCertificateExists = structKeyExists(application,"idpCertificate")/>
|
||||||
</cflock>
|
</cflock>
|
||||||
<cfif NOT idpCertificateExists>
|
<cfif NOT idpCertificateExists>
|
||||||
Obtain IDP Certificate (onRequest)
|
<!--- Obtain IDP Certificate (onRequest) --->
|
||||||
<cfset var idpCertificate = getIdpCertificate(this.idpCertUrl) />
|
<cfset var idpCertificate = getIdpCertificate(this.idpCertUrl) />
|
||||||
<cflock scope="application" type="exclusive" timeout=1>
|
<cflock scope="application" type="exclusive" timeout=1>
|
||||||
<cfset application.idpCertificate = idpCertificate/>
|
<cfset application.idpCertificate = idpCertificate/>
|
||||||
</cflock>
|
</cflock>
|
||||||
</cfif>
|
</cfif>
|
||||||
<cfoutput>onRequest:check for cert finished #(getTickCount()-request.startTickCount)#</cfoutput>
|
<!--- <cfoutput>onRequest:check for cert finished #(getTickCount()-request.startTickCount)#</cfoutput> --->
|
||||||
|
|
||||||
|
|
||||||
<cfset this.redirect_uri = "https://#CGI.SERVER_NAME#/#CGI.SCRIPT_NAME#" />
|
<cfset this.redirect_uri = "https://#CGI.SERVER_NAME#/#CGI.SCRIPT_NAME#" />
|
||||||
@@ -152,24 +153,26 @@
|
|||||||
|
|
||||||
<!--- SSO --->
|
<!--- SSO --->
|
||||||
<cflock scope="session" type="exclusive" timeout="3">
|
<cflock scope="session" type="exclusive" timeout="3">
|
||||||
<!--- --->
|
<!---
|
||||||
<cfdump var=#session#/>
|
<cfdump var=#session#/>
|
||||||
<cfoutput>#(getTickCount()-request.startTickCount)#</cfoutput>
|
<cfoutput>#(getTickCount()-request.startTickCount)#</cfoutput>--->
|
||||||
<!---
|
<!---
|
||||||
Это первая проба, с сессией
|
Это первая проба, с сессией
|
||||||
Когда протухнет сессия, мы можем пойти к IDP и повторно аутентифицироваться. Поскольку у нас сохраняются куки IDP, его сессия, возможно, будет жива. Если мы обновляли сессию IDP в другом приложении SSO, по всей видимости, она будет продолжаться. Если мы видим, что сессия IDP состарилась и хочет протухнуть, мы можем ее освежить и получить новый access token (который мы пока не используем - мы пользуемся ответом IDP) Наверное, правильно обходиться для этих целей вообще без сессии *** --->
|
Когда протухнет сессия, мы можем пойти к IDP и повторно аутентифицироваться. Поскольку у нас сохраняются куки IDP, его сессия, возможно, будет жива. Если мы обновляли сессию IDP в другом приложении SSO, по всей видимости, она будет продолжаться. Если мы видим, что сессия IDP состарилась и хочет протухнуть, мы можем ее освежить и получить новый access token (который мы пока не используем - мы пользуемся ответом IDP) Наверное, правильно обходиться для этих целей вообще без сессии *** --->
|
||||||
<!---
|
<!---
|
||||||
сделать логаут
|
проверить sso
|
||||||
сделать отображение ошибки IDP?
|
? сделать рефреш
|
||||||
|
? сделать логаут
|
||||||
|
? сделать отображение ошибки IDP
|
||||||
+ сделать получение сертификата
|
+ сделать получение сертификата
|
||||||
сделать рефреш
|
|
||||||
--->
|
--->
|
||||||
<cfif structKeyExists(session, "auth")>
|
<cfif structKeyExists(session, "auth")>
|
||||||
<cfset request.auth = structCopy(session.auth)/>
|
<cfset request.auth = structCopy(session.auth)/>
|
||||||
<!--- тут нужно сделать проверку и рефреш токена --->
|
<!--- тут нужно сделать проверку и рефреш токена --->
|
||||||
сессия есть
|
<!--- сессия есть --->
|
||||||
<cfelse>
|
<cfelse>
|
||||||
сессии нет
|
<!--- сессии нет --->
|
||||||
<cfset session.auth_state=createGUID().toString()/>
|
<cfset session.auth_state=createGUID().toString()/>
|
||||||
|
|
||||||
<cfscript>
|
<cfscript>
|
||||||
@@ -183,9 +186,9 @@
|
|||||||
может быть, KeyCloak так защищается от циклических редиректов
|
может быть, KeyCloak так защищается от циклических редиректов
|
||||||
Не очень понятно, как предотвратить повторный запрос (можно, конечно, использовать сессию, но странно)
|
Не очень понятно, как предотвратить повторный запрос (можно, конечно, использовать сессию, но странно)
|
||||||
*/
|
*/
|
||||||
writeOutput(getTickCount()-request.startTickCount);
|
//writeOutput(getTickCount()-request.startTickCount);
|
||||||
if(structKeyExists(url, "code")) { //обрабатываем редирект от IDP *** наличие поля code в URL введет нас в заблуждение, но эvar = data, label =то только без валидной сессии
|
if(structKeyExists(url, "code")) { //обрабатываем редирект от IDP *** наличие поля code в URL введет нас в заблуждение, но эvar = data, label =то только без валидной сессии
|
||||||
dump(var = url.code, label = "URL.code");
|
//dump(var = url.code, label = "URL.code");
|
||||||
// Request access token from idp with the
|
// Request access token from idp with the
|
||||||
// authorization code that we got via the URL
|
// authorization code that we got via the URL
|
||||||
//aFormFields = [];
|
//aFormFields = [];
|
||||||
@@ -196,13 +199,13 @@
|
|||||||
resp = idp.makeAccessTokenRequest(url.code/*, aFormFields*/);
|
resp = idp.makeAccessTokenRequest(url.code/*, aFormFields*/);
|
||||||
writeOutput("idp.makeAccessTokenRequest Done");
|
writeOutput("idp.makeAccessTokenRequest Done");
|
||||||
writeOutput(getTickCount()-request.startTickCount);
|
writeOutput(getTickCount()-request.startTickCount);
|
||||||
dump(resp);
|
//dump(resp);
|
||||||
data = deserializeJson(resp.content);
|
data = deserializeJson(resp.content);
|
||||||
//writedump(data);
|
//writedump(data);
|
||||||
// Print full response from idp
|
// Print full response from idp
|
||||||
dump(var = data, label = "makeAccessTokenRequest - Response from idp");
|
//dump(var = data, label = "makeAccessTokenRequest - Response from idp");
|
||||||
|
|
||||||
echo('<a href="./index.cfm?refresh=#data.refresh_token#">Refresh token #data.refresh_token#</a>');
|
//echo('<a href="./index.cfm?refresh=#data.refresh_token#">Refresh token #data.refresh_token#</a>');
|
||||||
|
|
||||||
session.auth.expires_in = data.expires_in;
|
session.auth.expires_in = data.expires_in;
|
||||||
session.auth.refresh_expires_in = data.refresh_expires_in;
|
session.auth.refresh_expires_in = data.refresh_expires_in;
|
||||||
|
|||||||
+3
-1
@@ -49,10 +49,12 @@ function timeFmt(date, sFormat = 'HH:MM'){
|
|||||||
}
|
}
|
||||||
request.timeFmt = timeFmt;
|
request.timeFmt = timeFmt;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////
|
||||||
function usrAuthenticated() {
|
function usrAuthenticated() {
|
||||||
return request.usr_id GT 0 AND NOT (request.usr_id EQ request.ANONYMOUS_USR_ID);
|
return structKeyExists(request, "auth");
|
||||||
}
|
}
|
||||||
request.usrAuthenticated = usrAuthenticated;
|
request.usrAuthenticated = usrAuthenticated;
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
|
||||||
function filterOn(filter) {
|
function filterOn(filter) {
|
||||||
return (isStruct(filter) AND NOT structIsEmpty(filter));
|
return (isStruct(filter) AND NOT structIsEmpty(filter));
|
||||||
|
|||||||
@@ -10,48 +10,6 @@
|
|||||||
<ul id="mainmenu">
|
<ul id="mainmenu">
|
||||||
<cfif request.usrAuthenticated()></cfif>
|
<cfif request.usrAuthenticated()></cfif>
|
||||||
|
|
||||||
<li class="menu-title">Реестры</li>
|
|
||||||
|
|
||||||
<c:menu_item acl="" page="deal_ls.cfm" label="Сделки"/>
|
|
||||||
<c:menu_item acl="" page="contract_ls.cfm" label="Договоры"/>
|
|
||||||
<c:menu_item acl="" page="additional_agreement_ls.cfm" label="Доп. соглашения"/>
|
|
||||||
|
|
||||||
<!---
|
|
||||||
<c:menu_item acl="" page="specification_ls.cfm" label="Спецификации"/>
|
|
||||||
<c:menu_item acl="" page="agreement_ls.cfm" label="Сделки"/>
|
|
||||||
<c:menu_item acl="" page="specification_item_ls.cfm" label="Экземпляры услуг"/>--->
|
|
||||||
<li class="menu-title">Отчеты</li>
|
|
||||||
<c:menu_item acl="" page="payg.cfm" label="PAYG"/>
|
|
||||||
<!--- <c:menu_item acl="" page="detail_daily_rpt.cfm" label="Детализация по дням"/>
|
|
||||||
<c:menu_item acl="" page="income_daily_rpt.cfm" label="Выручка по дням"/>
|
|
||||||
<c:menu_item acl="" page="income_monthly_rpt.cfm" label="Выручка по месяцам"/> --->
|
|
||||||
|
|
||||||
<li class="menu-title">Справочники</li>
|
|
||||||
|
|
||||||
<!--- <c:menu_item acl="" page="svc_ls.cfm" label="Сервисы"/> --->
|
|
||||||
<c:menu_item acl="" page="contragent_ls.cfm" label="Контрагенты"/>
|
|
||||||
<!--- <c:menu_item acl="" page="measure_ls.cfm" label="Единицы измерения"/>
|
|
||||||
|
|
||||||
<c:menu_item acl="" page="usr_ls.cfm" label="Пользователи"/>
|
|
||||||
--->
|
|
||||||
<!--- <li class="menu-title">Пользователь</li>
|
|
||||||
|
|
||||||
<cfif request.usrAuthenticated()>
|
|
||||||
<cfquery name="qUsr" datasource="#request.DS#">
|
|
||||||
select shortname/*, login, firstname, middlename, lastname*/
|
|
||||||
from usr
|
|
||||||
where usr_id=<cfqueryparam cfsqltype="CF_SQL_INTEGER" value=#request.usr_id#/>
|
|
||||||
</cfquery>
|
|
||||||
<li class="menu-item-noaction">#qUsr.shortname#</li> <!---#request.usr_id#--->
|
|
||||||
<c:menu_item acl="" page="password.cfm" queryString="#ATTRIBUTES.fwx#" label="Смена пароля"/>
|
|
||||||
<c:menu_item acl="" page="logout.cfm" queryString="target_page=#URLEncodedFormat(ATTRIBUTES.thisUrl)#" label="Выход"/>
|
|
||||||
|
|
||||||
<cfelse>
|
|
||||||
<c:menu_item acl="" page="saml/login.cfm" queryString="target_page=#URLEncodedFormat(ATTRIBUTES.thisUrl)#" label="Вход через WebSSO"/>
|
|
||||||
<c:menu_item acl="" page="login.cfm" queryString="#ATTRIBUTES.fwx#" label="Вход"/>
|
|
||||||
<c:menu_item acl="" page="logout.cfm" queryString="target_page=#URLEncodedFormat(ATTRIBUTES.thisUrl)#" label="Выход"/>
|
|
||||||
</cfif> --->
|
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</cfoutput>
|
</cfoutput>
|
||||||
|
|||||||
@@ -5,3 +5,4 @@
|
|||||||
По истечении установленного времени (скажем, половины срока жизни токена) идем к IDP за рефрешем
|
По истечении установленного времени (скажем, половины срока жизни токена) идем к IDP за рефрешем
|
||||||
Сертификат открытого ключа можно каждый раз получать от IDP.
|
Сертификат открытого ключа можно каждый раз получать от IDP.
|
||||||
Запрос к IDP идет только для освежения сессии, а не на каждый запрос. Логаут на IDP не проверяется при каждом запросе.
|
Запрос к IDP идет только для освежения сессии, а не на каждый запрос. Логаут на IDP не проверяется при каждом запросе.
|
||||||
|
<cflocation url="payg.cfm"/>
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<!---Замещает любую переменную (*** небезопасно) тега layout:page --->
|
||||||
|
<!---Тег layout:page проглатывает собственный контент, и вместо него после закрытия выводит разметку на основании атрибутов (при открытии тега он формирует переменные - фрагменты разметки - по шаблону) и вложенных тегов layout:attribute (которые по закрытии тега на самом деле перезаписывают переменные в layout:page).
|
||||||
|
Вложенные теги удобны, чтобы передавать читабельную разметку, а не строку в кавычках.--->
|
||||||
|
<cfparam name="ATTRIBUTES.name"/><!---имя переменной layout:page, чаще всего title, controls--->
|
||||||
|
<!--- ATTRIBUTES.value --->
|
||||||
|
<cfparam name="ATTRIBUTES.append" default=""/><!---before,after--->
|
||||||
|
|
||||||
|
<cfif thisTag.executionMode IS "end">
|
||||||
|
|
||||||
|
<cfassociate basetag="cf_page"/>
|
||||||
|
<cfset layoutTemplate=getBaseTagData('cf_page',1)/>
|
||||||
|
|
||||||
|
<cfif structKeyExists(ATTRIBUTES, "value")>
|
||||||
|
<cfset value=ATTRIBUTES.value/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset value=thisTag.generatedContent/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!---ниже в комментариях предполагается, что мы меняем фрагмент разметки, например, controls, но на самом деле это может быть любая переменная тега layout:page--->
|
||||||
|
<cfif structKeyExists(layoutTemplate,"#ATTRIBUTES.name#") AND lcase(ATTRIBUTES.append) EQ "before">
|
||||||
|
<!---дописать в начало фрагмента разметки с именем name, сгенерированного layout:page, например, controls--->
|
||||||
|
<cfset "layoutTemplate.#ATTRIBUTES.name#" = value & layoutTemplate["#ATTRIBUTES.name#"]/>
|
||||||
|
<cfelseif structKeyExists(layoutTemplate,"#ATTRIBUTES.name#") AND lcase(ATTRIBUTES.append) EQ "after">
|
||||||
|
<!---дописать в конец фрагмента разметки с именем name,, сгенерированного layout:page, например, controls--->
|
||||||
|
<cfset "layoutTemplate.#ATTRIBUTES.name#" = layoutTemplate["#ATTRIBUTES.name#"] & value/>
|
||||||
|
<cfelse>
|
||||||
|
<!---заменить фрагмент разметки с именем name, сгенерированный layout:page--->
|
||||||
|
<cfset "layoutTemplate.#ATTRIBUTES.name#"= value/>
|
||||||
|
</cfif>
|
||||||
|
<cfset thisTag.generatedContent=""/>
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfimport prefix="m" taglib="../lib"/>
|
||||||
|
<cfimport prefix="c" taglib="../lib/controls"/>
|
||||||
|
<!---*** идея включить титул в метаданные была не совсем пустой, но когда титул определяется в селекте, это не слишком удобно --->
|
||||||
|
<cfassociate basetag="cf_grid_head" datacollection="headersArray"/>
|
||||||
|
<cfset envelope=getBaseTagData('cf_grid_head',1)/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.title" type="string" default="#envelope.ATTRIBUTES.titleMap[ATTRIBUTES.name].title#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.name" type="string"/><!--- Заголовок указвается на том поле, по которому будет сортировка, а не на том, которе отображается --->
|
||||||
|
<cfparam name="ATTRIBUTES.arrows" type="boolean" default="Yes"/>
|
||||||
|
|
||||||
|
<cfset attributeList="name,arrows,title"/>
|
||||||
|
|
||||||
|
<!--- В данном случае реализован подход: дополнительные атрибуты передаются вниз.
|
||||||
|
Другой вариант - создавать атрибут extra, в который писать суффикс QueryString.
|
||||||
|
Первый подход, использованный здесь, позволяет иметь доступ к дополнительным атрибутам, но непонятно, зачем - ведь их имена все равно неизвестны.
|
||||||
|
Очевидный минус этого подхода - возможный конфликт имен с именами существующих атрибутов --->
|
||||||
|
<cfset extraAttributes=structNew()/>
|
||||||
|
|
||||||
|
<!--- дополнительные атрибуты родительского тега--->
|
||||||
|
<cfif StructKeyExists(envelope, "extraAttributes") AND isStruct(envelope.extraAttributes)>
|
||||||
|
<cfloop collection=#envelope.extraAttributes# item="attr">
|
||||||
|
<cfif NOT listFindNoCase(attributeList, attr)><!--- проверка относительно локальных атрибутов, чтобы их не перетереть--->
|
||||||
|
<cfset val="#structFind(envelope.extraAttributes, attr)#"/>
|
||||||
|
<cfif IsSimpleValue(val)>
|
||||||
|
<cfset structInsert(extraAttributes, attr, val)/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfloop collection=#ATTRIBUTES# item="attr">
|
||||||
|
<cfif NOT listFindNoCase(attributeList, attr)>
|
||||||
|
<cfset val="#structFind(ATTRIBUTES, attr)#"/>
|
||||||
|
<cfif IsSimpleValue(val)>
|
||||||
|
<cfset structInsert(extraAttributes, attr, val, "Yes")/><!--- локальные атрибуты перетрут родительские --->
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
|
||||||
|
</cfsilent><cfif thisTag.executionMode is "end"><cfoutput>#ATTRIBUTES.title#
|
||||||
|
<cfif ATTRIBUTES.arrows><c:order_arrows sortArray=#envelope.ATTRIBUTES.sortArray# n=#envelope.ATTRIBUTES.titleMap[ATTRIBUTES.name].ordinal# attributeCollection=#extraAttributes#/></cfif></cfoutput></cfif>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<!--- It is strongly recommended to keep this tag closed. Unclosed tag generates output into the variable ATTRIBUTES.output only, which may be confusing --->
|
||||||
|
<cfif thisTag.executionMode IS "end" OR NOT thisTag.hasEndTag>
|
||||||
|
<cfparam name="ATTRIBUTES.backUrl" type="string" default="."/>
|
||||||
|
<cfparam name="ATTRIBUTES.enable" type="boolean" default="No"/>
|
||||||
|
<cfoutput>
|
||||||
|
<button type="submit" name="save" class="maincontrol"<cfif NOT ATTRIBUTES.enable> disabled</cfif>>#request.i18("Сохранить","Save")#</button>
|
||||||
|
<button type="submit" name="saveAndClose" class="maincontrol"<cfif NOT ATTRIBUTES.enable> disabled</cfif>>#request.i18("Сохранить и закрыть","Save and Close")#</button>
|
||||||
|
<button type="button" name="close" class="maincontrol" onClick="document.location.href='#ATTRIBUTES.backUrl#'">#request.i18("Закрыть","Close")#</button>
|
||||||
|
</cfoutput>
|
||||||
|
</cfif>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.title" default=""/>
|
||||||
|
<!---
|
||||||
|
<cfset var content=thisTag.generagedContent/>
|
||||||
|
|
||||||
|
--->
|
||||||
|
</cfsilent><!---
|
||||||
|
---><cfif thisTag.executionMode is "start"><div class="tr">
|
||||||
|
<div class="th"><cfoutput>#ATTRIBUTES.title#</cfoutput></div><div class="td">
|
||||||
|
<cfelseif thisTag.executionMode is "end"></div></div></cfif>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<!--- It is strongly recommended to keep this tag closed. Unclosed tag generates output into the variable ATTRIBUTES.output only, which may be confusing --->
|
||||||
|
<cfif thisTag.executionMode IS "end" OR NOT thisTag.hasEndTag>
|
||||||
|
<cfparam name="ATTRIBUTES.backUrl" type="string" default=""/>
|
||||||
|
<cfoutput>
|
||||||
|
<button type="submit" name="saveAndClose" class="maincontrol">#request.i18("Применить","Apply")#</button>
|
||||||
|
<button type="submit" name="resetAndClose" class="maincontrol">#request.i18("Снять","Reset")#</button>
|
||||||
|
<button type="button" name="close" onClick="document.location.href='#ATTRIBUTES.backUrl#'" class="maincontrol">#request.i18("Закрыть","Close")#</button></cfoutput>
|
||||||
|
</cfif>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<!--- It is strongly recommended to keep this tag closed. Unclosed tag generates output into the variable ATTRIBUTES.output only, which may be confusing --->
|
||||||
|
<!---v0.2 10:07 02.01.2019 - skipped url params--->
|
||||||
|
<cfif thisTag.executionMode IS "end" OR NOT thisTag.hasEndTag>
|
||||||
|
<cfparam name="ATTRIBUTES.filterUrl" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.filter" type="struct" default=#structNew()#/>
|
||||||
|
<cfparam name="ATTRIBUTES.fwx" type="string" default=""/>
|
||||||
|
|
||||||
|
<cfset qString="#ATTRIBUTES.fwx#"/>
|
||||||
|
|
||||||
|
<!---Чтобы передавать параметры в URL (и не пользоваться сессией), надо правильно форматировать параметры всех типов--->
|
||||||
|
<!--- <cfloop collection=#ATTRIBUTES.filter# item="item">
|
||||||
|
<cftry>
|
||||||
|
<cfset fltr=structFind(ATTRIBUTES.filter,item)/>
|
||||||
|
<cfset qString=listAppend(qString, "#item#=#fltr.val#", "&")/>
|
||||||
|
<cfcatch type="ANY"></cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cfloop>--->
|
||||||
|
|
||||||
|
<cfset filterOn=request.filterOn(ATTRIBUTES.filter)/>
|
||||||
|
|
||||||
|
<!--- actually, any non-empty string can be assigned to reset_filter --->
|
||||||
|
<cfoutput><a href="#ATTRIBUTES.filterUrl#?#qString#"><cfif filterOn><b>#request.i18("Фильтр...","Filter")#</b><cfelse>#request.i18("Фильтр","Filter")#</cfif></b></a>
|
||||||
|
<cfif filterOn><a href="#request.thisPage#?reset_filter=yes">(#request.i18("снять","reset")#)</a></cfif></cfoutput>
|
||||||
|
</cfif>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.titleMap" default=#structNew()#/>
|
||||||
|
<cfparam name="ATTRIBUTES.sortArray" default=#arrayNew(1)#/>
|
||||||
|
|
||||||
|
<cfset attributeList="titleMap,sortArray"/>
|
||||||
|
|
||||||
|
<cfset extraAttributes=structNew()/>
|
||||||
|
<cfloop collection=#ATTRIBUTES# item="attr">
|
||||||
|
<cfif NOT listFindNoCase(attributeList, attr)>
|
||||||
|
<cfset val="#structFind(ATTRIBUTES, attr)#"/>
|
||||||
|
<cfif IsSimpleValue(val)>
|
||||||
|
<cfset structInsert(extraAttributes, attr, val)/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
</cfsilent><!---
|
||||||
|
---><cfif thisTag.executionMode is "start"><tr><cfelseif thisTag.executionMode is "end"></tr></cfif>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfimport prefix="m" taglib="../lib"/>
|
||||||
|
<cfimport prefix="c" taglib="../lib/controls"/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.useSummary" default="yes"/>
|
||||||
|
<cfparam name="ATTRIBUTES.addtionalUrlParams" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.self" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.recordCount" default="0"/>
|
||||||
|
<cfparam name="ATTRIBUTES.totalCount" default="0"/>
|
||||||
|
<cfparam name="ATTRIBUTES.recordsPerPage" default="500"/>
|
||||||
|
<cfparam name="ATTRIBUTES.footerOut" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.excelLink" default="No"/>
|
||||||
|
<cfparam name="ATTRIBUTES.jsonLink" default="No"/>
|
||||||
|
<cfparam name="ATTRIBUTES.buttonNew" default="No"/>
|
||||||
|
<cfparam name="ATTRIBUTES.buttonNewTitle" default="Создать"/>
|
||||||
|
<cfparam name="ATTRIBUTES.urlNew" default=""/>
|
||||||
|
|
||||||
|
<cfset UrlSuffix=""/>
|
||||||
|
<cfif len(ATTRIBUTES.addtionalUrlParams) GT 0>
|
||||||
|
<cfset UrlSuffix="&#ATTRIBUTES.addtionalUrlParams#"/>
|
||||||
|
</cfif>
|
||||||
|
<cfif len(ATTRIBUTES.self) GT 0>
|
||||||
|
<cfset UrlSuffix="&track=#ATTRIBUTES.self#"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
|
||||||
|
<c:paginator
|
||||||
|
thisPage=#request.thisPage#
|
||||||
|
recordCount=#ATTRIBUTES.recordCount#
|
||||||
|
recordsPerPage=#ATTRIBUTES.recordsPerPage#
|
||||||
|
self=#ATTRIBUTES.self#
|
||||||
|
addtionalUrlParams=#ATTRIBUTES.addtionalUrlParams#
|
||||||
|
output="paginator"/>
|
||||||
|
|
||||||
|
</cfsilent>
|
||||||
|
|
||||||
|
<cfoutput>
|
||||||
|
<cfif ATTRIBUTES.useSummary>
|
||||||
|
<div class="table wide" style="margin:3px 0;">
|
||||||
|
<div class="td" style="height:2em; line-height:2em; padding-left:0; vertical-align:middle;">
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.buttonNew>
|
||||||
|
<button type="button" class="maincontrol" onclick="document.location.href='#ATTRIBUTES.urlNew#'" style="margin:.0 1.5em 0 0;">
|
||||||
|
<a href="#ATTRIBUTES.urlNew#">#ATTRIBUTES.buttonNewTitle#</a>
|
||||||
|
</button>
|
||||||
|
<cfelse>
|
||||||
|
<div style="display:inline-block; width:1em;"/> </div>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif (ATTRIBUTES.recordCount GE 0)>
|
||||||
|
Выбрано <b>#ATTRIBUTES.recordCount#</b>
|
||||||
|
<cfif (ATTRIBUTES.totalCount GE 0)>
|
||||||
|
из <b>#ATTRIBUTES.totalCount#</b>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.excelLink>
|
||||||
|
<a href="#request.thisPage#?output_xls#UrlSuffix#" title="экспорт в Excel" style="margin-left:.5em; height:100%;" target="_blank"><img src="img/xls.gif" style="vertical-align:text-bottom;"/></a>
|
||||||
|
</cfif>
|
||||||
|
<cfif ATTRIBUTES.jsonLink>
|
||||||
|
<a href="#request.thisPage#?output_json#UrlSuffix#" title="экспорт в json" style="margin-left:.5em; height:100%;" target="_blank"><img src="img/json.svg" style="vertical-align:text-bottom;" width="13" height="13"/></a>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="td r">
|
||||||
|
#paginator.links#
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</cfif>
|
||||||
|
</cfoutput>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.footerOut)>
|
||||||
|
<cfsavecontent variable="CALLER.#ATTRIBUTES.footerOut#">
|
||||||
|
<cfoutput>
|
||||||
|
<div class="wide r" style="margin:0.3em">
|
||||||
|
#paginator.links#
|
||||||
|
</div>
|
||||||
|
</cfoutput>
|
||||||
|
</cfsavecontent>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.qRead" type="query"/>
|
||||||
|
<cfparam name="ATTRIBUTES.convertSnakeToCamel" type="boolean" default=true/>
|
||||||
|
<!---<cfdump var=#qRead#/>
|
||||||
|
<cfdump var=#ATTRIBUTES.titleMap#/>
|
||||||
|
<cfabort/>--->
|
||||||
|
|
||||||
|
<cffunction name="snake2camel"
|
||||||
|
returntype="string"
|
||||||
|
output="false"
|
||||||
|
hint="convert snake style name to camel style name">
|
||||||
|
<cfargument name="snake" type="string" required="true" />
|
||||||
|
<cfreturn #reReplace(ARGUMENTS.snake,"_([a-z])","\u\1","ALL")#/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="camel2snake"
|
||||||
|
returntype="string"
|
||||||
|
output="false"
|
||||||
|
hint="convert camel style name to snake style name">
|
||||||
|
<cfargument name="camel" type="string" required="true" />
|
||||||
|
<cfreturn #reReplace(ARGUMENTS.camel,"([A-Z])","_\l\1","ALL")#/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<!--- <cffunction name="escape4json"
|
||||||
|
returntype="string"
|
||||||
|
output="false"
|
||||||
|
hint="escape string for json format">
|
||||||
|
<cfargument name="s" type="string" required="true" />
|
||||||
|
<cfreturn #reReplace(ARGUMENTS.camel,"([A-Z])","_\l\1","ALL")#/>
|
||||||
|
</cffunction> --->
|
||||||
|
|
||||||
|
<!--- <cffunction name="query2array">
|
||||||
|
<cfargument name="qry" type="query" required="true"/><!--- column names should not contain commas --->
|
||||||
|
<cfargument name="convertSnakeToCamel" type="boolean" default=true/><cfsilent>
|
||||||
|
|
||||||
|
</cffunction> --->
|
||||||
|
|
||||||
|
<!--- <cffunction name="query2json"
|
||||||
|
output="true"
|
||||||
|
hint="print query in json format as array of structures">
|
||||||
|
<cfargument name="qry" type="query" required="true"/><!--- column names should not contain commas --->
|
||||||
|
<cfargument name="convertSnakeToCamel" type="boolean" default=true/><cfsilent>
|
||||||
|
<cfset var columnsIn=#qry.columnList()#/>
|
||||||
|
<cfset var columnsOut=""/>
|
||||||
|
<cfif arguments.convertSnakeToCamel>
|
||||||
|
<cfloop list=#columnsIn# item="col">
|
||||||
|
<cfset columnsOut=listAppend(columnsOut,snake2camel(col))/>
|
||||||
|
</cfloop>
|
||||||
|
<cfelse>
|
||||||
|
<cfset columnsOut=#columnsIn#/>
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>[<cfoutput query=#qry#>{<cfloop index="i" from="1" to=#listLen(columnsOut)#>"#listGetAt(#columnsOut#,i)#":"#qry[listGetAt(#columnsIn#,i)]#"<cfif #i# LT #listLen(columnsOut)#>,</cfif></cfloop>}<cfif #qry.currentRow()# LT #qry.recordCount()#>,</cfif></cfoutput>]
|
||||||
|
</cffunction> --->
|
||||||
|
|
||||||
|
<cffunction name="query2json"
|
||||||
|
output="false"
|
||||||
|
hint="print query in json format as array of structures">
|
||||||
|
<cfargument name="qry" type="query" required="true"/><!--- column names should not contain commas --->
|
||||||
|
<cfargument name="convertSnakeToCamel" type="boolean" default=true/>
|
||||||
|
|
||||||
|
<cfset var columnsIn=#arguments.qry.columnList()#/>
|
||||||
|
<cfset var columnsOut=""/>
|
||||||
|
|
||||||
|
<cfif arguments.convertSnakeToCamel>
|
||||||
|
<cfloop list=#columnsIn# item="col">
|
||||||
|
<cfset columnsOut=listAppend(columnsOut,snake2camel(col))/>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfset var outArray=arrayNew(1)/>
|
||||||
|
//copy fields to structure, renaming fields, this preserves data type
|
||||||
|
<cfloop query=#arguments.qry#>
|
||||||
|
<cfset var structRow=structNew()/>
|
||||||
|
<cfloop index="i" from="1" to=#listLen(columnsOut)#>
|
||||||
|
<cfset structInsert(structRow,"#listGetAt(#columnsOut#,i)#",arguments.qry["#listGetAt(#columnsIn#,i)#"])/>
|
||||||
|
</cfloop>
|
||||||
|
<cfset arrayAppend(outArray,structRow)/>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfreturn serializeJSON(outArray)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn serializeJSON(arguments.qry,"struct")/>
|
||||||
|
</cfif>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<!--- <cfset request.camel2snake = camel2snake/> --->
|
||||||
|
|
||||||
|
<!---<cfsavecontent variable="strXmlData">--->
|
||||||
|
<!--- <cfif ATTRIBUTES.snake2camel>
|
||||||
|
<cfloop collection=#ATTRIBUTES.qRead.columnList# item="col">
|
||||||
|
<cfset queryRenameColumn(ATTRIBUTES.qRead,col,snake2camel(col))/>
|
||||||
|
<cfset ATTRIBUTES.qREAD.
|
||||||
|
</cfloop>
|
||||||
|
</cfif> --->
|
||||||
|
<cfset msStartAt=getTickCount()/>
|
||||||
|
</cfsilent><cfcontent
|
||||||
|
type="application/json"
|
||||||
|
/><cfoutput>#query2json(ATTRIBUTES.qRead,ATTRIBUTES.convertSnakeToCamel)#</cfoutput><!---
|
||||||
|
,<cfoutput>"duration":"#(getTickCount()- msStartAt)#"</cfoutput>} --->
|
||||||
|
<cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<cfif thisTag.executionMode IS "end" OR NOT thisTag.hasEndTag>
|
||||||
|
<cfoutput>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a href="?language=ru"<cfif request.language EQ 'ru'> style="font-weight:bold;"</cfif>>Ru</a>
|
||||||
|
<a href="?language=en"<cfif request.language EQ 'en'> style="font-weight:bold;"</cfif>>En</a>
|
||||||
|
</cfoutput>
|
||||||
|
</cfif>
|
||||||
+240
@@ -0,0 +1,240 @@
|
|||||||
|
|
||||||
|
<cfsilent><!--- *** http://www.bennadel.com/blog/457-a-case-against-coldfusion-custom-tag-page-wrappers.htm--->
|
||||||
|
<cfimport prefix="layout" taglib="../layout"/>
|
||||||
|
<cfimport prefix="c" taglib="../lib/controls"/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.section" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.closeForm" type="boolean" default="No"/>
|
||||||
|
|
||||||
|
<cfparam name="title" default=""/><!---заголовок страницы, отображаемый в теле например, <h1>--->
|
||||||
|
<cfparam name="pageTitle" default="PAYG"/><!---заголовок окна браузера, в теге <title>--->
|
||||||
|
<cfparam name="controls" default=""/>
|
||||||
|
|
||||||
|
</cfsilent><cfsilent>
|
||||||
|
<!---Тег layout:page проглатывает собственный контент, и вместо него после закрытия выводит разметку на основании атрибутов (при открытии тега он формирует переменные - фрагменты разметки - по шаблону) и вложенных тегов layout:attribute (которые по закрытии тега на самом деле перезаписывают переменные в layout:page).
|
||||||
|
Вложенные теги удобны, чтобы передавать читабельную разметку, а не строку в кавычках.--->
|
||||||
|
<cfif thisTag.executionMode IS "start">
|
||||||
|
<!--- Формируется значение переменной controls--->
|
||||||
|
<!--- Но оно может быть переопределено вызовом layout:attribute в теле тега <layout:page>--->
|
||||||
|
<cfif #ATTRIBUTES.section# EQ "header">
|
||||||
|
<cfparam name="ATTRIBUTES.pageInfo" type="struct"/><!--- инициализируется вызывающей страницей --->
|
||||||
|
<cfsavecontent variable="controls">
|
||||||
|
<cfoutput>
|
||||||
|
<cfswitch expression=#ATTRIBUTES.pageInfo.getType()#>
|
||||||
|
<cfcase value="ls">
|
||||||
|
<layout:filter_link filterUrl="#ATTRIBUTES.pageInfo.entity#_filter.cfm" filter=#ATTRIBUTES.pageInfo.settings.filter# fwx="#ATTRIBUTES.pageInfo.track.fwx#" />
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="detail">
|
||||||
|
<layout:detail_buttons backUrl="#ATTRIBUTES.pageInfo.track.backUrl#" enable="#ATTRIBUTES.pageInfo.writePermitted()#"/>
|
||||||
|
<!---<cfset ATTRIBUTES.form="1"/>--->
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="filter">
|
||||||
|
<layout:filter_buttons backUrl="#ATTRIBUTES.pageInfo.track.backUrl#"/>
|
||||||
|
<!---<cfset ATTRIBUTES.form="1"/>--->
|
||||||
|
</cfcase><!--- *** not implemented
|
||||||
|
<cfcase value="del">
|
||||||
|
<layout:del_buttons backUrl="#ATTRIBUTES.pageInfo.track.backUrl#"/>
|
||||||
|
<cfset ATTRIBUTES.form="1"/>
|
||||||
|
</cfcase>--->
|
||||||
|
<cfdefaultcase>
|
||||||
|
<!---<cfthrow message="Unsupported page type" detail="Supported page types are ls, detail, filter, del"/>--->
|
||||||
|
</cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
<!---<layout:language_switch/>--->
|
||||||
|
</cfoutput>
|
||||||
|
</cfsavecontent>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<!--- Переменную (фрагмент разметки) title по традиции заполняем в вызывающей странице через layout:attribute--->
|
||||||
|
</cfsilent><!---
|
||||||
|
|
||||||
|
<!---*******************************************--->
|
||||||
|
---><cfif thisTag.executionMode IS "end"><!---
|
||||||
|
---><cfsilent>
|
||||||
|
<!---никакая разметка из тела даннного тега не выводится,
|
||||||
|
вместо этого по закрытии тега им генерируется собственный контент--->
|
||||||
|
<!---внутри тега на странице определяются его параметры, некорректно названные атрибутами--->
|
||||||
|
<cfset thisTag.generatedContent=""/><!---
|
||||||
|
---></cfsilent><!---
|
||||||
|
---><cfswitch expression=#ATTRIBUTES.section#><!---
|
||||||
|
---><cfcase value="header"><cfsilent>
|
||||||
|
|
||||||
|
</cfsilent><!--- ----------------------------------------------------------------------------
|
||||||
|
---------------------------------------------------------------------------------------------
|
||||||
|
---------------------------------------------------------------------------------------------
|
||||||
|
---><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||||
|
<html lang="en" xml:lang="en" xmlns= "http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"/><!--- this should be the first line in head --->
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var timerStart = Date.now();
|
||||||
|
</script>
|
||||||
|
<!---<cfquery name="qUsr" datasource="#request.DS#">
|
||||||
|
select login, firstname, middlename, lastname from worker where id='#request.usr_id#'
|
||||||
|
</cfquery>--->
|
||||||
|
<title><cfoutput>#pageTitle#</cfoutput></title>
|
||||||
|
<link rel="shortcut icon" href="img/favicon.ico"/>
|
||||||
|
|
||||||
|
<!--- <link rel="stylesheet" href="style/fontawesome-all.css"/> --->
|
||||||
|
<link rel="stylesheet" type="text/css" href="style/web.css"/>
|
||||||
|
<!--- override layout header style --->
|
||||||
|
<!--- <cfswitch expression=#request.STAND#>
|
||||||
|
<cfcase value="DEV">
|
||||||
|
<style>.title, .main-controls {background-color:#aaf}</style>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="TEST">
|
||||||
|
<style>.title, .main-controls {background-color:#9e9}</style>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="PROD">
|
||||||
|
<style>.title, .main-controls {background-color:#faa}</style>
|
||||||
|
</cfcase>
|
||||||
|
<cfdefaultcase>
|
||||||
|
<style>.title, .main-controls {background-color:#aaa}</style><!--- без изменений --->
|
||||||
|
</cfdefaultcase>
|
||||||
|
</cfswitch> --->
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript" src="js/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript" src="js/jquery.searchabledropdown.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function resetListsById(id){
|
||||||
|
for (var i = 0, j = arguments.length; i < j; i++) {
|
||||||
|
try {
|
||||||
|
document.getElementById(arguments[i]).selectedIndex=-1;
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload=function(){document.body.style.cursor='default';}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="cursor:wait;"><a name="up"></a> <!--- <cfdump var=#ATTRIBUTES.pageInfo.writePermitted()#/> --->
|
||||||
|
|
||||||
|
<div class="page">
|
||||||
|
<div class="page-main">
|
||||||
|
<div class="menu noprint" style="margin:0; padding:0;/*border:1px solid red;*/">
|
||||||
|
<div class="fixed-menu" style="width:145px; height:100%; padding:0 0 0 0; --border:1px solid blue;" />
|
||||||
|
<a name="up"></a>
|
||||||
|
|
||||||
|
<!---<a id="lime_ref" href="http://root" style="display:inline-block; margin: 0; padding: 0 6px; height: 30px; width: 48px; margin: 5px 0 0 0;" title="Home"><img src="img/alarm-arrow-up-icon.png" style="margin: 0; padding: 0; height: 30px; margin: 5px 0 0 0;"/></a>
|
||||||
|
--->
|
||||||
|
|
||||||
|
<!--- <div style="width:20px; height:30px; margin: 5px; background: url('img/logo0.png') 0 0 no-repeat; background-size:contain; display:inline-block;"></div>--->
|
||||||
|
<div style="width:138px; height:30px; margin: 5px 0 0 5px; background: url('img/logo0.png') 0 0 no-repeat; background-size:contain; display:inline-block; -border:1px solid red;">
|
||||||
|
<p class="b r" style="color:white; margin:0 10px 10px 10px; font-size:130%;">
|
||||||
|
Отчет PAYG
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p style="color:lightgray; margin:10px 12px; text-align:right;">
|
||||||
|
|
||||||
|
<cfoutput>v.#request.APP_VERSION#</cfoutput>
|
||||||
|
</p>
|
||||||
|
<style>
|
||||||
|
.menu-items-container::-webkit-scrollbar {width: 10px; margin:10px;}
|
||||||
|
}
|
||||||
|
.menu-items-container::-webkit-scrollbar-track {
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
.menu-items-container::-webkit-scrollbar-thumb {
|
||||||
|
--background-color: rgb(97,127,136);/*lightgray*/;
|
||||||
|
--border-radius: 2px;
|
||||||
|
--border: 1px solid white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="menu-items-container" style="width:100%; height:100%; overflow:auto; scrollbar-width:thin; --border:1px solid green;">
|
||||||
|
<cfmodule template="../inc/menu.cfm" fwx="#ATTRIBUTES.pageInfo.track.fwx#" thisUrl="#ATTRIBUTES.pageInfo.track.thisUrl#"/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- content -->
|
||||||
|
<div class="content">
|
||||||
|
|
||||||
|
<cfif isInstanceOf(ATTRIBUTES.pageInfo, "form_page_info")>
|
||||||
|
<cfoutput>
|
||||||
|
<form name="#ATTRIBUTES.pageInfo.formName#" action="#ATTRIBUTES.pageInfo.formAction#" method="#ATTRIBUTES.pageInfo.formMethod#" <!---style="height:100%;"---><cfif len(ATTRIBUTES.pageInfo.formEncType)> enctype="#ATTRIBUTES.pageInfo.formEncType#"</cfif>><!--- height:100% is required to make inner elements fill the container vertically ---><!--- but if we close the form in the middle of the page, layout is broken --->
|
||||||
|
</cfoutput>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<div class="control-bar">
|
||||||
|
<!--- <div style="display:table-cell; width:23px; background: #ccc/*rgb(117,147,156)*/; border: 1px solid #bbb;"> --->
|
||||||
|
<!--- <div style="display:table-cell; width:23px; background: #0078c9; border: 1px solid #000;"> --->
|
||||||
|
<!--- <div style="display:table-cell; width:23px; background: #07b; border: 1px solid #000;"></div> --->
|
||||||
|
<!--- <div style="display:table-cell; width:3px;"></div> --->
|
||||||
|
<div class="title">
|
||||||
|
<cfoutput>#title#</cfoutput>
|
||||||
|
</div>
|
||||||
|
<div class="main-controls">
|
||||||
|
<cfoutput>#controls#</cfoutput>
|
||||||
|
</div>
|
||||||
|
</div><!--- control-bar --->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout_vspacer"></div>
|
||||||
|
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.pageInfo.status.errorState)>
|
||||||
|
<span style="color:red"><cfoutput>#ATTRIBUTES.pageInfo.status.errorMessage#</cfoutput></span>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!-- content -->
|
||||||
|
</cfcase><cfcase value="extension"><!--- -------------------------------------------------------------------------------------------- --->
|
||||||
|
<cfif ATTRIBUTES.closeForm></form></cfif>
|
||||||
|
</cfcase><cfcase value="footer"><!--- -------------------------------------------------------------------------------------------- --->
|
||||||
|
<!--/content -->
|
||||||
|
<cfif ATTRIBUTES.closeForm></form></cfif>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!--/content -->
|
||||||
|
|
||||||
|
</div><!--- page main --->
|
||||||
|
|
||||||
|
<div class="page-footer noprint">
|
||||||
|
<div style="display:table-cell; padding:.5em; background-color:#555;">
|
||||||
|
<cftry>
|
||||||
|
<cfset pageTimeMs=GetTickCount()-request.startTickCount/>
|
||||||
|
<cfoutput><span title="page generation time">#pageTimeMs# ms</span></cfoutput>
|
||||||
|
<cfcatch type="any">request.startTickCount undefined</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<span id="load-time" title="page loading time"></span>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.onload=function() {
|
||||||
|
document.body.style.cursor='default';
|
||||||
|
document.getElementById("load-time").innerHTML = (Date.now()-timerStart) + " ms";
|
||||||
|
};
|
||||||
|
<!---alert(document.documentMode);// IE troubles --->
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
<div style="display:table-cell; padding: 3px;">
|
||||||
|
<div class="footer">
|
||||||
|
<cftry>
|
||||||
|
<cflock scope="session" type="readonly" timeout="3">
|
||||||
|
<cfif len(session.authentication_source)>
|
||||||
|
<cfoutput>authentication:#session.authentication_source#</cfoutput>
|
||||||
|
</cfif>
|
||||||
|
</cflock><cfcatch type="ANY"></cfcatch></cftry>
|
||||||
|
<a href="#up" class="up">вверх</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div><!--- page --->
|
||||||
|
|
||||||
|
<!--- fixed-menu class is defined at the bottom of page, so in case of unhandled error menu is not fixed --->
|
||||||
|
<style>
|
||||||
|
div.fixed-menu { position:fixed;}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html><!---
|
||||||
|
---></cfcase><cfdefaultcase><cfthrow message="Unsupported 'section' attribute value" detail="Supported section values are 'header','extension','footer'"></cfdefaultcase></cfswitch><!---
|
||||||
|
---></cfif>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<cfparam name="ATTRIBUTES.query" type="query"/>
|
||||||
|
<cfif thisTag.executionMode IS "end" OR NOT thisTag.hasEndTag>
|
||||||
|
<table class="worktable">
|
||||||
|
<tr>
|
||||||
|
<cfoutput>
|
||||||
|
<cfloop list=#queryColumnList(ATTRIBUTES.query)# item="col">
|
||||||
|
<th>#col#</th>
|
||||||
|
</cfloop>
|
||||||
|
</cfoutput>
|
||||||
|
</tr>
|
||||||
|
<cfoutput query=#ATTRIBUTES.query#>
|
||||||
|
<tr>
|
||||||
|
<cfloop list=#queryColumnList(ATTRIBUTES.query)# item="col">
|
||||||
|
<td>#ATTRIBUTES.query[col]#</td>
|
||||||
|
</cfloop>
|
||||||
|
</tr>
|
||||||
|
</cfoutput>
|
||||||
|
</table>
|
||||||
|
</cfif>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
<!---https://www.bennadel.com/blog/461-creating-microsoft-excel-documents-with-coldfusion-and-xml.htm--->
|
||||||
|
<!---*** locale-specific--->
|
||||||
|
<cfparam name="ATTRIBUTES.qRead" type="query"/>
|
||||||
|
<cfparam name="ATTRIBUTES.titleMap" type="struct"/>
|
||||||
|
<cfparam name="ATTRIBUTES.sheetTitle" type="string" default="Sheet1"/>
|
||||||
|
<cfparam name="ATTRIBUTES.filename" type="string" default="export.xls"/>
|
||||||
|
<cfset fieldArray=structSort(ATTRIBUTES.titleMap, "numeric", "ASC", "ordinal")/>
|
||||||
|
|
||||||
|
<cfsavecontent variable="strXmlData">
|
||||||
|
<cfoutput>
|
||||||
|
<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:x='urn:schemas-microsoft-com:office:excel' xmlns='http://www.w3.org/TR/REC-html40'><meta http-equiv="content-type" content="application/vnd.ms-excel; charset=UTF-8"><meta http-equiv="content-type" content="application/vnd.ms-excel; charset=UTF-8"><head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>#ATTRIBUTES.sheetTitle#</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body><table><!---
|
||||||
|
<cfloop index="i" from="1" to=#structCount(ATTRIBUTES.titleMap)#>
|
||||||
|
<Column ss:Index="#i#"/>
|
||||||
|
</cfloop>
|
||||||
|
--->
|
||||||
|
<tr>
|
||||||
|
<cfloop from=1 to=#arrayLen(fieldArray)# index="i">
|
||||||
|
<cfset title=structFind(ATTRIBUTES.titleMap, fieldArray[i]).title/>
|
||||||
|
<th><cfif len(title)>#title#<cfelse>#fieldArray[i]#</cfif></th>
|
||||||
|
</cfloop>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<cfloop query=#ATTRIBUTES.qRead#>
|
||||||
|
<tr>
|
||||||
|
<cfloop from=1 to=#arrayLen(fieldArray)# index="i">
|
||||||
|
|
||||||
|
<cfset data=#ATTRIBUTES.qRead[fieldArray[i]]#/>
|
||||||
|
<cftry>
|
||||||
|
<cfif isDate(data)><td>#dateFormat(data,'YYYY-MM-DD')#<cfif hour(data) GT 0 OR minute(data) EQ 0> #timeFormat(data,'HH:mm')#</cfif></td><cfelseif isNumeric(data)><td>#Replace(data, '.', ',')#</td><cfelse><td>#data#</td></cfif>
|
||||||
|
<cfcatch type="Any"><td>#data#</td></cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cfloop>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
</table></body></html>
|
||||||
|
</cfoutput>
|
||||||
|
</cfsavecontent>
|
||||||
|
|
||||||
|
<cfheader
|
||||||
|
name="content-disposition"
|
||||||
|
value="attachment; filename=#ATTRIBUTES.filename#"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!---
|
||||||
|
When streaming the Excel XML data, trim the data and
|
||||||
|
replace all the inter-tag white space. No need to stream
|
||||||
|
any more content than we have to.
|
||||||
|
--->
|
||||||
|
<cfcontent
|
||||||
|
type="application/msexcel"
|
||||||
|
variable="#ToBinary( ToBase64( strXmlData.Trim().ReplaceAll( '>\s+', '>' ).ReplaceAll( '\s+<', '<' ) ) )#"
|
||||||
|
/><cfabort/>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<!---https://www.bennadel.com/blog/461-creating-microsoft-excel-documents-with-coldfusion-and-xml.htm--->
|
||||||
|
<cfparam name="ATTRIBUTES.qRead" type="query"/>
|
||||||
|
<cfparam name="ATTRIBUTES.titleMap" type="struct"/>
|
||||||
|
<cfparam name="ATTRIBUTES.sheetTitle" type="string" default="Sheet1"/>
|
||||||
|
<cfparam name="ATTRIBUTES.filename" type="string" default="export.xlsx"/>
|
||||||
|
|
||||||
|
<cfsavecontent variable="strXmlData">
|
||||||
|
<cfoutput>
|
||||||
|
<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:x='urn:schemas-microsoft-com:office:excel' xmlns='http://www.w3.org/TR/REC-html40'>
|
||||||
|
<meta http-equiv="content-type" content="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">
|
||||||
|
<head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet><x:Name>xlsWorksheetName</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head>
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<cfloop struct=#ATTRIBUTES.titleMap# item="field">
|
||||||
|
<th>#structFind(ATTRIBUTES.titleMap,field).title#</th>
|
||||||
|
</cfloop>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<cfloop query=#ATTRIBUTES.qRead#>
|
||||||
|
<tr>
|
||||||
|
<cfloop struct=#ATTRIBUTES.titleMap# item="field">
|
||||||
|
<td>
|
||||||
|
#ATTRIBUTES.qRead[field]#
|
||||||
|
</td>
|
||||||
|
</cfloop>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</cfloop>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</cfoutput>
|
||||||
|
</cfsavecontent>
|
||||||
|
<cfheader
|
||||||
|
name="content-disposition"
|
||||||
|
value="attachment; filename=#ATTRIBUTES.filename#"
|
||||||
|
/>
|
||||||
|
<!---
|
||||||
|
When streaming the Excel XML data, trim the data and
|
||||||
|
replace all the inter-tag white space. No need to stream
|
||||||
|
any more content than we have to.
|
||||||
|
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||||||
|
--->
|
||||||
|
<cfcontent
|
||||||
|
type="application/vnd.ms-excel"
|
||||||
|
variable="#ToBinary( ToBase64( strXmlData.Trim().ReplaceAll( '>\s+', '>' ).ReplaceAll( '\s+<', '<' ) ) )#"
|
||||||
|
/>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
+239
@@ -0,0 +1,239 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<!---https://www.bennadel.com/blog/461-creating-microsoft-excel-documents-with-coldfusion-and-xml.htm--->
|
||||||
|
<!---порядок колонок, как в селекте, не управляется никак
|
||||||
|
Для определения порядка колонок используется поле ATTRIBUTES.titleMap.ordinal, возможно, не самый надежный способ
|
||||||
|
Можно попробовать использовать порядок колонок в массиве query --->
|
||||||
|
<cfparam name="ATTRIBUTES.qRead" type="query"/>
|
||||||
|
<cfparam name="ATTRIBUTES.titleMap" type="struct"/><!---see ../lib/data/field_set.cfm--->
|
||||||
|
<cfparam name="ATTRIBUTES.sheetTitle" type="string" default="Sheet1"/>
|
||||||
|
<cfparam name="ATTRIBUTES.filename" type="string" default="export.xlsx"/>
|
||||||
|
<cfset AVG_SYMBOL_WIDTH=6.5/>
|
||||||
|
<cfset MAX_COL_LENGTH=30/>
|
||||||
|
|
||||||
|
<cfset qRead=ATTRIBUTES.qRead/>
|
||||||
|
|
||||||
|
<!---<cfdump var=#qRead#/>
|
||||||
|
<cfdump var=#ATTRIBUTES.titleMap#/>
|
||||||
|
|
||||||
|
<cfabort/>
|
||||||
|
--->
|
||||||
|
|
||||||
|
<!---<cfsavecontent variable="strXmlData">--->
|
||||||
|
|
||||||
|
</cfsilent><cfheader
|
||||||
|
name="content-disposition"
|
||||||
|
value="attachment; filename=#ATTRIBUTES.filename#"
|
||||||
|
/><cfcontent
|
||||||
|
type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
/><cfoutput><?xml version="1.0"?>
|
||||||
|
<?mso-application progid="Excel.Sheet"?>
|
||||||
|
<!---
|
||||||
|
This is the Workbook root element. This element
|
||||||
|
stores characteristics and properties of the
|
||||||
|
workbook, such as the namespaces used in
|
||||||
|
SpreadsheetML.
|
||||||
|
--->
|
||||||
|
<Workbook
|
||||||
|
xmlns="urn:schemas-microsoft-com:office:spreadsheet"
|
||||||
|
xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||||
|
xmlns:x="urn:schemas-microsoft-com:office:excel"
|
||||||
|
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
|
||||||
|
xmlns:html="http://www.w3.org/TR/REC-html40">
|
||||||
|
<!---
|
||||||
|
The first child element of the WorkBook element
|
||||||
|
is DocumentProperties. Office documents store
|
||||||
|
metadata related to the document—for example,
|
||||||
|
the author name, company, creation date, and
|
||||||
|
more in the DocumentProperties element.
|
||||||
|
--->
|
||||||
|
<DocumentProperties
|
||||||
|
xmlns="urn:schemas-microsoft-com:office:office">
|
||||||
|
<Author>xls-generator</Author>
|
||||||
|
<Company></Company>
|
||||||
|
</DocumentProperties>
|
||||||
|
<!---
|
||||||
|
The Styles node represents information related
|
||||||
|
to individual styles that can be used to format
|
||||||
|
components of the workbook.
|
||||||
|
--->
|
||||||
|
<Styles>
|
||||||
|
<!--- Basic format used by all cells. --->
|
||||||
|
<Style ss:ID="Default" ss:Name="Normal">
|
||||||
|
<Alignment ss:Vertical="Top"/>
|
||||||
|
<Borders/>
|
||||||
|
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="11"/>
|
||||||
|
<Interior/>
|
||||||
|
<NumberFormat/>
|
||||||
|
<Protection/>
|
||||||
|
</Style>
|
||||||
|
<Style ss:ID="Th">
|
||||||
|
<Alignment ss:Horizontal="Center" ss:Vertical="Top"/>
|
||||||
|
<Font ss:FontName="Calibri" x:Family="Swiss" ss:Size="11" ss:Bold="1"/>
|
||||||
|
</Style>
|
||||||
|
<Style ss:ID="Int">
|
||||||
|
<NumberFormat ss:Format="0" />
|
||||||
|
</Style>
|
||||||
|
<Style ss:ID="DateTime">
|
||||||
|
<NumberFormat ss:Format="dd/mm/yyyy\ hh:mm;@"/>
|
||||||
|
</Style>
|
||||||
|
<!---
|
||||||
|
<Style ss:ID="ShortDate">
|
||||||
|
<NumberFormat ss:Format="[ENG][$-409]d\-mmm\-yyyy;@" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style ss:ID="YesNo">
|
||||||
|
<NumberFormat ss:Format="Yes/No" />
|
||||||
|
</Style>
|
||||||
|
--->
|
||||||
|
</Styles>
|
||||||
|
<!---
|
||||||
|
This defines the first worksheeet and it's name.
|
||||||
|
We are only using one worksheet in this example,
|
||||||
|
but you could add more Worksheet nodes after
|
||||||
|
this one for multiple tabs. The "Name" attribute
|
||||||
|
here is the name that shows up in the tab.
|
||||||
|
--->
|
||||||
|
<Worksheet ss:Name="#ATTRIBUTES.sheetTitle#">
|
||||||
|
<Table
|
||||||
|
<!---
|
||||||
|
We need a column for each column of the
|
||||||
|
query. This attribute is required to be
|
||||||
|
correct. If the value here does NOT
|
||||||
|
match the data in Excel file, the
|
||||||
|
document will not render properly.
|
||||||
|
--->
|
||||||
|
ss:ExpandedColumnCount="#ListLen(qRead.ColumnList)#"
|
||||||
|
<!---
|
||||||
|
We need a row for every query record
|
||||||
|
plus one for the header row. Again, if
|
||||||
|
this value does not match what is in the
|
||||||
|
document, the excel file will not
|
||||||
|
render properly.
|
||||||
|
--->
|
||||||
|
ss:ExpandedRowCount="#(qRead.RecordCount + 1)#"
|
||||||
|
x:FullColumns="1"
|
||||||
|
x:FullRows="1">
|
||||||
|
<!---
|
||||||
|
Here, we can define general properties
|
||||||
|
regarding each column in the data output.
|
||||||
|
|
||||||
|
<Column ss:Index="1" ss:Width="30" />
|
||||||
|
<Column ss:Index="2" ss:Width="100" />
|
||||||
|
<Column ss:Index="3" ss:Width="42" />
|
||||||
|
<Column ss:Index="4" ss:Width="84" />
|
||||||
|
<Column ss:Index="5" ss:Width="66" />
|
||||||
|
<Column ss:Index="6" ss:Width="70" />--->
|
||||||
|
|
||||||
|
<cfsilent>
|
||||||
|
<cfset columns=arrayNew(1)/>
|
||||||
|
<cfset i=1/>
|
||||||
|
<cfloop struct=#ATTRIBUTES.titleMap# item="field">
|
||||||
|
<cfset col=structNew()/>
|
||||||
|
<cfset fieldSpec=structFind(ATTRIBUTES.titleMap, field)/>
|
||||||
|
<cfset col.name=field/>
|
||||||
|
|
||||||
|
<cfset col.title=fieldSpec.title/>
|
||||||
|
<cfif len(col.title) EQ 0>
|
||||||
|
<cfset col.title=col.name/>
|
||||||
|
</cfif>
|
||||||
|
<cfset col.titleLength=len(col.title)/>
|
||||||
|
|
||||||
|
<cfquery name="qLen" dbtype="query">
|
||||||
|
select coalesce(max(length(#field#)),0) as maxlen from qRead
|
||||||
|
</cfquery>
|
||||||
|
<cfset col.length=max(col.titleLength, qLen.maxLen)/>
|
||||||
|
|
||||||
|
<cfset col.cfSqlType="CF_SQL_VARCHAR"/>
|
||||||
|
<cfif structKeyExists(fieldSpec,"cfSqlType")>
|
||||||
|
<cfset col.cfSqlType=#fieldSpec.cfSqlType#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfswitch expression=#col.cfSqlType#><!---should use ## around variable name--->
|
||||||
|
<cfcase value="CF_SQL_BIGINT,CF_SQL_INTEGER,CF_SQL_SMALLINT,CF_SQL_TINYINT,CF_SQL_BIT,CF_SQL_NUMERIC,CF_SQL_DECIMAL,CF_SQL_REAL,CF_SQL_FLOAT,CF_SQL_DOUBLE">
|
||||||
|
<cfset col.ssType="Number"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="CF_SQL_TIMESTAMP,CF_SQL_DATE,CF_SQL_TIME,CF_SQL_DATETIME">
|
||||||
|
<cfset col.ssType="DateTime"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfdefaultcase>
|
||||||
|
<cfset col.ssType="String"/>
|
||||||
|
</cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
|
||||||
|
<cfset columns[fieldSpec.ordinal]=col/><!---new col created at each iteration, so we have valid references--->
|
||||||
|
<cfset i=i+1/>
|
||||||
|
</cfloop>
|
||||||
|
</cfsilent>
|
||||||
|
|
||||||
|
<cfloop from=1 to=#arrayLen(columns)# index="i">
|
||||||
|
<cfif columns[i].ssType EQ "DateTime"><!--- timestamp string representation is too long, --->
|
||||||
|
<Column ss:Index="#i#" ss:Width="#round(min(columns[i].titleLength, MAX_COL_LENGTH)*AVG_SYMBOL_WIDTH)#" ss:AutoFitWidth="1"/>
|
||||||
|
<cfelse>
|
||||||
|
<Column ss:Index="#i#" ss:Width="#round(min(columns[i].length, MAX_COL_LENGTH)*AVG_SYMBOL_WIDTH)#" ss:AutoFitWidth="1"/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<!---
|
||||||
|
This is our header row. All cells in the
|
||||||
|
header row will be of type string.
|
||||||
|
--->
|
||||||
|
<Row>
|
||||||
|
<cfloop from=1 to=#arrayLen(columns)# index="i">
|
||||||
|
<Cell ss:StyleID="Th">
|
||||||
|
<Data ss:Type="String">#columns[i].title#</Data>
|
||||||
|
</Cell>
|
||||||
|
</cfloop>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<cfflush/>
|
||||||
|
|
||||||
|
<cfloop query=#qRead#><!---
|
||||||
|
---><Row><!---
|
||||||
|
---><cfloop from=1 to=#ArrayLen(columns)# index="i"><!---
|
||||||
|
---><cfset field=columns[i].name/><!---
|
||||||
|
---><Cell<cfif columns[i].ssType EQ "DateTime"> ss:StyleID="DateTime"</cfif>><!---
|
||||||
|
---><cfif len(qRead[field])><Data ss:Type="#columns[i].ssType#"><!---no extra whitespace allowed here---><!---
|
||||||
|
---><cfswitch expression=#columns[i].ssType#><!---
|
||||||
|
---><cfcase value="DateTime"><!---
|
||||||
|
--->#dateFormat(qRead[field],"YYYY-MM-DD")#T#timeFormat(qRead[field],"HH:mm:ss.l")#<!---
|
||||||
|
---></cfcase><!---
|
||||||
|
---><cfcase value="Number"><!---
|
||||||
|
--->#qRead[field]#<!---
|
||||||
|
---></cfcase><!---
|
||||||
|
---><cfdefaultcase><!---
|
||||||
|
---><![CDATA[#request.clean4CDATA(qRead[field])#]]><!---
|
||||||
|
---></cfdefaultcase><!---
|
||||||
|
---></cfswitch><!---
|
||||||
|
---></Data></cfif><!---
|
||||||
|
---></Cell><!---
|
||||||
|
---></cfloop><!---
|
||||||
|
---></Row><!---
|
||||||
|
---></cfloop><!---
|
||||||
|
|
||||||
|
---></Table>
|
||||||
|
|
||||||
|
</Worksheet>
|
||||||
|
|
||||||
|
</Workbook>
|
||||||
|
|
||||||
|
</cfoutput>
|
||||||
|
<!---</cfsavecontent>--->
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Define the way in which the browser should interpret
|
||||||
|
the content that we are about to stream.
|
||||||
|
--->
|
||||||
|
<!---<cfheader
|
||||||
|
name="content-disposition"
|
||||||
|
value="attachment; filename=#ATTRIBUTES.filename#"
|
||||||
|
/>--->
|
||||||
|
|
||||||
|
<!---
|
||||||
|
When streaming the Excel XML data, trim the data and
|
||||||
|
replace all the inter-tag white space. No need to stream
|
||||||
|
any more content than we have to..ReplaceAll( '>\s+', '>' ).ReplaceAll( '\s+<', '<' )
|
||||||
|
--->
|
||||||
|
<!---<cfcontent
|
||||||
|
type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
variable="#ToBinary( ToBase64( strXmlData.Trim()))#"
|
||||||
|
/>---><!---<cfabort/>--->
|
||||||
|
<cfexit method="exittag"/>
|
||||||
+1
-4
@@ -183,10 +183,7 @@
|
|||||||
<cffunction name="saveSettings" access="private">
|
<cffunction name="saveSettings" access="private">
|
||||||
<cfargument name=settings>
|
<cfargument name=settings>
|
||||||
|
|
||||||
<cfif !isNumeric(request.usr_id)
|
<cfif NOT request.usrAuthenticated()>
|
||||||
OR !(request.usr_id GT 0)
|
|
||||||
OR request.usr_id EQ request.ANONYMOUS_USR_ID
|
|
||||||
OR request.usr_id EQ request.UNDEFINED_USR_ID>
|
|
||||||
<cfreturn />
|
<cfreturn />
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,392 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfimport prefix="m" taglib="lib"/>
|
||||||
|
<cfimport prefix="c" taglib="lib/controls"/>
|
||||||
|
<cfimport prefix="d" taglib="lib/data"/>
|
||||||
|
<cfimport prefix="layout" taglib="layout"/>
|
||||||
|
</cfsilent>
|
||||||
|
|
||||||
|
<m:prepare_ls entity="payg" accessObject="" pageInfoOut="pageInfo" trackOut="tr"/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfparam name="dt_finish" type="date" default=#createDateTime(year(Now()),month(Now()),1,0,0,0)#/>
|
||||||
|
<cfparam name="dt_start" type="date" default=#dateAdd('m',-1,dt_finish)#/>
|
||||||
|
<cfset hours=dateDiff('h',dt_start,dt_finish)/>
|
||||||
|
|
||||||
|
<cfset tenant_wz_index=val(mid(request.auth.WZ,3,5))/><!--- cut off WZ --->
|
||||||
|
|
||||||
|
<!--- Для начала выкатываем просто кусок каталога, не фильтруя по спеке (которой иногда нет) --->
|
||||||
|
<cfquery name="qSpec" datasource="rpt">
|
||||||
|
select * from (
|
||||||
|
values
|
||||||
|
('Объектное хранилище S3 CEPH', 'Дисковое пространство: Объем хранения','paas.s3.ceph.vol-m','ГБайт',1.80,0)
|
||||||
|
,('Объектное хранилище S3 CEPH','Исходящий трафик','paas.s3.ceph.trf-m','ГБайт',1.50,0)
|
||||||
|
,('Объектное хранилище S3 CEPH','Запросы GET, HEAD, OPTIONS','paas.s3.ceph.get-m','10тыс.шт.',0.35,0)
|
||||||
|
,('Объектное хранилище S3 CEPH','Запросы PUT, POST, PATCH','paas.s3.ceph.put-m','10тыс.шт.',4.50,0)
|
||||||
|
) as
|
||||||
|
s (svc, component, code, unit, price, discount)
|
||||||
|
order by code
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--- Нужно еще реализовать в модели данных политики бесплатного минимума --->
|
||||||
|
<cfquery name="qComputing" datasource="rpt">
|
||||||
|
select 'WZ'||to_char(tenant.wzcode,'FM00000') as wz, vdc.name as vdc_name,
|
||||||
|
case
|
||||||
|
when vdc.name like '%-v1cl1%' then 'iaas.ngc.i29'
|
||||||
|
when vdc.name like '%-v1cl2%' then 'iaas.ngc.i31'
|
||||||
|
when vdc.name like '%-v1cl4%' then 'iaas.ngc.a28'
|
||||||
|
when vdc.name like '%-v1cl6%' then 'iaas.ngc.i28'
|
||||||
|
else '['||vdc.name||']' end as code,
|
||||||
|
sum(cpu_used/cpu_speed)/4. as core_h,
|
||||||
|
sum(mem_used)/1024/4. as gb_h
|
||||||
|
from ngcloud_ru.capacity_resource
|
||||||
|
join ngcloud_ru.vdc on capacity_resource.vdc_id=vdc.id
|
||||||
|
join ngcloud_ru.tenant on tenant.id=vdc.tenant_id
|
||||||
|
where tenant.wzcode = <cfqueryparam cfsqltype="cf_sql_integer" value=#tenant_wz_index#/>
|
||||||
|
AND capacity_resource.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND capacity_resource.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by tenant.wzcode, vdc.name
|
||||||
|
order by tenant.wzcode, vdc.name;
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfif isDefined("DEBUG")>
|
||||||
|
<cfdump var=#qComputing#/>
|
||||||
|
<cfflush/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfquery name="qComputingAge" datasource="rpt">
|
||||||
|
select max(capacity_resource.timestamp) as dt_load from ngcloud_ru.capacity_resource
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
|
||||||
|
<!--- переделать storage_profile_types с расшифровкой ssd, sata, можно прямо до каталожного кода --->
|
||||||
|
<cfquery name="qStorage" datasource="rpt">
|
||||||
|
select 'WZ'||to_char(tenant.wzcode,'FM00000') as wz, vdc.name as vdc_name,
|
||||||
|
case
|
||||||
|
WHEN vdc.name LIKE '%-v1cl1%' THEN 'iaas.ngc.i29'
|
||||||
|
WHEN vdc.name LIKE '%-v1cl2%' THEN 'iaas.ngc.i31'
|
||||||
|
WHEN vdc.name LIKE '%-v1cl4%' THEN 'iaas.ngc.a28'
|
||||||
|
WHEN vdc.name LIKE '%-v1cl6%' THEN 'iaas.ngc.i28'
|
||||||
|
ELSE '['||vdc.name||']' END
|
||||||
|
|| CASE
|
||||||
|
WHEN storage_profile_types.name ILIKE '%-SSD%' THEN '.ssd-m'
|
||||||
|
WHEN storage_profile_types.name ILIKE '%-SAS' THEN '.sas-m'
|
||||||
|
WHEN storage_profile_types.name ILIKE '%-SATA' THEN '.sata-m'
|
||||||
|
ELSE '.['|| storage_profile_types.name ||']' END
|
||||||
|
as code,
|
||||||
|
storage_profile_types.name, -- *** это не тип, а профайл
|
||||||
|
sum("limit")/1024./4 as gb_h_limit, -- 4 потому, что сбор раз в 15 минут
|
||||||
|
sum(used)/1024./4 as gb_h_used
|
||||||
|
from ngcloud_ru.capacity_storage
|
||||||
|
join ngcloud_ru.vdc on capacity_storage.vdc_id=vdc.id
|
||||||
|
join ngcloud_ru.tenant on tenant.id=vdc.tenant_id
|
||||||
|
join ngcloud_ru.storage_profile_types on capacity_storage.storage_profile_types_id=storage_profile_types.id
|
||||||
|
where tenant.wzcode = <cfqueryparam cfsqltype="cf_sql_integer" value=#tenant_wz_index#/>
|
||||||
|
AND capacity_storage.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND capacity_storage.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by tenant.wzcode, vdc.name, storage_profile_types.name
|
||||||
|
order by tenant.wzcode, vdc.name, storage_profile_types.name;
|
||||||
|
</cfquery>
|
||||||
|
<cfquery name="qStorageAge" datasource="rpt">
|
||||||
|
select max(capacity_storage.timestamp) as dt_load from ngcloud_ru.capacity_storage
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfif isDefined("DEBUG")>
|
||||||
|
<cfdump var=#qStorage#/>
|
||||||
|
<cfflush/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!---
|
||||||
|
"placement" "size_gb" "get_10k" "put_10k" "bytes_sent_gb"
|
||||||
|
HOT_FREE_LIMIT 1 10 1 100
|
||||||
|
--->
|
||||||
|
<!--- надо дисконтировать не ГБ-часы а ГБ-месяцы --->
|
||||||
|
<cfquery name="qS3Vol" datasource="rpt">
|
||||||
|
select /* *** ниже дублирование кода агрегации - round ceil etc, обратить внимание при правке */
|
||||||
|
(sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>) as vol_B
|
||||||
|
,ceil(sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>/1024/1024/1024) as vol_GB
|
||||||
|
,greatest(ceil(sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>
|
||||||
|
/1024/1024/1024)-
|
||||||
|
case when bucket_stat.placement_id = 1 then 1 else 0 end, 0) as vol_GB_free_tier_subtracted /*считается не в точности так, как в функции*/
|
||||||
|
,case
|
||||||
|
when bucket_stat.placement_id = 1 then 'paas.s3.ceph'
|
||||||
|
when bucket_stat.placement_id = 2 then 'paas.s3.cphc'
|
||||||
|
else '['|| TO_CHAR(bucket_stat.placement_id,'FM9') ||']' end as code
|
||||||
|
--,bucket_stat.placement_id
|
||||||
|
,'WZ'||lpad(bucket_stat.owner, 5, '0') as wz -- *** здесь не число, а строка, поэтому другая формула обратной сборки WZ
|
||||||
|
|
||||||
|
from s3billing.bucket_stat
|
||||||
|
join s3billing.placement ON s3billing.placement.id=bucket_stat.placement_id
|
||||||
|
join s3billing.bucket_info ON bucket_info.id=bucket_stat.bucket_id
|
||||||
|
where bucket_stat.placement_id in (1,2)
|
||||||
|
AND bucket_stat.owner = <cfqueryparam cfsqltype="cf_sql_varchar" value=#tenant_wz_index#/>
|
||||||
|
AND bucket_stat.timestamp_addition >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND bucket_stat.timestamp_addition < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by bucket_stat.owner, bucket_stat.placement_id
|
||||||
|
order by bucket_stat.owner, bucket_stat.placement_id;
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfif isDefined("DEBUG")>
|
||||||
|
<cfdump var=#qS3Vol#/>
|
||||||
|
<cfflush/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfquery name="qS3VolAge" datasource="rpt">
|
||||||
|
select max(timestamp_addition) as dt_load from s3billing.bucket_stat
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfquery name="qS3OpsTrf" datasource="rpt">
|
||||||
|
select
|
||||||
|
sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'get%'
|
||||||
|
OR category.name LIKE 'head%'
|
||||||
|
OR category.name LIKE 'options%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
) as get
|
||||||
|
,ceil(sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'get%'
|
||||||
|
OR category.name LIKE 'head%'
|
||||||
|
OR category.name LIKE 'options%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
)/10000) as get_10k
|
||||||
|
,greatest(ceil(sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'get%'
|
||||||
|
OR category.name LIKE 'head%'
|
||||||
|
OR category.name LIKE 'options%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
)/10000) - CASE WHEN bucket_info.placement_id = 1 THEN 10 ELSE 0 END, 0
|
||||||
|
) as get_10k_free_tier_subtracted
|
||||||
|
|
||||||
|
,sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'put%'
|
||||||
|
OR category.name LIKE 'post%'
|
||||||
|
OR category.name LIKE 'patch%'
|
||||||
|
OR category.name LIKE 'list%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
) as put
|
||||||
|
,ceil(sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'put%'
|
||||||
|
OR category.name LIKE 'post%'
|
||||||
|
OR category.name LIKE 'patch%'
|
||||||
|
OR category.name LIKE 'list%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
)/10000) as put_10k
|
||||||
|
,greatest(ceil(sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'put%'
|
||||||
|
OR category.name LIKE 'post%'
|
||||||
|
OR category.name LIKE 'patch%'
|
||||||
|
OR category.name LIKE 'list%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
)/10000) - CASE WHEN bucket_info.placement_id = 1 THEN 1 ELSE 0 END, 0
|
||||||
|
) as put_10k_free_tier_subtracted
|
||||||
|
|
||||||
|
,(sum(bytes_sent)) as bytes_sent
|
||||||
|
,ceil(sum(bytes_sent)/1024/1024/1024) as bytes_sent_GB
|
||||||
|
,greatest(ceil(sum(bytes_sent)/1024/1024/1024) - CASE WHEN bucket_info.placement_id = 1 THEN 100 ELSE 0 END, 0
|
||||||
|
) as bytes_sent_GB_free_tier_subtracted
|
||||||
|
|
||||||
|
,case
|
||||||
|
when bucket_info.placement_id = 1 then 'paas.s3.ceph'
|
||||||
|
when bucket_info.placement_id = 2 then 'paas.s3.cphc'
|
||||||
|
else '['|| TO_CHAR(bucket_info.placement_id,'FM9') ||']' end as code --*** зачем делать там ключ bigint
|
||||||
|
,bucket_info.placement_id
|
||||||
|
--category.name, bucket_info.name
|
||||||
|
,'WZ'||lpad(user_info.name, 5, '0') as wz
|
||||||
|
from s3billing.usage_bucket_by_user
|
||||||
|
join s3billing.user_info on usage_bucket_by_user.user_id=user_info.id
|
||||||
|
join s3billing.category on usage_bucket_by_user.category_id=category.id
|
||||||
|
join s3billing.bucket_info on usage_bucket_by_user.bucketid=bucket_info.id
|
||||||
|
join s3billing.placement on bucket_info.placement_id=placement.id
|
||||||
|
where 1=1
|
||||||
|
AND user_info.name = <cfqueryparam cfsqltype="cf_sql_varchar" value=#tenant_wz_index#/>
|
||||||
|
AND usage_bucket_by_user.time >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND usage_bucket_by_user.time < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by user_info.name, bucket_info.placement_id
|
||||||
|
order by user_info.name, bucket_info.placement_id;
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfif isDefined("DEBUG")>
|
||||||
|
<cfdump var=#qS3OpsTrf#/>
|
||||||
|
<cfflush/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfquery name="qS3OpsTrfAge" datasource="rpt">
|
||||||
|
select max(time) as dt_load from s3billing.usage_bucket_by_user
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
|
||||||
|
<cfquery name="qUnifiedMetric" dbType="query">
|
||||||
|
select wz, code || '.vcpu-m' as code, core_h as raw_metric, core_h as metric, core_h as chargeable_metric from qComputing
|
||||||
|
union all
|
||||||
|
select wz, code || '.ram-m' as code, gb_h as raw_metric, gb_h as metric, gb_h as chargeable_metric from qComputing
|
||||||
|
union all
|
||||||
|
select wz, code, gb_h_used as raw_metric, gb_h_used as metric, gb_h_used as chargeable_metric from qStorage
|
||||||
|
union all
|
||||||
|
select wz, code || '.vol-m', vol_b as raw_metric, vol_gb as metric, vol_GB_free_tier_subtracted as chargeable_metric from qS3Vol
|
||||||
|
union all
|
||||||
|
select wz, code || '.get-m', get as raw_metric, get_10k as metric, get_10k_free_tier_subtracted as chargeable_metric from qS3OpsTrf
|
||||||
|
union all
|
||||||
|
select wz, code || '.put-m', put as raw_metric, put_10k as metric, put_10k_free_tier_subtracted as chargeable_metric from qS3OpsTrf
|
||||||
|
union all
|
||||||
|
select wz, code || '.trf-m', bytes_sent as raw_metric, bytes_sent_gb as metric, bytes_sent_gb_free_tier_subtracted as chargeable_metric from qS3OpsTrf
|
||||||
|
</cfquery>
|
||||||
|
<cfif isDefined("DEBUG")>
|
||||||
|
<cfdump var=#qUnifiedMetric#/>
|
||||||
|
<cfflush/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfquery name="qCharge" dbType="query">
|
||||||
|
SELECT
|
||||||
|
<d:field_set titleMapOut="titleMap" lengthOut="fieldCount">
|
||||||
|
<d:field title="Артикул">s.code</d:field>
|
||||||
|
<d:field title="Услуга">s.svc</d:field>
|
||||||
|
<d:field title="Компонент">s.component</d:field>
|
||||||
|
<d:field title="Кол-во (метрика)" cfSqlType="CF_SQL_NUMERIC">m.chargeable_metric</d:field>
|
||||||
|
<d:field title="Ед.изм.">s.unit</d:field>
|
||||||
|
<d:field title="Цена &##8381; с НДС" cfSqlType="CF_SQL_NUMERIC">s.price*(100-s.discount)/100 as discounted_price</d:field>
|
||||||
|
<d:field title="Стоимость &##8381; с НДС" cfSqlType="CF_SQL_NUMERIC">s.price*(100-s.discount)/100*m.chargeable_metric as charge</d:field>
|
||||||
|
<!--- <d:field title="Дата НОУ" cfSqlType="CF_SQL_TIMESTAMP">s.dt_from</d:field> --->
|
||||||
|
<!--- <d:field title="Дата оконч." cfSqlType="CF_SQL_TIMESTAMP">s.dt_to</d:field>
|
||||||
|
<d:field title="Ключ строки">s.line_key</d:field> --->
|
||||||
|
<d:field title="GPL с НДС" cfSqlType="CF_SQL_NUMERIC">s.price</d:field>
|
||||||
|
<d:field title="Скидка%" cfSqlType="CF_SQL_NUMERIC">s.discount</d:field>
|
||||||
|
<d:field title="Сырая метрика" cfSqlType="CF_SQL_NUMERIC">m.raw_metric</d:field>
|
||||||
|
<d:field title="Приведен­ная метрика" cfSqlType="CF_SQL_NUMERIC">m.metric</d:field>
|
||||||
|
|
||||||
|
<!--- <d:field title="Клиентс. назв.">s.user_description</d:field> --->
|
||||||
|
</d:field_set>
|
||||||
|
FROM qSpec as s, qUnifiedMetric as m
|
||||||
|
WHERE m.wz=<cfqueryparam cfsqltype="cf_sql_varchar" value=#request.auth.wz#/> AND s.code=m.code
|
||||||
|
order by s.code
|
||||||
|
</cfquery>
|
||||||
|
<!--- looks like QoQ does not support join syntax, at least of Lucee 5.4--->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<cfif isDefined("output_xls")>
|
||||||
|
<layout:xml qRead=#qCharge# titleMap=#titleMap# filename="#pageInfo.entity#.xml"/>
|
||||||
|
<cfabort/>
|
||||||
|
</cfif><cfif isDefined("output_json")>
|
||||||
|
<layout:json qRead=#qCharge# titleMap=#titleMap# filename="#pageInfo.entity#.json"/>
|
||||||
|
<cfabort/>
|
||||||
|
</cfif><!---
|
||||||
|
---><layout:page section="header" pageInfo=#pageInfo#>
|
||||||
|
|
||||||
|
<layout:attribute name="title">
|
||||||
|
<cfoutput><b>Отчет по PAYG</b></cfoutput>
|
||||||
|
</layout:attribute>
|
||||||
|
<layout:attribute name="controls">
|
||||||
|
<!---skip filter link, filter is not implemented--->
|
||||||
|
<!---<layout:language_switch/>--->
|
||||||
|
</layout:attribute>
|
||||||
|
</layout:page>
|
||||||
|
|
||||||
|
<!--- Внимание! округление вверх при расчете метрик get, put S3. Расчетные суммы могут содержать погрешности округления, поэтому окончательный расчет стоимости (количество * цену) должен выполняться в бухгалтерской программе.
|
||||||
|
<b>Если у клиента больше 1 действующего допника с услугами PAYG, то данные в отчете могут быть замножены, в этом случае считать вручную</b> --->
|
||||||
|
|
||||||
|
<cfoutput>
|
||||||
|
<form method="post" action="">
|
||||||
|
Период с <input type="text" name="dt_start" value="#dateFormat(dt_start,'YYYY-MM-DD')#"/>
|
||||||
|
по <input type="text" name="dt_finish" value="#dateFormat(dt_finish,'YYYY-MM-DD')#"/>
|
||||||
|
<input type="submit" style="cursor:pointer;"/>
|
||||||
|
<!--- <input type="submit" name="DEBUG" value="DEBUG" style="cursor:pointer;"/> --->
|
||||||
|
</form>
|
||||||
|
Часов в периоде: <b>#hours#</b> Строк в отчете: <b>#qCharge.recordCount#</b><br/>
|
||||||
|
Актуальность данных:
|
||||||
|
VCD Computing: <b>#dateFormat(qComputingAge.dt_load,'YYYY-MM-DD')# #timeFormat(qComputingAge.dt_load,'HH:MM:SS')#</b>
|
||||||
|
VCD Storage: <b>#dateFormat(qStorageAge.dt_load,'YYYY-MM-DD')# #timeFormat(qStorageAge.dt_load,'HH:MM:SS')#</b>
|
||||||
|
S3 хранение: <b>#dateFormat(qS3VolAge.dt_load,'YYYY-MM-DD')# #timeFormat(qS3VolAge.dt_load,'HH:MM:SS')#</b>
|
||||||
|
S3 операции и трафик: <b>#dateFormat(qS3OpsTrfAge.dt_load,'YYYY-MM-DD')# #timeFormat(qS3OpsTrfAge.dt_load,'HH:MM:SS')#</b>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<a href="#request.thisPage#?output_xls" title="экспорт в Excel" style="margin-left:.5em; height:100%;" target="_blank"><img src="img/xls.gif" style="vertical-align:text-bottom;"/></a>
|
||||||
|
|
||||||
|
<a href="#request.thisPage#?output_json" title="экспорт в json" style="margin-left:.5em; height:100%;" target="_blank"><img src="img/json.svg" style="vertical-align:text-bottom;" width="13" height="13"/></a>
|
||||||
|
</cfoutput>
|
||||||
|
<table class="worktable">
|
||||||
|
<thead>
|
||||||
|
<td width="1%"></td>
|
||||||
|
<!--- <th width="3%">WZ</th> --->
|
||||||
|
<th width="7%">Артикул</th>
|
||||||
|
<th width="7%">Кол-во (метрика)</th>
|
||||||
|
<th width="3%">Ед.изм.</th>
|
||||||
|
<th width="3%">Цена ₽ со скид., с НДС</th>
|
||||||
|
<th width="3%">Стоимость ₽ с НДС</th>
|
||||||
|
|
||||||
|
<td width="3%"> </td>
|
||||||
|
|
||||||
|
<td width="10%">Услуга</td>
|
||||||
|
<td width="10%">Компонент</td>
|
||||||
|
<!--- <td width="10%">Клиентс. назв.</td> --->
|
||||||
|
|
||||||
|
<td width="3%">GPL с НДС</td>
|
||||||
|
<td width="3%">Скидка%</td>
|
||||||
|
|
||||||
|
<!--- <td width="6%">Дата НОУ</td>
|
||||||
|
<td width="6%">Дата оконч.</td>
|
||||||
|
|
||||||
|
<td width="3%">Ключ строки</td> --->
|
||||||
|
<!--- <td width="3%">Сырая метрика</td> --->
|
||||||
|
<td width="3%">Оплачиваемый объем</td>
|
||||||
|
|
||||||
|
<!--- <td width="5%">Договор</td>
|
||||||
|
<td width="10%">Допник</td>
|
||||||
|
<td width="10%">Сделка</td> --->
|
||||||
|
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<cfoutput query="qCharge" group="svc">
|
||||||
|
<cfset var acc=0/>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="##ccc"></td>
|
||||||
|
<td bgcolor="##ccc" colspan="5" class="b"> #svc#</td>
|
||||||
|
<td></td>
|
||||||
|
<td bgcolor="##ccc" colspan="99"></td>
|
||||||
|
</tr>
|
||||||
|
<cfoutput>
|
||||||
|
<cfset acc=acc+charge/>
|
||||||
|
<tr>
|
||||||
|
<td>#currentRow#</a></td>
|
||||||
|
|
||||||
|
<td>#code#</td>
|
||||||
|
<td class="r" style="font-size:120%; padding:0 1em;">#chargeable_metric#</td>
|
||||||
|
<td class="c" style="font-size:90%;">#ed_izm#</td>
|
||||||
|
<td class="r">#discounted_price#</td>
|
||||||
|
<td class="r">#charge#</td>
|
||||||
|
|
||||||
|
<td></td>
|
||||||
|
|
||||||
|
<td>#svc#</td>
|
||||||
|
<td>#component#</td>
|
||||||
|
<!--- <td>#user_description#</td> --->
|
||||||
|
|
||||||
|
<td class="r">#price#</td>
|
||||||
|
<td class="r">#discount#</td>
|
||||||
|
|
||||||
|
<td class="r">#metric#</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
</cfoutput>
|
||||||
|
<tr>
|
||||||
|
<td bgcolor="##eee" style="border-bottom:2px solid gray;"></td>
|
||||||
|
<td bgcolor="##eee" colspan="4" class="r" style="border-bottom:2px solid gray;"><b>Итого</b>#svc#:</td>
|
||||||
|
<td bgcolor="##eee" colspan="1" class="r b" style="border-bottom:2px solid gray;">#NumFmt(acc,2)#</td>
|
||||||
|
<td colspan="14" style="border-bottom:2px solid gray;"></td>
|
||||||
|
</tr >
|
||||||
|
</cfoutput>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
<!--- <cfdump var=#qCharge#/> --->
|
||||||
|
|
||||||
|
|
||||||
|
<layout:page section="footer"/>
|
||||||
|
|
||||||
|
<!--- select * from s3billing.billing_per_user_fix_pl('2025-05-01 00:00:00','2025-06-01 00:00:00','1395'); --->
|
||||||
|
|
||||||
Reference in New Issue
Block a user