071 deep debug
This commit is contained in:
+22
-4
@@ -38,6 +38,20 @@
|
||||
<cfset request.DS = "#this.datasource#"/>
|
||||
<cfset request.language=""/>
|
||||
<cfset getDS(this.datasource)/>
|
||||
|
||||
<cfset this.datasources["inmem"] = {
|
||||
class: "org.hsqldb.jdbcDriver",
|
||||
// Внимание на jdbc:hsqldb:mem:
|
||||
connectionString: "jdbc:hsqldb:mem:lucee_mem;shutdown=false",
|
||||
username: "sa",
|
||||
password: "",
|
||||
// Защита от "висяка" и исчезновения базы:
|
||||
// заставляем Lucee держать минимум 1 коннект открытым всегда
|
||||
connectionLimit: 5,
|
||||
connectionMin: 1
|
||||
}
|
||||
/><!--- inmemory DB --->
|
||||
|
||||
<!--- кажется, нужно инициализировать датасорцы в псевдоконструкторе - onRequest не получается --->
|
||||
<!--- *** проверить, что есть - если есть, не создавать --->
|
||||
|
||||
@@ -94,7 +108,7 @@
|
||||
|
||||
<!--- global settings --->
|
||||
<cfset request.RECORDS_PER_PAGE=500/>
|
||||
<cfset request.APP_VERSION="0.00.030"/>
|
||||
<cfset request.APP_VERSION="0.00.031"/>
|
||||
<cfheader name="X-Application-Version" value=#request.APP_VERSION#/>
|
||||
<cfset request.STAND=getStand()/>
|
||||
|
||||
@@ -155,8 +169,8 @@
|
||||
--->
|
||||
<!--- <cfoutput>onRequest:check for cert started #(getTickCount()-request.startTickCount)#</cfoutput> --->
|
||||
<cfset var idpCertificateExists = false/>
|
||||
<cflock scope="application" type="readonly" timeout=1>
|
||||
<cfset request.APP_NAME=this.Name/>
|
||||
<cflock scope="application" type = "readonly" timeout=1>
|
||||
<cfset request.APP_NAME = this.Name/>
|
||||
<cfset idpCertificateExists = structKeyExists(application,"idpCertificate")/>
|
||||
</cflock>
|
||||
|
||||
@@ -196,14 +210,17 @@
|
||||
<cfscript>
|
||||
var requestData = GetHttpRequestData();
|
||||
var headers = requestData.headers;
|
||||
var local_token = false; //для отладки мы можем использовать токен из окружения, но когда он истекает, это легко упустить и потом искать проблему
|
||||
|
||||
//var authorizationHeader = server.system.environment?.AUTHORIZATION;
|
||||
|
||||
if (structKeyExists(headers,"Authorization")) {
|
||||
request.auth_header = headers.Authorization;
|
||||
//writedump(headers.Authorization);
|
||||
} else {
|
||||
//request.auth_header = createObject("java", "java.lang.System").getEnv("AUTHORIZATION");
|
||||
request.auth_header = server.system.environment?.AUTHORIZATION; //так короче
|
||||
local_token = true;
|
||||
if (isNull(request.auth_header)){ /*for local debug we can inject long-living token into environment*/
|
||||
structDelete(request,"auth_header");
|
||||
} else {
|
||||
@@ -236,8 +253,9 @@
|
||||
if (prefix.status_code NEQ 200) {
|
||||
//writedump(this.iamServiceUrl);
|
||||
//writedump(resp);
|
||||
//abort;
|
||||
var iamStatusCode = (isValid("integer", prefix.status_code)) ? val(prefix.status_code) : 500;
|
||||
throw(message="IAM error", detail="URL: #this.iamServiceUrl# Status Code: #prefix.status_code#");
|
||||
throw(message="IAM error", detail="local_token: #local_token# URL: #this.iamServiceUrl# Status Code: #prefix.status_code#");
|
||||
//abort;
|
||||
//throw("IDP response not OK");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
truncate table ngcloud_ru.tenant;
|
||||
alter table ngcloud_ru.tenant ADD clientid varchar(30) NULL;
|
||||
alter table ngcloud_ru.tenant drop column wzcode;
|
||||
@@ -107,10 +107,12 @@ ALTER SEQUENCE ngcloud_ru.storage_profile_types_id_seq OWNED BY ngcloud_ru.stora
|
||||
CREATE TABLE ngcloud_ru.tenant (
|
||||
id uuid NOT NULL,
|
||||
name character varying(255) NOT NULL,
|
||||
wzcode bigint
|
||||
--wzcode bigint
|
||||
clientid varchar(30) NULL
|
||||
);
|
||||
|
||||
|
||||
|
||||
ALTER TABLE ngcloud_ru.tenant OWNER TO vc_billing;
|
||||
|
||||
--
|
||||
|
||||
@@ -88,18 +88,26 @@ create table vmreports.storage (
|
||||
"Organization" varchar(255) NULL,
|
||||
"WZ" varchar(255) NULL,
|
||||
"Cluster" varchar(255) NULL,
|
||||
"Disk" varchar(255) NULL,
|
||||
"Storage_Policy" varchar(255) NULL,
|
||||
|
||||
"Disk" varchar(255) NOT NULL, --part of the key
|
||||
"Storage_Policy" varchar(255) NULL,
|
||||
"Disk_Allocated" float NULL,
|
||||
"Disk_Used" float NULL,
|
||||
"VM_is_ON" float NULL,
|
||||
"Object_UUID" uuid NOT NULL,
|
||||
ts timestamptz NOT NULL
|
||||
,CONSTRAINT PK_storage PRIMARY KEY (ts, "Object_UUID")
|
||||
,CONSTRAINT PK_storage PRIMARY KEY (ts, "Object_UUID", "Disk")
|
||||
);
|
||||
--Disk мог содержать NULL, но должен быть частью ключа, как оказалось
|
||||
--grant all on vmreports.storage to nifi;
|
||||
|
||||
--UPDATE vmreports.storage SET "Disk"='' WHERE "Disk" IS NULL;
|
||||
--ALTER TABLE vmreports.storage ALTER COLUMN "Disk" SET NOT NULL;
|
||||
--ALTER TABLE vmreports.storage DROP CONSTRAINT PK_storage;
|
||||
--ALTER TABLE vmreports.storage ADD CONSTRAINT PK_storage PRIMARY KEY (ts, "Object_UUID", "Disk");
|
||||
|
||||
UpdateRecord[id=3d3d3faa-fabd-1de9-d3d8-8f24953a9832] Failed to process FlowFile[filename=9cd148f7-bd09-43da-9ccd-82c8607ee98d]; will route to failure: java.lang.NullPointerException: Cannot invoke "org.apache.nifi.serialization.record.RecordField.getDataType()" because the return value of "org.apache.nifi.record.path.FieldValue.getField()" is null
|
||||
|
||||
|
||||
drop table if exists vmreports.vm_gpu;
|
||||
create table vmreports.vm_gpu (
|
||||
"Name" varchar(255) NULL,
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
component {
|
||||
/*0.02 timestamp support*/
|
||||
/**
|
||||
* HSQL 1.8
|
||||
* Принимает Lucee Query, создает в HSQL временную таблицу и льет в нее данные
|
||||
* @name Имя таблицы, которое ты хочешь использовать в SQL
|
||||
* @data Сам запрос (результат queryExecute или cfhttp)
|
||||
* @dsn Имя datasource для HSQL (по умолчанию "inmem")
|
||||
*/
|
||||
public string function register(required string name, required query data, string dsn = "inmem") {
|
||||
var cols = arguments.data.columnList;
|
||||
var meta = getMetaData(arguments.data);
|
||||
|
||||
//writedump(meta);
|
||||
var colDefs = [];
|
||||
var colTypes = {};
|
||||
var params = [];
|
||||
|
||||
// 1. Генерируем "CREATE TEMP TABLE name (col1 TYPE, col2 TYPE...)"
|
||||
for (var i = 1; i <= arrayLen(meta); i++) {
|
||||
var colName = meta[i].name;
|
||||
// Маппинг типов Lucee -> типы H2
|
||||
var dbType = mapType(meta[i].typeName);
|
||||
arrayAppend(colDefs, "#colName# #dbType#");
|
||||
colTypes[colName] = meta[i].typeName;
|
||||
}
|
||||
//writedump(colDefs);
|
||||
//var checkSql = "SELECT * FROM INFORMATION_SCHEMA.SYSTEM_TABLES WHERE TABLE_NAME = '#ucase(arguments.name)#';"
|
||||
//var res = queryExecute(checkSql, {}, { datasource: arguments.dsn });
|
||||
|
||||
//if (res.recordCount EQ 0) {
|
||||
var createSql = "DROP TABLE IF EXISTS #arguments.name#;
|
||||
CREATE TEMPORARY TABLE #arguments.name#(#arrayToList(colDefs, ', ')#) ON COMMIT PRESERVE ROWS";
|
||||
queryExecute(createSql, {}, { datasource: arguments.dsn });
|
||||
//}
|
||||
|
||||
// 2. Цикл вставки
|
||||
var insertSql = "INSERT INTO #arguments.name# (#cols#) VALUES (#repeatString("?,", listLen(cols)-1)#?)";
|
||||
|
||||
for (var row in arguments.data) {
|
||||
var rowData = [];
|
||||
for (var col in cols) {
|
||||
|
||||
arrayAppend(rowData, formatValue(row[col], colTypes[col]));
|
||||
}
|
||||
|
||||
queryExecute(insertSql, rowData, { datasource: arguments.dsn });
|
||||
}
|
||||
|
||||
return arguments.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить временные таблицы (опционально, база сама почистит при закрытии коннекта)
|
||||
*/
|
||||
public void function drop(required string names, string dsn = "inmem") {
|
||||
var list = arguments.names;
|
||||
if (!isArray(list)) list = [list];
|
||||
for (var name in list) {
|
||||
queryExecute("DROP TABLE IF EXISTS #name#", {}, { datasource: arguments.dsn });
|
||||
}
|
||||
}
|
||||
|
||||
// --- Вспомогательная функция маппинга типов ---
|
||||
private string function mapType(required string luceeType) {
|
||||
switch (arguments.luceeType) {
|
||||
case "VARCHAR": case "CHAR": case "LONGVARCHAR": return "LONGVARCHAR";
|
||||
case "INTEGER": case "BIGINT": case "SMALLINT": case "TINYINT": return "INT";
|
||||
case "DOUBLE": case "FLOAT": case "REAL": return "DOUBLE";
|
||||
case "BOOLEAN": case "BIT": return "BIT";
|
||||
case "DECIMAL": case "NUMERIC": return "DECIMAL(20,6)";
|
||||
case "TIMESTAMP": case "DATE": case "TIME": return "TIMESTAMP";
|
||||
default: return "LONGVARCHAR"; // И тут тоже
|
||||
}
|
||||
}
|
||||
|
||||
private function formatValue(required any val, required string targetType) {
|
||||
var isTextType = listFindNoCase("VARCHAR,CHAR,LONGVARCHAR", arguments.targetType);
|
||||
|
||||
// 1. Обработка пустых значений
|
||||
if (isNull(arguments.val) || (isSimpleValue(arguments.val) && len(trim(arguments.val)) == 0)) {
|
||||
// Если это текстовое поле — сохраняем пустую строку как есть
|
||||
if (isTextType) {
|
||||
return "";
|
||||
}
|
||||
// Для чисел, дат и булев — отдаем настоящий SQL NULL
|
||||
return javaCast("null", "");
|
||||
}
|
||||
|
||||
// 2. Форматирование непустых дат
|
||||
if (listFindNoCase("DATE,TIMESTAMP,TIME", arguments.targetType)) {
|
||||
return dateFormat(arguments.val, "yyyy-mm-dd") & " " & timeFormat(arguments.val, "HH:mm:ss");
|
||||
}
|
||||
|
||||
// 3. Числа и непустой текст отдаем как есть
|
||||
return arguments.val;
|
||||
}
|
||||
|
||||
}
|
||||
+96
-100
@@ -86,8 +86,10 @@
|
||||
<!--- Важно: если в периоде было 2+ цены, то строки должны быть замножены, а цена за период перестает иметь смысл (не показывать же среднее арифметическое, это неправильно) --->
|
||||
<!--- Left Outer Join with Free Tier --->
|
||||
<cfquery name="qSpec" dbtype="query">
|
||||
select s.catalogName as svc, s.component, s.serviceCode as service_code, s.fullCode as code, s.measureShort as unit
|
||||
, s.priceWVat as price, (s.discount + s.serviceDiscount - s.discount*s.serviceDiscount/100) as discount
|
||||
select s.catalogName as svc, s.component, s.serviceCode as service_code, NULL as sku_code, s.fullCode as code, s.measureShort as unit
|
||||
, s.priceWVat as price
|
||||
,s.componentDiscount as component_discount, s.serviceDiscount as service_discount, s.serviceQuantity as service_quantity, s.componentQuantity as component_quantity
|
||||
,coalesce(s.serviceQuantity*(100-s.serviceDiscount)/100*s.priceWVat*(100-s.componentDiscount)/100,0) as discounted_price
|
||||
,s.serviceStart, s.serviceEnd, s.serviceHash, s.serviceCustomName, f.free_tier
|
||||
from qSpecification s, qFreeTier f
|
||||
WHERE s.fullCode=f.code
|
||||
@@ -95,13 +97,18 @@
|
||||
|
||||
UNION
|
||||
|
||||
select s.catalogName as svc, s.component, s.serviceCode as service_code, s.fullCode as code, s.measureShort as unit
|
||||
, s.priceWVat as price, (s.discount + s.serviceDiscount - s.discount*s.serviceDiscount/100) as discount
|
||||
select s.catalogName as svc, s.component, s.serviceCode as service_code, NULL as sku_code, s.fullCode as code, s.measureShort as unit
|
||||
, s.priceWVat as price
|
||||
,s.componentDiscount as component_discount, s.serviceDiscount as service_discount, s.serviceQuantity as service_quantity, s.componentQuantity as component_quantity
|
||||
,coalesce(s.serviceQuantity*(100-s.serviceDiscount)/100*s.priceWVat*(100-s.componentDiscount)/100,0) as discounted_price
|
||||
,s.serviceStart, s.serviceEnd, s.serviceHash, s.serviceCustomName, 0
|
||||
from qSpecification s
|
||||
WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsqltype="cf_sql_varchar" list="true">)
|
||||
AND s.serviceType = 3
|
||||
</cfquery>
|
||||
<cfloop query="qSpec">
|
||||
<cfset qSpec.sku_code = (len(qSpec.code) GT 2) ? qSpec.code.left(qSpec.code.len() - 2) : qSpec.code/>
|
||||
</cfloop>
|
||||
|
||||
<cfif (ATTRIBUTES.debug)>
|
||||
qSpec (Left Outer Join with Free Tier)
|
||||
@@ -124,7 +131,7 @@ CREATE INDEX idx_tenant_wzcode ON ngcloud_ru.tenant (wzcode); --->
|
||||
|
||||
<!--- *** Нужно еще реализовать в модели данных (каталоге) политики бесплатного минимума (free tier), сейчас прибито гвоздями --->
|
||||
<cfquery name="qComputing">
|
||||
select 'WZ'||to_char(tenant.wzcode,'FM00000') as wz, vdc.name as vdc_name,
|
||||
select upper(clientid) 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'
|
||||
@@ -141,11 +148,11 @@ CREATE INDEX idx_tenant_wzcode ON ngcloud_ru.tenant (wzcode); --->
|
||||
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=#ATTRIBUTES.tenant_wz_index#/>
|
||||
where tenant.clientid = <cfqueryparam cfsqltype="cf_sql_varchar" value="WZ#numberFormat(ATTRIBUTES.tenant_wz_index,'00000')#"/>
|
||||
AND capacity_resource.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#attributes.dt_start#/>
|
||||
AND capacity_resource.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dateAdd("d",1,attributes.dt_finish)#/>
|
||||
group by tenant.wzcode, vdc.name, capacity_resource.timestamp::date
|
||||
order by tenant.wzcode, vdc.name, capacity_resource.timestamp::date;
|
||||
group by tenant.clientid, vdc.name, capacity_resource.timestamp::date
|
||||
order by tenant.clientid, vdc.name, capacity_resource.timestamp::date;
|
||||
</cfquery>
|
||||
|
||||
<cfif (ATTRIBUTES.debug)>
|
||||
@@ -172,7 +179,7 @@ from a
|
||||
<!--- переделать storage_profile_types с расшифровкой ssd, sata, можно прямо до каталожного кода --->
|
||||
<!--- очень долго исполняется --->
|
||||
<cfquery name="qStorage">
|
||||
select 'WZ'||to_char(tenant.wzcode,'FM00000') as wz, vdc.name as vdc_name,
|
||||
select upper(clientid) 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'
|
||||
@@ -182,15 +189,15 @@ from a
|
||||
WHEN vdc.name LIKE '%-v1cl7%' THEN 'iaas.ngc.a40'
|
||||
ELSE '['||vdc.name||']' END
|
||||
|| CASE
|
||||
WHEN storage_profile_types.name ILIKE '%FASTSSD%' THEN '.fstssd-m'
|
||||
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'
|
||||
WHEN storage_profile_types.name ILIKE '%FASTSSD%' THEN '.fstssd'
|
||||
WHEN storage_profile_types.name ILIKE '%SSD%' THEN '.ssd'
|
||||
WHEN storage_profile_types.name ILIKE '%SAS%' THEN '.sas'
|
||||
WHEN storage_profile_types.name ILIKE '%SATA%' THEN '.sata'
|
||||
ELSE '.['|| storage_profile_types.name ||']' END
|
||||
as code,
|
||||
storage_profile_types.name, -- *** это не тип, а профайл
|
||||
sum("limit")/1024./4./720 as gb_m_limit,
|
||||
sum(used)/1024./4./720 as gb_m_used,
|
||||
sum("limit")/1024./4./<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/> as gb_m_limit,
|
||||
sum(used)/1024./4./<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/> as gb_m_used,
|
||||
capacity_storage.timestamp::date as dt,
|
||||
vdc.name as dimension_1,
|
||||
storage_profile_types.name as dimension_2
|
||||
@@ -198,11 +205,11 @@ from a
|
||||
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=#ATTRIBUTES.tenant_wz_index#/> --AND 1=0
|
||||
where tenant.clientid = <cfqueryparam cfsqltype="cf_sql_varchar" value="WZ#numberFormat(ATTRIBUTES.tenant_wz_index,'00000')#"/>
|
||||
AND capacity_storage.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#attributes.dt_start#/>
|
||||
AND capacity_storage.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dateAdd("d",1,attributes.dt_finish)#/>
|
||||
group by tenant.wzcode, vdc.name, storage_profile_types.name, capacity_storage.timestamp::date
|
||||
order by tenant.wzcode, vdc.name, storage_profile_types.name, capacity_storage.timestamp::date
|
||||
group by tenant.clientid, vdc.name, storage_profile_types.name, capacity_storage.timestamp::date
|
||||
order by tenant.clientid, vdc.name, storage_profile_types.name, capacity_storage.timestamp::date
|
||||
;
|
||||
</cfquery>
|
||||
<!--- create index IX_capacity_storage_sort on ngcloud_ru.capacity_storage(vdc_id,storage_profile_types_id,timestamp); --->
|
||||
@@ -221,11 +228,8 @@ from a
|
||||
<cfquery name="qS3Vol">
|
||||
select /* *** ниже дублирование кода агрегации - round ceil etc, обратить внимание при правке */
|
||||
/* *** переделать на avg - сначала агрегировать по бакетам*/
|
||||
(sum(bucket_stat.usage_rgw_main_size_actual)/720) as vol_B
|
||||
,ceil(sum(bucket_stat.usage_rgw_main_size_actual)/720/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 /*считается не в точности так, как в функции*/ --->
|
||||
(sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>) as vol_B
|
||||
,sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>/1024/1024/1024 as vol_GB
|
||||
,case
|
||||
when bucket_stat.placement_id = 1 then 'paas.s3.ceph'
|
||||
when bucket_stat.placement_id = 2 then 'paas.s3.cphc'
|
||||
@@ -233,10 +237,10 @@ from a
|
||||
--,bucket_stat.placement_id
|
||||
,'WZ'||lpad(bucket_stat.owner, 5, '0') as wz -- *** здесь не число, а строка, поэтому другая формула обратной сборки WZ
|
||||
,bucket_stat.timestamp_addition::date as dt
|
||||
,bucket_info.name || ' - ' || case
|
||||
,bucket_info.name /*|| ' - ' || 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
|
||||
else '['|| TO_CHAR(bucket_stat.placement_id,'FM9') ||']' end */
|
||||
as dimension_1
|
||||
,null as dimension_2
|
||||
from s3billing.bucket_stat
|
||||
@@ -249,7 +253,6 @@ from a
|
||||
group by bucket_stat.owner, bucket_stat.placement_id, bucket_info.name, bucket_stat.timestamp_addition::date
|
||||
order by bucket_stat.owner, bucket_stat.placement_id, bucket_info.name, bucket_stat.timestamp_addition::date;
|
||||
</cfquery>
|
||||
|
||||
<cfif (ATTRIBUTES.debug)>
|
||||
qS3Vol
|
||||
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
|
||||
@@ -267,21 +270,14 @@ from a
|
||||
OR category.name LIKE 'options%'
|
||||
) THEN ops ELSE 0 END
|
||||
) as get
|
||||
,ceil(sum(
|
||||
,(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
|
||||
@@ -291,7 +287,7 @@ from a
|
||||
OR category.name LIKE 'list%'
|
||||
) THEN ops ELSE 0 END
|
||||
) as put
|
||||
,ceil(sum(
|
||||
,(sum(
|
||||
CASE WHEN
|
||||
(category.name LIKE 'put%'
|
||||
OR category.name LIKE 'post%'
|
||||
@@ -299,20 +295,10 @@ from a
|
||||
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 */
|
||||
,(sum(bytes_sent)/1024/1024/1024) as bytes_sent_GB
|
||||
|
||||
,case
|
||||
when bucket_info.placement_id = 1 then 'paas.s3.ceph'
|
||||
when bucket_info.placement_id = 2 then 'paas.s3.cphc'
|
||||
@@ -321,10 +307,10 @@ from a
|
||||
--category.name, bucket_info.name
|
||||
,'WZ'||lpad(user_info.name, 5, '0') as wz
|
||||
,usage_bucket_by_user.time::date as dt
|
||||
,bucket_info.name || ' - ' || case
|
||||
,bucket_info.name <!--- || ' - ' || 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
|
||||
else '['|| TO_CHAR(bucket_info.placement_id,'FM9') ||']' end --->
|
||||
as dimension_1
|
||||
,null as dimension_2
|
||||
from s3billing.usage_bucket_by_user
|
||||
@@ -352,12 +338,12 @@ from a
|
||||
<!--- собрали все метрики в один резалтсет --->
|
||||
<!--- Здесь мы знаем единицы измерения, причем надежнее, чем в спецификации --->
|
||||
<cfquery name="qUnifiedMetric" dbType="query">
|
||||
select dt, wz, code + '.vcpu-m' as code, core_h as raw_metric, core_h as metric,
|
||||
select dt, wz, code + '.vcpu' as code, core_h as raw_metric, core_h as metric,
|
||||
dimension_1, 'vdc' as dimension_1_name, dimension_2, NULL as dimension_2_name, 'ядро-ч' as unit
|
||||
from qComputing
|
||||
|
||||
union all
|
||||
select dt, wz, code + '.ram-m' as code, gb_h as raw_metric, gb_h as metric,
|
||||
select dt, wz, code + '.ram' as code, gb_h as raw_metric, gb_h as metric,
|
||||
dimension_1, 'vdc' as dimension_1_name, dimension_2, NULL as dimension_2_name, 'ГБайт-ч' as unit
|
||||
from qComputing
|
||||
|
||||
@@ -367,22 +353,22 @@ from a
|
||||
from qStorage
|
||||
|
||||
union all
|
||||
select dt, wz, code + '.vol-m', vol_b as raw_metric, vol_gb as metric,
|
||||
select dt, wz, code + '.vol', vol_b as raw_metric, vol_gb as metric,
|
||||
dimension_1, 'bucket' as dimension_1_name, dimension_2, NULL as dimension_2_name, 'ГБайт-м' as unit
|
||||
from qS3Vol
|
||||
|
||||
union all
|
||||
select dt, wz, code + '.get-m', get as raw_metric, get_10k as metric,
|
||||
select dt, wz, code + '.get', get as raw_metric, get_10k as metric,
|
||||
dimension_1, 'bucket' as dimension_1_name, dimension_2, NULL as dimension_2_name, '10тыс.шт.' as unit
|
||||
from qS3OpsTrf
|
||||
|
||||
union all
|
||||
select dt, wz, code + '.put-m', put as raw_metric, put_10k as metric,
|
||||
select dt, wz, code + '.put', put as raw_metric, put_10k as metric,
|
||||
dimension_1, 'bucket' as dimension_1_name, dimension_2, NULL as dimension_2_name, '10тыс.шт.' as unit
|
||||
from qS3OpsTrf
|
||||
|
||||
union all
|
||||
select dt, wz, code + '.trf-m', bytes_sent as raw_metric, bytes_sent_gb as metric,
|
||||
select dt, wz, code + '.trf', bytes_sent as raw_metric, bytes_sent_gb as metric,
|
||||
dimension_1, 'bucket' as dimension_1_name, dimension_2, NULL as dimension_2_name, 'ГБайт' as unit
|
||||
from qS3OpsTrf
|
||||
|
||||
@@ -398,7 +384,6 @@ from a
|
||||
<!--- лучше никогда не связываться с QoQ, но если все исполнять в исходной бд, это скорее мегаселект с большим числом CTE --->
|
||||
<!--- надо было сразу делать в БД. Причем сводить в одну, чтобы избежать нескольких источников данных... можно было бы и так добиться, но все равно сводим в буферную базу --->
|
||||
|
||||
<!--- <cfdump var=#qUnifiedMetric#/> --->
|
||||
<cfif (ATTRIBUTES.debug)>
|
||||
qUnifiedMetric
|
||||
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
|
||||
@@ -421,20 +406,30 @@ from a
|
||||
<!--- Вычитание Free Tier можно начинать с начала месяца - для этого можно сгруппировать по месяцам --->
|
||||
<!--- Кажется, здесь еще рано агрегировать, если мы хотим OUTER JOIN --->
|
||||
<!--- агрегировать вообще не надо в этом компоненте, а стоимость вычислять после агрегации --->
|
||||
<cfquery name="qChargeDaily" dbType="query">
|
||||
|
||||
<cfset db = new MemoryDbHelper()/>
|
||||
<cfset db.register("qUnifiedMetric", qUnifiedMetric, "inmem")/>
|
||||
<cfset db.register("qSpec", qSpec, "inmem")/>
|
||||
|
||||
<cfquery name="qChargeDaily" datasource="inmem">
|
||||
SELECT
|
||||
<d:field_set titleMapOut="titleMap" lengthOut="fieldCount">
|
||||
<d:field title="Код компонента">s.code as s_code</d:field>
|
||||
<d:field title="Код метрики">m.code as m_code</d:field>
|
||||
<d:field title="Код услуги">s.service_code as service_code</d:field>
|
||||
<d:field title="Код услуги">s.service_code</d:field>
|
||||
<d:field title="Код услуги">s.sku_code</d:field>
|
||||
<d:field title="Услуга">s.svc</d:field>
|
||||
<d:field title="Компонент">s.component</d:field>
|
||||
<d:field title="Ед.изм.">m.unit</d:field>
|
||||
<d:field title="Цена &##8381; с НДС" cfSqlType="CF_SQL_NUMERIC">
|
||||
<!--- <d:field title="Цена &##8381; с НДС" cfSqlType="CF_SQL_NUMERIC">
|
||||
s.price*(100-s.discount)/100 as discounted_price
|
||||
</d:field>
|
||||
</d:field> --->
|
||||
<d:field title="Цена &##8381; с НДС" cfSqlType="CF_SQL_NUMERIC">s.discounted_price</d:field>
|
||||
<d:field title="Цена с НДС" cfSqlType="CF_SQL_NUMERIC">s.price</d:field>
|
||||
<d:field title="Скидка%" cfSqlType="CF_SQL_NUMERIC">s.discount</d:field>
|
||||
<d:field cfSqlType="CF_SQL_NUMERIC">s.component_quantity</d:field>
|
||||
<d:field title="Скидка%" cfSqlType="CF_SQL_NUMERIC">s.component_discount</d:field>
|
||||
<d:field cfSqlType="CF_SQL_NUMERIC">s.service_quantity</d:field>
|
||||
<d:field cfSqlType="CF_SQL_NUMERIC">s.service_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="serviceStart" cfSqlType="CF_SQL_TIMESTAMP">s.serviceStart</d:field>
|
||||
@@ -448,33 +443,35 @@ from a
|
||||
<d:field>m.dimension_2</d:field>
|
||||
<d:field>m.dimension_2_name</d:field>
|
||||
</d:field_set>
|
||||
FROM qSpec as s, qUnifiedMetric as m
|
||||
WHERE s.code=m.code
|
||||
AND s.serviceStart <= m.dt
|
||||
AND (m.dt <= s.serviceEnd OR s.serviceEnd IS NULL)
|
||||
FROM qSpec s
|
||||
FULL OUTER JOIN qUnifiedMetric m ON (
|
||||
s.sku_code=m.code
|
||||
AND s.serviceStart <= m.dt
|
||||
AND (m.dt <= s.serviceEnd OR s.serviceEnd IS NULL)
|
||||
)
|
||||
WHERE 1=1
|
||||
<cfif #attributes.restrict_to_own_instances#>
|
||||
AND m.wz=<cfqueryparam cfsqltype="cf_sql_varchar" value=#request.auth.wz#/>
|
||||
</cfif>
|
||||
<!--- таким замысловатым способом мы реализуем FULL JOIN --->
|
||||
|
||||
|
||||
<!--- <!--- таким замысловатым способом мы реализуем FULL JOIN --->
|
||||
UNION ALL
|
||||
|
||||
SELECT NULL, m.code, NULL, NULL, NULL, NULL, 0,
|
||||
NULL, NULL, m.raw_metric, m.metric, NULL, NULL, NULL, NULL, m.dt, 0, m.dimension_1, m.dimension_1_name, m.dimension_2, m.dimension_2_name
|
||||
SELECT NULL, m.code, NULL, NULL, NULL, NULL, NULL, 0,
|
||||
0, 0, 0, 0, 0, m.raw_metric, m.metric, NULL, NULL, NULL, NULL, m.dt, 0, m.dimension_1, m.dimension_1_name, m.dimension_2, m.dimension_2_name
|
||||
FROM qUnifiedMetric as m
|
||||
WHERE m.code NOT IN (<cfqueryparam value="#ValueList(qSpec.code)#" cfsqltype="cf_sql_varchar" list="true">)
|
||||
WHERE m.code NOT IN (<cfqueryparam value="#ValueList(qSpec.sku_code)#" cfsqltype="cf_sql_varchar" list="true">)
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT s.code, NULL, s.service_code, s.svc, s.component, s.unit, s.price*(100-s.discount)/100 as discounted_price,
|
||||
s.price, s.discount, NULL, NULL, s.serviceStart, s.serviceEnd, s.serviceHash, s.serviceCustomName, NULL, 0, NULL, NULL, NULL, NULL
|
||||
SELECT s.code, NULL, s.service_code, s.sku_code, s.svc, s.component, s.unit, s.discounted_price,
|
||||
s.price, s.component_quantity, s.component_discount, s.service_quantity, s.service_discount, NULL, NULL, s.serviceStart, s.serviceEnd, s.serviceHash, s.serviceCustomName, NULL, 0, NULL, NULL, NULL, NULL
|
||||
FROM qSpec as s
|
||||
WHERE s.code NOT IN (<cfqueryparam value="#listRemoveDuplicates(ValueList(qUnifiedMetric.code))#" cfsqltype="cf_sql_varchar" list="true">)
|
||||
|
||||
ORDER by dt, s_code, m_code, dimension_1, dimension_2
|
||||
WHERE s.code NOT IN (<cfqueryparam value="#listRemoveDuplicates(ValueList(qUnifiedMetric.code))#" cfsqltype="cf_sql_varchar" list="true">) --->
|
||||
</cfquery>
|
||||
<!--- диагностика ошибок в QoQ крайне невнятна и вводит в заблуждение --->
|
||||
|
||||
<!---<cfdump var=#qChargeDaily# abort=true/> --->
|
||||
<!--- <cfdump var=#qChargeDaily# abort=true/> --->
|
||||
<cfif (ATTRIBUTES.debug)>
|
||||
qChargeDaily
|
||||
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
|
||||
@@ -491,11 +488,18 @@ from a
|
||||
<d:field title="Артикул по системам">m_code</d:field>
|
||||
<d:field title="Артикул">s_code as code</d:field>
|
||||
<d:field title="Код услуги">service_code</d:field>
|
||||
<d:field title="SKU code">sku_code</d:field>
|
||||
<d:field title="Услуга по каталогу">svc</d:field>
|
||||
<d:field title="serviceCustomName">serviceCustomName</d:field>
|
||||
<d:field title="Пользовательское наименование">serviceCustomName</d:field>
|
||||
<d:field title="Компонент">component</d:field>
|
||||
<d:field title="Ед.изм.">unit</d:field>
|
||||
<d:field title="Цена &##8381; с НДС" cfSqlType="CF_SQL_NUMERIC">discounted_price</d:field>
|
||||
<d:field cfSqlType="CF_SQL_NUMERIC">price</d:field>
|
||||
<d:field cfSqlType="CF_SQL_NUMERIC">component_quantity</d:field>
|
||||
<d:field cfSqlType="CF_SQL_NUMERIC">component_discount</d:field>
|
||||
<d:field cfSqlType="CF_SQL_NUMERIC">0 as discounted_price2</d:field>
|
||||
<d:field cfSqlType="CF_SQL_NUMERIC">service_quantity</d:field>
|
||||
<d:field cfSqlType="CF_SQL_NUMERIC">service_discount</d:field>
|
||||
<d:field title="Сырая метрика" cfSqlType="CF_SQL_NUMERIC">sum(raw_metric) as raw_metric</d:field>
|
||||
<d:field title="Приведенный объем" cfSqlType="CF_SQL_NUMERIC">sum(metric) as metric</d:field>
|
||||
<d:field title="serviceStart" cfSqlType="CF_SQL_TIMESTAMP">serviceStart</d:field>
|
||||
@@ -508,35 +512,27 @@ from a
|
||||
<d:field>dimension_1_name</d:field>
|
||||
<d:field>dimension_2</d:field>
|
||||
<d:field>dimension_2_name</d:field>
|
||||
<d:field title="Год" cfSqlType="CF_SQL_INTEGER">year(dt) as year</d:field>
|
||||
<d:field title="Месяц" cfSqlType="CF_SQL_INTEGER">month(dt) as month</d:field>
|
||||
<d:field title="Год" cfSqlType="CF_SQL_INTEGER">year(dt) as year</d:field>
|
||||
<d:field title="Месяц" cfSqlType="CF_SQL_INTEGER">month(dt) as month</d:field>
|
||||
</d:field_set>
|
||||
from qChargeDaily
|
||||
group by s_code, m_code, svc, service_code, serviceCustomName, component, unit, discounted_price, serviceStart, ServiceEnd, serviceHash, free_tier, year(dt), month(dt),
|
||||
group by s_code, m_code, svc, service_code, sku_code, serviceCustomName, component, unit, discounted_price, price, component_quantity, component_discount, service_quantity, service_discount, serviceStart, ServiceEnd, serviceHash, free_tier, year(dt), month(dt),
|
||||
dimension_1_name, dimension_1, dimension_2_name, dimension_2
|
||||
order by year(dt), month(dt), m_code, code, serviceStart, dimension_1, dimension_2
|
||||
order by year(dt), month(dt), sku_code, m_code, serviceStart, dimension_1, dimension_2
|
||||
</cfquery>
|
||||
<!--- в связи с бедностью языка QoQ (нет CASE) грубо патчим резалтсет --->
|
||||
<cfloop query="qChargeMonthly">
|
||||
<!--- <cfloop query="qChargeMonthly">
|
||||
<cfset qChargeMonthly.code = (len(code) GT 0) ? code : m_code/><!--- имя в левой части должно быть квалифицировано запросом (резалтсетом) --->
|
||||
<cftry>
|
||||
<cfset qChargeMonthly.discounted_price2 = precisionEvaluate("service_quantity*(100-service_discount)/100*price*(100-component_discount)/100")/><!--- имя в левой части должно быть квалифицировано запросом (резалтсетом) --->
|
||||
<cfcatch type="any">
|
||||
<cfdump var=#cfcatch#/>
|
||||
<cfdump var=#queryGetRow(qChargeMonthly, qChargeMonthly.currentRow)# abort=true/>
|
||||
</cfcatch>
|
||||
</cftry>
|
||||
</cfloop>
|
||||
|
||||
|
||||
<!--- тут могут быть подводные камни с копированием и переиспользованием объектов --->
|
||||
<!--- <cfif isQuery(qTotal)>
|
||||
<cfquery name="qTotalNew" dbType="query">
|
||||
select * from qTotal
|
||||
union all
|
||||
select * from qCharge
|
||||
</cfquery>
|
||||
<cfset qTotal = qTotalNew/>
|
||||
<cfelse>
|
||||
<cfset qTotal = qCharge/>
|
||||
</cfif>
|
||||
<cfdump var=#qTotal#/> --->
|
||||
|
||||
<!--- <cfdump var=#qChargeMonthly#/> --->
|
||||
<!--- *** проверить краевые даты --->
|
||||
--->
|
||||
|
||||
<cfif (ATTRIBUTES.debug)>
|
||||
qChargeMonthly
|
||||
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
|
||||
|
||||
@@ -55,9 +55,9 @@
|
||||
,componentCode
|
||||
,component
|
||||
,measureShort
|
||||
,quantity
|
||||
,componentQuantity
|
||||
,priceWVat
|
||||
,discount
|
||||
,componentDiscount
|
||||
,atomicCode
|
||||
,fullCode
|
||||
",
|
||||
@@ -93,9 +93,9 @@
|
||||
componentCode=local.component.componentCode,
|
||||
component=local.component.component,
|
||||
measureShort=local.component.measureShort,
|
||||
quantity=local.component.quantity,
|
||||
componentQuantity=local.component.quantity,
|
||||
priceWVat=local.component.priceWVat,
|
||||
discount=local.component.discount,
|
||||
componentDiscount=local.component.discount,
|
||||
atomicCode=local.component.atomicCode,
|
||||
fullCode="#local.component.componentCode#-#listGetAt('i,f,m',local.service.serviceType)#"
|
||||
}])/>
|
||||
|
||||
@@ -130,6 +130,14 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
|
||||
<th width="10%">Услуга</th>
|
||||
<th width="10%">Компонент</th>
|
||||
|
||||
<td width="3%">Цена ₽ до ск. с НДС</td>
|
||||
<td width="3%">Цена ₽ co ск. с НДС без окр.</td>
|
||||
<!--- <td width="3%">Цена ₽ co ск. с НДС без окр.v2</td> --->
|
||||
<td width="3%">Service Quantity</td>
|
||||
<td width="3%">Service Discount</td>
|
||||
<td width="3%">Component Quantity</td>
|
||||
<td width="3%">Component Discount</td>
|
||||
|
||||
<!--- <td width="10%">Клиентс. назв.</td> --->
|
||||
|
||||
<!--- <td width="3%">GPL с НДС</td>
|
||||
@@ -156,6 +164,7 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
|
||||
<!--- видимо, Free Tier и единицу измерения нужно брать и для строк, которые не найдены в договоре
|
||||
(еше можно было бы туда для полноты добавлять GPL цену, но для этого нужен каталог)
|
||||
Добавить реквизиты договора? --->
|
||||
<cfset accum_cost_total = 0/>
|
||||
<cfoutput query="report.qCharge" group="year">
|
||||
<cfoutput group="month">
|
||||
<tr><td colspan="99"><cfif month GT 0><b>#year#-#numberFormat(month,"00")#</b></cfif></td></tr>
|
||||
@@ -164,7 +173,7 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
|
||||
<cfset var accum_metric_svc = 0/>
|
||||
<tr>
|
||||
<td bgcolor="##ccc"></td>
|
||||
<td bgcolor="##ccc" colspan="12">
|
||||
<td bgcolor="##ccc" colspan="99">
|
||||
#svc# <b>#service_code#</b>
|
||||
<cfif len(serviceCustomName)>(#serviceCustomName#)</cfif>
|
||||
<cfif serviceHash EQ "">(не найдено в договоре)</cfif>
|
||||
@@ -179,7 +188,7 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
|
||||
<td></td>
|
||||
<td colspan="10">#code# <cfif code EQ "">(#m_code#)</cfif></td>
|
||||
<td>#svc#</td>
|
||||
<td>#component# </td>
|
||||
<td colspan="99">#component# </td>
|
||||
</tr>
|
||||
|
||||
<cfset accum_metric_dimension_1 = 0/>
|
||||
@@ -196,33 +205,43 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
|
||||
<td><!--- free tier ---></td>
|
||||
<td class="r" style="font-size:120%; padding:0 1em;"></td>
|
||||
<td class="c">#unit#</td>
|
||||
<td class="r">#gaussianRound(discounted_price,2)#</td>
|
||||
<td class="r">#gaussianRoundSafe(discounted_price,2)#</td>
|
||||
<td class="r"><!--- здесь не вычисляется стоимость, потому что Free Tier вычитается после агрегации (скидочные политики реализовывать будет непросто) ---></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<!--- сырые исходные данные без округлений --->
|
||||
<td class="r gray">#price#</td>
|
||||
<td class="r gray">#discounted_price#</td>
|
||||
<!--- <td class="r gray">#discounted_price2#</td> --->
|
||||
<td class="r gray">#service_quantity#</td>
|
||||
<td class="r gray">#service_discount#</td>
|
||||
<td class="r gray">#component_quantity#</td>
|
||||
<td class="r gray">#component_discount#</td>
|
||||
</tr>
|
||||
</cfoutput>
|
||||
|
||||
<cfset cost_dimension_1 = precisionEvaluate("gaussianRound(max(accum_metric_dimension_1 - free_tier, 0)*gaussianRound(discounted_price, 2), 2)")/>
|
||||
<cfset final_metric_dimension_1 = ceiling(accum_metric_dimension_1)/>
|
||||
<cfset cost_dimension_1 = (isNumeric(discounted_price)) ? precisionEvaluate("gaussianRound(max(final_metric_dimension_1 - free_tier, 0)*gaussianRound(discounted_price, 2), 2)"):0/>
|
||||
|
||||
<tr class="b">
|
||||
<td></td>
|
||||
<td colspan="4">Итого #code# <cfif code EQ "">(#m_code#)</cfif>:</td>
|
||||
<td class="r"><cfif accum_metric_dimension_1 NEQ 0 AND isNumeric(accum_metric_dimension_1)>#round(accum_metric_dimension_1,3)#</cfif></td>
|
||||
<td class="r"><cfif final_metric_dimension_1 NEQ 0 AND isNumeric(final_metric_dimension_1)>#round(final_metric_dimension_1,3)#</cfif></td>
|
||||
<td class="r">#free_tier#</td>
|
||||
<td class="r" style="font-size:120%; padding:0 1em;"><cfif accum_metric_dimension_1 NEQ 0 AND isNumeric(accum_metric_dimension_1)>#max(ceiling(accum_metric_dimension_1) - free_tier, 0)#</cfif></td>
|
||||
<td class="r" style="font-size:120%; padding:0 1em;"><cfif final_metric_dimension_1 NEQ 0 AND isNumeric(final_metric_dimension_1) AND isNumeric(free_tier)>#max(final_metric_dimension_1 - free_tier, 0)#</cfif></td>
|
||||
<td class="c">#unit#</td>
|
||||
<td class="r">#gaussianRound(discounted_price,2)#</td><!--- если цена менялась в периоде, неправильно показыввать последнюю - но мы не тестировали с изменением в периоде --->
|
||||
<td class="r">#gaussianRoundSafe(discounted_price,2)#</td><!--- если цена менялась в периоде, неправильно показыввать последнюю - но мы не тестировали с изменением в периоде --->
|
||||
<!--- непонятно, как вычитать Free Tier для неполных месяцев. Если с 15 по 15 - вычитать 2 раза, вычитать (каким-то сложным образом) 1 раз или ни разу --->
|
||||
<!--- видимо, нужно сформировать тест кейсы на неполные месяцы. Где взять биллинговые данные для этого ВПР --->
|
||||
<td class="r">#cost_dimension_1#</td>
|
||||
<cfset accum_cost_svc = precisionEvaluate("accum_cost_svc + cost_dimension_1")/><!--- простое сложение дает ошибку округления и лишние знаки, но и precisionEvaluate не выглядит слишком надежным --->
|
||||
<td>#svc# <!--- #service_code# ---></td>
|
||||
<td>#component# </td>
|
||||
<td colspan="99">#component#</td>
|
||||
</tr>
|
||||
|
||||
</cfoutput><!--- serviceStart --->
|
||||
</cfoutput><!--- code --->
|
||||
<cfset accum_cost_total += isNumeric(accum_cost_svc) ? accum_cost_svc : 0/>
|
||||
<tr>
|
||||
<td bgcolor="##eee" style="border-bottom:2px solid gray;"></td>
|
||||
<td bgcolor="##eee" colspan="9" class="r" style="border-bottom:2px solid gray;">
|
||||
@@ -241,6 +260,7 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
|
||||
<tr><td colspan="99"> </td></tr>
|
||||
</cfoutput>
|
||||
</cfoutput>
|
||||
<cfoutput><tr class="b"><td colspan="99">Total: #accum_cost_total# </td></tr></cfoutput>
|
||||
|
||||
</table>
|
||||
|
||||
@@ -268,7 +288,7 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
|
||||
<tr>
|
||||
<td>#contractOutsideId#</td>
|
||||
<td>#additionalAgreementName#</td>
|
||||
<td class="c">#serviceType#</td>
|
||||
<td class="c"<cfif serviceType EQ 3> style="background:lightgreen"</cfif>>#serviceType#</td>
|
||||
<td>#serviceCustomName#</td>
|
||||
<td class="c">#serviceQuantity#</td>
|
||||
<td class="r">#serviceDiscount#</td>
|
||||
@@ -276,11 +296,11 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
|
||||
<td class="c">#dateFormat(serviceEnd, "YYYY-MM-DD")#</td>
|
||||
<td>#component#</td>
|
||||
<td>#componentCode#</td>
|
||||
<td class="r">#quantity#</td>
|
||||
<td class="r">#componentQuantity#</td>
|
||||
<td class="c">#measureShort#</td>
|
||||
<td class="r">#gaussianRound(priceWVat,2)#</td>
|
||||
<td class="r">#discount#</td>
|
||||
<td class="r">#gaussianRound((100-serviceDiscount)/100*(100-discount)/100*priceWVat,4)#</td>
|
||||
<td class="r">#gaussianRoundSafe(priceWVat,2)# | #priceWVat#</td>
|
||||
<td class="r">#componentDiscount#</td>
|
||||
<td class="r">#gaussianRoundSafe((100-serviceDiscount)/100*(100-componentDiscount)/100*priceWVat,7)#/td>
|
||||
</tr>
|
||||
</cfoutput>
|
||||
|
||||
@@ -293,6 +313,10 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
|
||||
|
||||
<!--- select * from s3billing.billing_per_user_fix_pl('2025-05-01 00:00:00','2025-06-01 00:00:00','1395'); --->
|
||||
<cfscript>
|
||||
string function gaussianRoundSafe(any num, numeric precision = 0, string default="") {
|
||||
return (isNumeric(arguments.num)) ? gaussianRound(arguments.num, arguments.precision) : arguments.default;
|
||||
}
|
||||
|
||||
function gaussianRound(numeric number, numeric precision = 0) {
|
||||
var factor = 10 ^ precision;
|
||||
var scaledNumber = number * factor;
|
||||
|
||||
Reference in New Issue
Block a user