071 deep debug

This commit is contained in:
2026-08-14 18:07:24 +03:00
parent 5401b7e96d
commit 91b6687220
8 changed files with 277 additions and 127 deletions
+22 -4
View File
@@ -38,6 +38,20 @@
<cfset request.DS = "#this.datasource#"/> <cfset request.DS = "#this.datasource#"/>
<cfset request.language=""/> <cfset request.language=""/>
<cfset getDS(this.datasource)/> <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 не получается ---> <!--- кажется, нужно инициализировать датасорцы в псевдоконструкторе - onRequest не получается --->
<!--- *** проверить, что есть - если есть, не создавать ---> <!--- *** проверить, что есть - если есть, не создавать --->
@@ -94,7 +108,7 @@
<!--- global settings ---> <!--- global settings --->
<cfset request.RECORDS_PER_PAGE=500/> <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#/> <cfheader name="X-Application-Version" value=#request.APP_VERSION#/>
<cfset request.STAND=getStand()/> <cfset request.STAND=getStand()/>
@@ -155,8 +169,8 @@
---> --->
<!--- <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>
@@ -196,14 +210,17 @@
<cfscript> <cfscript>
var requestData = GetHttpRequestData(); var requestData = GetHttpRequestData();
var headers = requestData.headers; var headers = requestData.headers;
var local_token = false; //для отладки мы можем использовать токен из окружения, но когда он истекает, это легко упустить и потом искать проблему
//var authorizationHeader = server.system.environment?.AUTHORIZATION; //var authorizationHeader = server.system.environment?.AUTHORIZATION;
if (structKeyExists(headers,"Authorization")) { if (structKeyExists(headers,"Authorization")) {
request.auth_header = headers.Authorization; request.auth_header = headers.Authorization;
//writedump(headers.Authorization);
} else { } else {
//request.auth_header = createObject("java", "java.lang.System").getEnv("AUTHORIZATION"); //request.auth_header = createObject("java", "java.lang.System").getEnv("AUTHORIZATION");
request.auth_header = server.system.environment?.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*/ if (isNull(request.auth_header)){ /*for local debug we can inject long-living token into environment*/
structDelete(request,"auth_header"); structDelete(request,"auth_header");
} else { } else {
@@ -236,8 +253,9 @@
if (prefix.status_code NEQ 200) { if (prefix.status_code NEQ 200) {
//writedump(this.iamServiceUrl); //writedump(this.iamServiceUrl);
//writedump(resp); //writedump(resp);
//abort;
var iamStatusCode = (isValid("integer", prefix.status_code)) ? val(prefix.status_code) : 500; 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; //abort;
//throw("IDP response not OK"); //throw("IDP response not OK");
} }
+3
View File
@@ -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;
+3 -1
View File
@@ -107,10 +107,12 @@ ALTER SEQUENCE ngcloud_ru.storage_profile_types_id_seq OWNED BY ngcloud_ru.stora
CREATE TABLE ngcloud_ru.tenant ( CREATE TABLE ngcloud_ru.tenant (
id uuid NOT NULL, id uuid NOT NULL,
name character varying(255) 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; ALTER TABLE ngcloud_ru.tenant OWNER TO vc_billing;
-- --
+11 -3
View File
@@ -88,18 +88,26 @@ create table vmreports.storage (
"Organization" varchar(255) NULL, "Organization" varchar(255) NULL,
"WZ" varchar(255) NULL, "WZ" varchar(255) NULL,
"Cluster" varchar(255) NULL, "Cluster" varchar(255) NULL,
"Disk" varchar(255) NULL, "Disk" varchar(255) NOT NULL, --part of the key
"Storage_Policy" varchar(255) NULL, "Storage_Policy" varchar(255) NULL,
"Disk_Allocated" float NULL, "Disk_Allocated" float NULL,
"Disk_Used" float NULL, "Disk_Used" float NULL,
"VM_is_ON" float NULL, "VM_is_ON" float NULL,
"Object_UUID" uuid NOT NULL, "Object_UUID" uuid NOT NULL,
ts timestamptz 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; --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; drop table if exists vmreports.vm_gpu;
create table vmreports.vm_gpu ( create table vmreports.vm_gpu (
"Name" varchar(255) NULL, "Name" varchar(255) NULL,
+99
View File
@@ -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;
}
}
+91 -95
View File
@@ -86,8 +86,10 @@
<!--- Важно: если в периоде было 2+ цены, то строки должны быть замножены, а цена за период перестает иметь смысл (не показывать же среднее арифметическое, это неправильно) ---> <!--- Важно: если в периоде было 2+ цены, то строки должны быть замножены, а цена за период перестает иметь смысл (не показывать же среднее арифметическое, это неправильно) --->
<!--- Left Outer Join with Free Tier ---> <!--- Left Outer Join with Free Tier --->
<cfquery name="qSpec" dbtype="query"> <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 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.discount + s.serviceDiscount - s.discount*s.serviceDiscount/100) as discount , 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 ,s.serviceStart, s.serviceEnd, s.serviceHash, s.serviceCustomName, f.free_tier
from qSpecification s, qFreeTier f from qSpecification s, qFreeTier f
WHERE s.fullCode=f.code WHERE s.fullCode=f.code
@@ -95,13 +97,18 @@
UNION UNION
select s.catalogName as svc, s.component, s.serviceCode as service_code, s.fullCode as code, s.measureShort as unit 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.discount + s.serviceDiscount - s.discount*s.serviceDiscount/100) as discount , 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 ,s.serviceStart, s.serviceEnd, s.serviceHash, s.serviceCustomName, 0
from qSpecification s from qSpecification s
WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsqltype="cf_sql_varchar" list="true">) WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsqltype="cf_sql_varchar" list="true">)
AND s.serviceType = 3 AND s.serviceType = 3
</cfquery> </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)> <cfif (ATTRIBUTES.debug)>
qSpec (Left Outer Join with Free Tier) qSpec (Left Outer Join with Free Tier)
@@ -124,7 +131,7 @@ CREATE INDEX idx_tenant_wzcode ON ngcloud_ru.tenant (wzcode); --->
<!--- *** Нужно еще реализовать в модели данных (каталоге) политики бесплатного минимума (free tier), сейчас прибито гвоздями ---> <!--- *** Нужно еще реализовать в модели данных (каталоге) политики бесплатного минимума (free tier), сейчас прибито гвоздями --->
<cfquery name="qComputing"> <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 case
WHEN vdc.name LIKE '%-v1cl1%' THEN 'iaas.ngc.i29' WHEN vdc.name LIKE '%-v1cl1%' THEN 'iaas.ngc.i29'
WHEN vdc.name LIKE '%-v1cl2%' THEN 'iaas.ngc.i31' 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 from ngcloud_ru.capacity_resource
join ngcloud_ru.vdc on capacity_resource.vdc_id=vdc.id join ngcloud_ru.vdc on capacity_resource.vdc_id=vdc.id
join ngcloud_ru.tenant on tenant.id=vdc.tenant_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=#attributes.dt_start#/>
AND capacity_resource.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dateAdd("d",1,attributes.dt_finish)#/> 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 group by tenant.clientid, vdc.name, capacity_resource.timestamp::date
order by tenant.wzcode, vdc.name, capacity_resource.timestamp::date; order by tenant.clientid, vdc.name, capacity_resource.timestamp::date;
</cfquery> </cfquery>
<cfif (ATTRIBUTES.debug)> <cfif (ATTRIBUTES.debug)>
@@ -172,7 +179,7 @@ from a
<!--- переделать storage_profile_types с расшифровкой ssd, sata, можно прямо до каталожного кода ---> <!--- переделать storage_profile_types с расшифровкой ssd, sata, можно прямо до каталожного кода --->
<!--- очень долго исполняется ---> <!--- очень долго исполняется --->
<cfquery name="qStorage"> <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 case
WHEN vdc.name LIKE '%-v1cl1%' THEN 'iaas.ngc.i29' WHEN vdc.name LIKE '%-v1cl1%' THEN 'iaas.ngc.i29'
WHEN vdc.name LIKE '%-v1cl2%' THEN 'iaas.ngc.i31' WHEN vdc.name LIKE '%-v1cl2%' THEN 'iaas.ngc.i31'
@@ -182,15 +189,15 @@ from a
WHEN vdc.name LIKE '%-v1cl7%' THEN 'iaas.ngc.a40' WHEN vdc.name LIKE '%-v1cl7%' THEN 'iaas.ngc.a40'
ELSE '['||vdc.name||']' END ELSE '['||vdc.name||']' END
|| CASE || CASE
WHEN storage_profile_types.name ILIKE '%FASTSSD%' THEN '.fstssd-m' WHEN storage_profile_types.name ILIKE '%FASTSSD%' THEN '.fstssd'
WHEN storage_profile_types.name ILIKE '%SSD%' THEN '.ssd-m' WHEN storage_profile_types.name ILIKE '%SSD%' THEN '.ssd'
WHEN storage_profile_types.name ILIKE '%SAS%' THEN '.sas-m' WHEN storage_profile_types.name ILIKE '%SAS%' THEN '.sas'
WHEN storage_profile_types.name ILIKE '%SATA%' THEN '.sata-m' WHEN storage_profile_types.name ILIKE '%SATA%' THEN '.sata'
ELSE '.['|| storage_profile_types.name ||']' END ELSE '.['|| storage_profile_types.name ||']' END
as code, as code,
storage_profile_types.name, -- *** это не тип, а профайл storage_profile_types.name, -- *** это не тип, а профайл
sum("limit")/1024./4./720 as gb_m_limit, sum("limit")/1024./4./<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/> as gb_m_limit,
sum(used)/1024./4./720 as gb_m_used, sum(used)/1024./4./<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/> as gb_m_used,
capacity_storage.timestamp::date as dt, capacity_storage.timestamp::date as dt,
vdc.name as dimension_1, vdc.name as dimension_1,
storage_profile_types.name as dimension_2 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.vdc on capacity_storage.vdc_id=vdc.id
join ngcloud_ru.tenant on tenant.id=vdc.tenant_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 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=#attributes.dt_start#/>
AND capacity_storage.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dateAdd("d",1,attributes.dt_finish)#/> 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 group by tenant.clientid, vdc.name, storage_profile_types.name, capacity_storage.timestamp::date
order by tenant.wzcode, 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> </cfquery>
<!--- create index IX_capacity_storage_sort on ngcloud_ru.capacity_storage(vdc_id,storage_profile_types_id,timestamp); ---> <!--- 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"> <cfquery name="qS3Vol">
select /* *** ниже дублирование кода агрегации - round ceil etc, обратить внимание при правке */ select /* *** ниже дублирование кода агрегации - round ceil etc, обратить внимание при правке */
/* *** переделать на avg - сначала агрегировать по бакетам*/ /* *** переделать на avg - сначала агрегировать по бакетам*/
(sum(bucket_stat.usage_rgw_main_size_actual)/720) as vol_B (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)/720/1024/1024/1024) as vol_GB ,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 ,case
when bucket_stat.placement_id = 1 then 'paas.s3.ceph' when bucket_stat.placement_id = 1 then 'paas.s3.ceph'
when bucket_stat.placement_id = 2 then 'paas.s3.cphc' when bucket_stat.placement_id = 2 then 'paas.s3.cphc'
@@ -233,10 +237,10 @@ from a
--,bucket_stat.placement_id --,bucket_stat.placement_id
,'WZ'||lpad(bucket_stat.owner, 5, '0') as wz -- *** здесь не число, а строка, поэтому другая формула обратной сборки WZ ,'WZ'||lpad(bucket_stat.owner, 5, '0') as wz -- *** здесь не число, а строка, поэтому другая формула обратной сборки WZ
,bucket_stat.timestamp_addition::date as dt ,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 = 1 then 'paas.s3.ceph'
when bucket_stat.placement_id = 2 then 'paas.s3.cphc' 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 as dimension_1
,null as dimension_2 ,null as dimension_2
from s3billing.bucket_stat 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 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; order by bucket_stat.owner, bucket_stat.placement_id, bucket_info.name, bucket_stat.timestamp_addition::date;
</cfquery> </cfquery>
<cfif (ATTRIBUTES.debug)> <cfif (ATTRIBUTES.debug)>
qS3Vol qS3Vol
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput> <cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
@@ -267,21 +270,14 @@ from a
OR category.name LIKE 'options%' OR category.name LIKE 'options%'
) THEN ops ELSE 0 END ) THEN ops ELSE 0 END
) as get ) as get
,ceil(sum( ,(sum(
CASE WHEN CASE WHEN
(category.name LIKE 'get%' (category.name LIKE 'get%'
OR category.name LIKE 'head%' OR category.name LIKE 'head%'
OR category.name LIKE 'options%' OR category.name LIKE 'options%'
) THEN ops ELSE 0 END ) THEN ops ELSE 0 END
)/10000) as get_10k )/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( ,sum(
CASE WHEN CASE WHEN
@@ -291,7 +287,7 @@ from a
OR category.name LIKE 'list%' OR category.name LIKE 'list%'
) THEN ops ELSE 0 END ) THEN ops ELSE 0 END
) as put ) as put
,ceil(sum( ,(sum(
CASE WHEN CASE WHEN
(category.name LIKE 'put%' (category.name LIKE 'put%'
OR category.name LIKE 'post%' OR category.name LIKE 'post%'
@@ -299,20 +295,10 @@ from a
OR category.name LIKE 'list%' OR category.name LIKE 'list%'
) THEN ops ELSE 0 END ) THEN ops ELSE 0 END
)/10000) as put_10k )/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 ,(sum(bytes_sent)) as bytes_sent
,ceil(sum(bytes_sent)/1024/1024/1024) as bytes_sent_GB ,(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 ,case
when bucket_info.placement_id = 1 then 'paas.s3.ceph' when bucket_info.placement_id = 1 then 'paas.s3.ceph'
when bucket_info.placement_id = 2 then 'paas.s3.cphc' when bucket_info.placement_id = 2 then 'paas.s3.cphc'
@@ -321,10 +307,10 @@ from a
--category.name, bucket_info.name --category.name, bucket_info.name
,'WZ'||lpad(user_info.name, 5, '0') as wz ,'WZ'||lpad(user_info.name, 5, '0') as wz
,usage_bucket_by_user.time::date as dt ,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 = 1 then 'paas.s3.ceph'
when bucket_info.placement_id = 2 then 'paas.s3.cphc' 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 as dimension_1
,null as dimension_2 ,null as dimension_2
from s3billing.usage_bucket_by_user from s3billing.usage_bucket_by_user
@@ -352,12 +338,12 @@ from a
<!--- собрали все метрики в один резалтсет ---> <!--- собрали все метрики в один резалтсет --->
<!--- Здесь мы знаем единицы измерения, причем надежнее, чем в спецификации ---> <!--- Здесь мы знаем единицы измерения, причем надежнее, чем в спецификации --->
<cfquery name="qUnifiedMetric" dbType="query"> <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 dimension_1, 'vdc' as dimension_1_name, dimension_2, NULL as dimension_2_name, 'ядро-ч' as unit
from qComputing from qComputing
union all 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 dimension_1, 'vdc' as dimension_1_name, dimension_2, NULL as dimension_2_name, 'ГБайт-ч' as unit
from qComputing from qComputing
@@ -367,22 +353,22 @@ from a
from qStorage from qStorage
union all 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 dimension_1, 'bucket' as dimension_1_name, dimension_2, NULL as dimension_2_name, 'ГБайт-м' as unit
from qS3Vol from qS3Vol
union all 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 dimension_1, 'bucket' as dimension_1_name, dimension_2, NULL as dimension_2_name, '10тыс.шт.' as unit
from qS3OpsTrf from qS3OpsTrf
union all 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 dimension_1, 'bucket' as dimension_1_name, dimension_2, NULL as dimension_2_name, '10тыс.шт.' as unit
from qS3OpsTrf from qS3OpsTrf
union all 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 dimension_1, 'bucket' as dimension_1_name, dimension_2, NULL as dimension_2_name, 'ГБайт' as unit
from qS3OpsTrf from qS3OpsTrf
@@ -398,7 +384,6 @@ from a
<!--- лучше никогда не связываться с QoQ, но если все исполнять в исходной бд, это скорее мегаселект с большим числом CTE ---> <!--- лучше никогда не связываться с QoQ, но если все исполнять в исходной бд, это скорее мегаселект с большим числом CTE --->
<!--- надо было сразу делать в БД. Причем сводить в одну, чтобы избежать нескольких источников данных... можно было бы и так добиться, но все равно сводим в буферную базу ---> <!--- надо было сразу делать в БД. Причем сводить в одну, чтобы избежать нескольких источников данных... можно было бы и так добиться, но все равно сводим в буферную базу --->
<!--- <cfdump var=#qUnifiedMetric#/> --->
<cfif (ATTRIBUTES.debug)> <cfif (ATTRIBUTES.debug)>
qUnifiedMetric qUnifiedMetric
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput> <cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
@@ -421,20 +406,30 @@ from a
<!--- Вычитание Free Tier можно начинать с начала месяца - для этого можно сгруппировать по месяцам ---> <!--- Вычитание Free Tier можно начинать с начала месяца - для этого можно сгруппировать по месяцам --->
<!--- Кажется, здесь еще рано агрегировать, если мы хотим OUTER JOIN ---> <!--- Кажется, здесь еще рано агрегировать, если мы хотим 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 SELECT
<d:field_set titleMapOut="titleMap" lengthOut="fieldCount"> <d:field_set titleMapOut="titleMap" lengthOut="fieldCount">
<d:field title="Код компонента">s.code as s_code</d:field> <d:field title="Код компонента">s.code as s_code</d:field>
<d:field title="Код метрики">m.code as m_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.svc</d:field>
<d:field title="Компонент">s.component</d:field> <d:field title="Компонент">s.component</d:field>
<d:field title="Ед.изм.">m.unit</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 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.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.raw_metric</d:field>
<d:field title="Приведенная метрика" cfSqlType="CF_SQL_NUMERIC">m.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> <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</d:field>
<d:field>m.dimension_2_name</d:field> <d:field>m.dimension_2_name</d:field>
</d:field_set> </d:field_set>
FROM qSpec as s, qUnifiedMetric as m FROM qSpec s
WHERE s.code=m.code FULL OUTER JOIN qUnifiedMetric m ON (
s.sku_code=m.code
AND s.serviceStart <= m.dt AND s.serviceStart <= m.dt
AND (m.dt <= s.serviceEnd OR s.serviceEnd IS NULL) AND (m.dt <= s.serviceEnd OR s.serviceEnd IS NULL)
)
WHERE 1=1
<cfif #attributes.restrict_to_own_instances#> <cfif #attributes.restrict_to_own_instances#>
AND m.wz=<cfqueryparam cfsqltype="cf_sql_varchar" value=#request.auth.wz#/> AND m.wz=<cfqueryparam cfsqltype="cf_sql_varchar" value=#request.auth.wz#/>
</cfif> </cfif>
<!--- таким замысловатым способом мы реализуем FULL JOIN --->
<!--- <!--- таким замысловатым способом мы реализуем FULL JOIN --->
UNION ALL UNION ALL
SELECT NULL, m.code, NULL, NULL, NULL, NULL, 0, SELECT NULL, m.code, NULL, 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 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 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 UNION ALL
SELECT s.code, NULL, s.service_code, s.svc, s.component, s.unit, s.price*(100-s.discount)/100 as discounted_price, SELECT s.code, NULL, s.service_code, s.sku_code, s.svc, s.component, s.unit, s.discounted_price,
s.price, s.discount, NULL, NULL, s.serviceStart, s.serviceEnd, s.serviceHash, s.serviceCustomName, NULL, 0, NULL, NULL, NULL, NULL 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 FROM qSpec as s
WHERE s.code NOT IN (<cfqueryparam value="#listRemoveDuplicates(ValueList(qUnifiedMetric.code))#" cfsqltype="cf_sql_varchar" list="true">) 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
</cfquery> </cfquery>
<!--- диагностика ошибок в QoQ крайне невнятна и вводит в заблуждение --->
<!---<cfdump var=#qChargeDaily# abort=true/> ---> <!--- <cfdump var=#qChargeDaily# abort=true/> --->
<cfif (ATTRIBUTES.debug)> <cfif (ATTRIBUTES.debug)>
qChargeDaily qChargeDaily
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput> <cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
@@ -491,11 +488,18 @@ from a
<d:field title="Артикул по системам">m_code</d:field> <d:field title="Артикул по системам">m_code</d:field>
<d:field title="Артикул">s_code as code</d:field> <d:field title="Артикул">s_code as code</d:field>
<d:field title="Код услуги">service_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="Услуга по каталогу">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="Компонент">component</d:field>
<d:field title="Ед.изм.">unit</d:field> <d:field title="Ед.изм.">unit</d:field>
<d:field title="Цена &##8381; с НДС" cfSqlType="CF_SQL_NUMERIC">discounted_price</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(raw_metric) as raw_metric</d:field>
<d:field title="Приведенный объем" cfSqlType="CF_SQL_NUMERIC">sum(metric) as 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> <d:field title="serviceStart" cfSqlType="CF_SQL_TIMESTAMP">serviceStart</d:field>
@@ -512,31 +516,23 @@ from a
<d:field title="Месяц" cfSqlType="CF_SQL_INTEGER">month(dt) as month</d:field> <d:field title="Месяц" cfSqlType="CF_SQL_INTEGER">month(dt) as month</d:field>
</d:field_set> </d:field_set>
from qChargeDaily 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 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> </cfquery>
<!--- в связи с бедностью языка QoQ (нет CASE) грубо патчим резалтсет ---> <!--- в связи с бедностью языка QoQ (нет CASE) грубо патчим резалтсет --->
<cfloop query="qChargeMonthly"> <!--- <cfloop query="qChargeMonthly">
<cfset qChargeMonthly.code = (len(code) GT 0) ? code : m_code/><!--- имя в левой части должно быть квалифицировано запросом (резалтсетом) ---> <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> </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)> <cfif (ATTRIBUTES.debug)>
qChargeMonthly qChargeMonthly
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput> <cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
+4 -4
View File
@@ -55,9 +55,9 @@
,componentCode ,componentCode
,component ,component
,measureShort ,measureShort
,quantity ,componentQuantity
,priceWVat ,priceWVat
,discount ,componentDiscount
,atomicCode ,atomicCode
,fullCode ,fullCode
", ",
@@ -93,9 +93,9 @@
componentCode=local.component.componentCode, componentCode=local.component.componentCode,
component=local.component.component, component=local.component.component,
measureShort=local.component.measureShort, measureShort=local.component.measureShort,
quantity=local.component.quantity, componentQuantity=local.component.quantity,
priceWVat=local.component.priceWVat, priceWVat=local.component.priceWVat,
discount=local.component.discount, componentDiscount=local.component.discount,
atomicCode=local.component.atomicCode, atomicCode=local.component.atomicCode,
fullCode="#local.component.componentCode#-#listGetAt('i,f,m',local.service.serviceType)#" fullCode="#local.component.componentCode#-#listGetAt('i,f,m',local.service.serviceType)#"
}])/> }])/>
+37 -13
View File
@@ -130,6 +130,14 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
<th width="10%">Услуга</th> <th width="10%">Услуга</th>
<th width="10%">Компонент</th> <th width="10%">Компонент</th>
<td width="3%">Цена &#8381; до ск. с НДС</td>
<td width="3%">Цена &#8381; co ск. с НДС без окр.</td>
<!--- <td width="3%">Цена &#8381; 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="10%">Клиентс. назв.</td> --->
<!--- <td width="3%">GPL с НДС</td> <!--- <td width="3%">GPL с НДС</td>
@@ -156,6 +164,7 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
<!--- видимо, Free Tier и единицу измерения нужно брать и для строк, которые не найдены в договоре <!--- видимо, Free Tier и единицу измерения нужно брать и для строк, которые не найдены в договоре
(еше можно было бы туда для полноты добавлять GPL цену, но для этого нужен каталог) (еше можно было бы туда для полноты добавлять GPL цену, но для этого нужен каталог)
Добавить реквизиты договора? ---> Добавить реквизиты договора? --->
<cfset accum_cost_total = 0/>
<cfoutput query="report.qCharge" group="year"> <cfoutput query="report.qCharge" group="year">
<cfoutput group="month"> <cfoutput group="month">
<tr><td colspan="99"><cfif month GT 0><b>#year#-#numberFormat(month,"00")#</b></cfif></td></tr> <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/> <cfset var accum_metric_svc = 0/>
<tr> <tr>
<td bgcolor="##ccc"></td> <td bgcolor="##ccc"></td>
<td bgcolor="##ccc" colspan="12"> <td bgcolor="##ccc" colspan="99">
#svc# <b>#service_code#</b> #svc# <b>#service_code#</b>
<cfif len(serviceCustomName)>(#serviceCustomName#)</cfif> <cfif len(serviceCustomName)>(#serviceCustomName#)</cfif>
<cfif serviceHash EQ "">(не найдено в договоре)</cfif> <cfif serviceHash EQ "">(не найдено в договоре)</cfif>
@@ -179,7 +188,7 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
<td></td> <td></td>
<td colspan="10">#code# <cfif code EQ "">(#m_code#)</cfif></td> <td colspan="10">#code# <cfif code EQ "">(#m_code#)</cfif></td>
<td>#svc#</td> <td>#svc#</td>
<td>#component# </td> <td colspan="99">#component# </td>
</tr> </tr>
<cfset accum_metric_dimension_1 = 0/> <cfset accum_metric_dimension_1 = 0/>
@@ -196,33 +205,43 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
<td><!--- free tier ---></td> <td><!--- free tier ---></td>
<td class="r" style="font-size:120%; padding:0 1em;"></td> <td class="r" style="font-size:120%; padding:0 1em;"></td>
<td class="c">#unit#</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 class="r"><!--- здесь не вычисляется стоимость, потому что Free Tier вычитается после агрегации (скидочные политики реализовывать будет непросто) ---></td>
<td></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> </tr>
</cfoutput> </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"> <tr class="b">
<td></td> <td></td>
<td colspan="4">Итого #code# <cfif code EQ "">(#m_code#)</cfif>:</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">#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="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 раз или ни разу ---> <!--- непонятно, как вычитать Free Tier для неполных месяцев. Если с 15 по 15 - вычитать 2 раза, вычитать (каким-то сложным образом) 1 раз или ни разу --->
<!--- видимо, нужно сформировать тест кейсы на неполные месяцы. Где взять биллинговые данные для этого ВПР ---> <!--- видимо, нужно сформировать тест кейсы на неполные месяцы. Где взять биллинговые данные для этого ВПР --->
<td class="r">#cost_dimension_1#</td> <td class="r">#cost_dimension_1#</td>
<cfset accum_cost_svc = precisionEvaluate("accum_cost_svc + cost_dimension_1")/><!--- простое сложение дает ошибку округления и лишние знаки, но и precisionEvaluate не выглядит слишком надежным ---> <cfset accum_cost_svc = precisionEvaluate("accum_cost_svc + cost_dimension_1")/><!--- простое сложение дает ошибку округления и лишние знаки, но и precisionEvaluate не выглядит слишком надежным --->
<td>#svc# <!--- #service_code# ---></td> <td>#svc# <!--- #service_code# ---></td>
<td>#component# </td> <td colspan="99">#component#</td>
</tr> </tr>
</cfoutput><!--- serviceStart ---> </cfoutput><!--- serviceStart --->
</cfoutput><!--- code ---> </cfoutput><!--- code --->
<cfset accum_cost_total += isNumeric(accum_cost_svc) ? accum_cost_svc : 0/>
<tr> <tr>
<td bgcolor="##eee" style="border-bottom:2px solid gray;"></td> <td bgcolor="##eee" style="border-bottom:2px solid gray;"></td>
<td bgcolor="##eee" colspan="9" class="r" style="border-bottom:2px solid gray;"> <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">&nbsp;</td></tr> <tr><td colspan="99">&nbsp;</td></tr>
</cfoutput> </cfoutput>
</cfoutput> </cfoutput>
<cfoutput><tr class="b"><td colspan="99">Total: #accum_cost_total# </td></tr></cfoutput>
</table> </table>
@@ -268,7 +288,7 @@ S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt
<tr> <tr>
<td>#contractOutsideId#</td> <td>#contractOutsideId#</td>
<td>#additionalAgreementName#</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>#serviceCustomName#</td>
<td class="c">#serviceQuantity#</td> <td class="c">#serviceQuantity#</td>
<td class="r">#serviceDiscount#</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 class="c">#dateFormat(serviceEnd, "YYYY-MM-DD")#</td>
<td>#component#</td> <td>#component#</td>
<td>#componentCode#</td> <td>#componentCode#</td>
<td class="r">#quantity#</td> <td class="r">#componentQuantity#</td>
<td class="c">#measureShort#</td> <td class="c">#measureShort#</td>
<td class="r">#gaussianRound(priceWVat,2)#</td> <td class="r">#gaussianRoundSafe(priceWVat,2)# | #priceWVat#</td>
<td class="r">#discount#</td> <td class="r">#componentDiscount#</td>
<td class="r">#gaussianRound((100-serviceDiscount)/100*(100-discount)/100*priceWVat,4)#</td> <td class="r">#gaussianRoundSafe((100-serviceDiscount)/100*(100-componentDiscount)/100*priceWVat,7)#/td>
</tr> </tr>
</cfoutput> </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'); ---> <!--- select * from s3billing.billing_per_user_fix_pl('2025-05-01 00:00:00','2025-06-01 00:00:00','1395'); --->
<cfscript> <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) { function gaussianRound(numeric number, numeric precision = 0) {
var factor = 10 ^ precision; var factor = 10 ^ precision;
var scaledNumber = number * factor; var scaledNumber = number * factor;