Compare commits

...
8 Commits
Author SHA1 Message Date
smishchuk 5401b7e96d 030 s3vol critical fix 2026-07-30 16:02:05 +03:00
smishchuk 0985d285b9 029 additional fields at root 2026-07-29 13:11:52 +03:00
smishchuk bf964bde34 027 query optimized 2026-07-28 13:08:12 +03:00
smishchuk e8df23c2e2 026 ceiling 2026-07-28 12:38:02 +03:00
smishchuk 849a4c2796 024 improvements 2026-07-26 23:04:05 +03:00
smishchuk f7421c1155 023 sslCertificateInstall 2026-07-20 20:31:27 +03:00
smishchuk 9e0fdd7b7b 022 connection diagnostics 2026-07-20 18:26:30 +03:00
smishchuk 3bc7cea1d1 021 dimensions dimension1 2026-07-20 11:06:58 +03:00
17 changed files with 2301 additions and 261 deletions
+90 -12
View File
@@ -8,6 +8,7 @@
<!--- Set up the application. ---> <!--- Set up the application. --->
<cfset this.Name = "PAYG-REPORT" /> <cfset this.Name = "PAYG-REPORT" />
<cfset this.applicationTimeout = createTimeSpan( 0, 3, 0, 0 ) /> <cfset this.applicationTimeout = createTimeSpan( 0, 3, 0, 0 ) />
<cfset this.requestTimeout = createTimeSpan( 0, 0, 2, 0 )/>
<cfset this.sessionmanagement="Yes"/> <cfset this.sessionmanagement="Yes"/>
<cfset this.clientmanagement="No"/> <cfset this.clientmanagement="No"/>
<cfset this.sessiontimeout=CreateTimeSpan(0, 1, 0, 0)/> <cfset this.sessiontimeout=CreateTimeSpan(0, 1, 0, 0)/>
@@ -24,8 +25,11 @@
<cfset this.access_token_endpoint = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/token" /> <cfset this.access_token_endpoint = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/token" />
<cfset this.idpCertUrl = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs" /> <cfset this.idpCertUrl = "https://keycloak.nubes.ru/realms/cloud/protocol/openid-connect/certs" />
<cfset this.client_secret = createObject("java", "java.lang.System").getEnv("IDP_CLIENT_SECRET")/> <cfset this.client_secret = createObject("java", "java.lang.System").getEnv("IDP_CLIENT_SECRET")/>
<cfset this.iam_url = "https://auth-api.ngcloud.ru/api/v1/auth/user"/> <!--- <cfset this.iam_url = "https://auth-api.ngcloud.ru/api/v1/auth/user"/> --->
<cfset this.iamServiceUrl = "https://auth-api-dev.ngcloud.ru/api/v1/auth/user "/>
<cfset this.redirect_uri = "https://#CGI.SERVER_NAME##CGI.SCRIPT_NAME#"/><!--- куда возвращаться от IDP ---> <cfset this.redirect_uri = "https://#CGI.SERVER_NAME##CGI.SCRIPT_NAME#"/><!--- куда возвращаться от IDP --->
<cfset this.crm_cache_host = "elma-cache.adl.nubes.ru"/><!--- *** почему-то приходится устанавливать серт sslCertificateInstall, иначе не коннектится --->
<cfset request.crm_cache_base = "https://#this.crm_cache_host#/contract_ls.cfm"/>
<cfset this.datasource = "rpt"/><!--- к буферной базе ---> <cfset this.datasource = "rpt"/><!--- к буферной базе --->
<cfset this.defaultdatasource = this.datasource/> <cfset this.defaultdatasource = this.datasource/>
@@ -38,7 +42,6 @@
<!--- *** проверить, что есть - если есть, не создавать ---> <!--- *** проверить, что есть - если есть, не создавать --->
<cffunction <cffunction
name="OnApplicationStart" name="OnApplicationStart"
access="public" access="public"
@@ -46,9 +49,23 @@
output="false" output="false"
hint="Fires when the application is first created."> hint="Fires when the application is first created.">
<!--- *** Lucee specific function --->
<cfset sslCertificateInstall(host="elma-cache.adl.nubes.ru", port=443)/><!--- ************************ duplicate address --->
<!--- *** мы не проверяем срок жизни сертификата и его отзыв---> <!--- *** мы не проверяем срок жизни сертификата и его отзыв--->
<!--- Obtain idp certificate OnApplicationStart --->
<cfset application.idpCertificate = getIdpCertificate(this.idpCertUrl)/> <cfset application.idpCertificate = getIdpCertificate(this.idpCertUrl)/>
<!--- <cfset this.iamServiceUrl = locateIamService()/> --->
<!--- <cfreturn super.onApplicationStart() /> --->
<cfreturn true />
</cffunction>
<cffunction name="onRequestStart" returnType="boolean">
<cfargument name="targetPage" type="string" required="true" />
<cfif structKeyExists(url, "application_reset")>
<cfset ApplicationStop() />
<cflocation url="index.cfm" addtoken="false" />
</cfif>
<cfreturn true /> <cfreturn true />
</cffunction> </cffunction>
@@ -77,7 +94,7 @@
<!--- global settings ---> <!--- global settings --->
<cfset request.RECORDS_PER_PAGE=500/> <cfset request.RECORDS_PER_PAGE=500/>
<cfset request.APP_VERSION="0.00.020"/> <cfset request.APP_VERSION="0.00.030"/>
<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()/>
@@ -177,15 +194,29 @@
<!--- Вариант: если есть Authorization:bearer, идем с ним к IAM ---> <!--- Вариант: если есть Authorization:bearer, идем с ним к IAM --->
<cfscript> <cfscript>
var requestData = GetHttpRequestData(); var requestData = GetHttpRequestData();
var headers = requestData.headers; var headers = requestData.headers;
if (structKeyExists(headers,"authorization")) { // аутентификация для клиента ЛК //var authorizationHeader = server.system.environment?.AUTHORIZATION;
var authorizationHeader = headers.authorization;
if (structKeyExists(headers,"Authorization")) {
request.auth_header = headers.Authorization;
} else {
//request.auth_header = createObject("java", "java.lang.System").getEnv("AUTHORIZATION");
request.auth_header = server.system.environment?.AUTHORIZATION; //так короче
if (isNull(request.auth_header)){ /*for local debug we can inject long-living token into environment*/
structDelete(request,"auth_header");
} else {
request.auth_header = "Bearer #request.auth_header#";
}
}
// if (structKeyExists(headers,"authorization")) { // аутентификация для клиента ЛК
if (structKeyExists(request,"auth_header")) { // аутентификация для клиента ЛК
var authorizationHeader = request.auth_header;
if (authorizationHeader.startsWith("Bearer ")) { if (authorizationHeader.startsWith("Bearer ")) {
var bearerToken = Mid(authorizationHeader, 8); var bearerToken = Mid(authorizationHeader, 8);
writeDump(bearerToken); //writeDump(bearerToken);
//var token=toString(binaryDecode(bearerToken, "base64")); //var token=toString(binaryDecode(bearerToken, "base64"));
//writeDump(token); //writeDump(token);
//jwt = new lib.jwt(this.client_secret); //jwt = new lib.jwt(this.client_secret);
@@ -194,7 +225,7 @@
//} //}
var result=""; var result="";
//try { //try {
var iamService = new http(method = "GET", charset = "utf-8", url = #this.iam_url#, timeout="5"); var iamService = new http(method = "GET", charset = "utf-8", url = #this.iamServiceUrl#, timeout="5");
iamService.addParam(type = "HEADER", name = "Accept", value = "application/json"); iamService.addParam(type = "HEADER", name = "Accept", value = "application/json");
iamService.addParam(type = "HEADER", name = "Authorization", value = "#authorizationHeader#"); //passthrough iamService.addParam(type = "HEADER", name = "Authorization", value = "#authorizationHeader#"); //passthrough
//writedump(this.iamServiceUrl);abort; //writedump(this.iamServiceUrl);abort;
@@ -206,13 +237,15 @@
//writedump(this.iamServiceUrl); //writedump(this.iamServiceUrl);
//writedump(resp); //writedump(resp);
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.iam_url# Status Code: #prefix.status_code#"); throw(message="IAM error", detail="URL: #this.iamServiceUrl# Status Code: #prefix.status_code#");
//abort; //abort;
//throw("IDP response not OK"); //throw("IDP response not OK");
} }
result = prefix.filecontent; result = prefix.filecontent;
var idpUserData=deserializeJson(result); var idpUserData=deserializeJson(result);
//request.auth.wz=idpUserData.userInfo.ClientId;//GUID! request.auth.wz=idpUserData.userInfo.ClientId;
//writeDump(idpUserData);
request.auth.login = idpUserData.userInfo.login; request.auth.login = idpUserData.userInfo.login;
@@ -313,7 +346,16 @@
// после возвращения от IDP нам нужно будет зачистить сессию SSO, сделаем это по слову logout // после возвращения от IDP нам нужно будет зачистить сессию SSO, сделаем это по слову logout
} }
request.auth.wz='WZ01348'; //******************* //request.auth.wz='WZ01348'; //*******************
//request.auth.wz='WZ01005'; //*******************
//request.auth.wz='WZ01653'; //*******************
//request.auth.wz='WZ02319'; //*******************
//request.auth.wz='WZ01762'; //*******************
//writedump(request.auth);abort;
//********************
//костыль
if (structKeyExists(URL,"wz")) { request.auth.wz=URL.wz }
//*********************
</cfscript> </cfscript>
@@ -519,4 +561,40 @@
</cftry> </cftry>
<cfthrow type="Context validation error" message="RETHROW() called outside TRY-CATCH"/> <cfthrow type="Context validation error" message="RETHROW() called outside TRY-CATCH"/>
</cffunction> </cffunction>
<!--- как определить стенд? Из персистенса. Из окружения --->
<!--- <cffunction name="locateIamService"><!--- *** архитектурный колхоз --->
<!--- почему-то мы не видим других функций, когда нас вызывают onApplicationStart. С другой стороны, а почему вообще должны вызывать без перезагрузки приложения --->
<cfset var stand=""/>
<!--- дублируем код, потому что getStand отсюда почему-то не виден --->
<cftry>
<cfquery name=qConfig>
select value as stand from config
where name='STAND'
</cfquery>
<cfset stand = qConfig.stand/>
<cfswitch expression=#stand#>
<cfcase value=",dev">
<cfreturn "https://auth-api-dev.ngcloud.ru/api/v1" />
</cfcase>
<cfcase value="test">
<cfreturn "https://auth-api-test.ngcloud.ru/api/v1" />
</cfcase>
<cfcase value="prod">
<cfreturn "https://auth-api.ngcloud.ru/api/v1" />
</cfcase>
<cfdefaultcase><!--- proceed to the legacy locator ---></cfdefaultcase>
</cfswitch>
<cfcatch type="ANY">
<!--- do nothing, proceed to the legacy locator --->
<cfrethrow/>
<cfthrow message="IAM service unavailable" detail="there is no IAM for stand #stand# defined"/>
</cfcatch>
</cftry>
</cffunction> --->
</cfcomponent> </cfcomponent>
+10
View File
@@ -0,0 +1,10 @@
pvdc_map(pvdc,code) as (VALUES
('v1cl1-vsan-pvdc',''),
('v1cl2-pvdc',''),
('v2cl2-1c-pvdc',''),
('v1cl4-vsan-pvdc',''),
('v1cl6-vsan-pvdc',''),
('v1cl7-vsan-pvdc',''),
('hystax-pvdc',''),
('','')
)
+205
View File
@@ -0,0 +1,205 @@
DROP FOREIGN TABLE IF EXISTS vmreports.compute CASCADE;
DROP FOREIGN TABLE IF EXISTS vmreports.storage CASCADE;
DROP FOREIGN TABLE IF EXISTS vmreports.host CASCADE;
DROP FOREIGN TABLE IF EXISTS vmreports.vm_gpu CASCADE;
DROP FOREIGN TABLE IF EXISTS vmreports.vm_power_state CASCADE;
--------------------------------------------------------------------------------
CREATE SCHEMA IF NOT EXISTS vmreports;
ALTER SCHEMA vmreports OWNER TO dbo;
GRANT USAGE ON SCHEMA vmreports TO nifi,appserver,reader,analytics,analyst;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT SELECT ON TABLES TO appserver,reader;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT SELECT, REFERENCES ON TABLES TO appserver,analytics,analyst;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT ALL ON TABLES TO nifi;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT USAGE ON SEQUENCES TO nifi, appserver;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- TOC entry 721 (class 1259 OID 9816992)
-- Name: compute; Type: TABLE; Schema: vmreports; Owner: smishchuk
--
CREATE TABLE vmreports.compute (
"Name" character varying(255),
"UUID_VM" character varying(255),
"VDC" character varying(255),
"Organization_UUID" uuid,
"Organization" character varying(255),
"WZ" character varying(255),
"Cluster" character varying(255),
"Host" character varying(255),
"Host_Model" character varying(255),
"CPU_Model" character varying(255),
"Host_CPU_Cores" double precision,
"Host_CPU_Sockets" double precision,
"Host_CPU_Speed" double precision,
"Host_RAM" double precision,
"Host_CPU_Used" double precision,
"Host_CPU_Used_Avg" double precision,
"VDC_CPU_Reserv" double precision,
"VDC_CPU_Speed" double precision,
"VM_vCPU_count" double precision,
"VM_CPU_Used" double precision,
"VM_CPU_Used_AVG" double precision,
"VM_CPU_Reserv_GHz" double precision,
"VM_CPU_Reserv_perc" double precision,
"VM_RAM_Allocated" double precision,
"VM_RAM_Used" double precision,
"VM_is_ON" double precision,
"Object_UUID" uuid NOT NULL,
ts timestamp with time zone NOT NULL
);
ALTER TABLE vmreports.compute OWNER TO dbo;
--
-- TOC entry 722 (class 1259 OID 9817004)
-- Name: storage; Type: TABLE; Schema: vmreports; Owner: smishchuk
--
CREATE TABLE vmreports.storage (
"Name" character varying(255),
"VM_UUID" character varying(255),
"VDC" character varying(255),
"Organization_UUID" uuid,
"Organization" character varying(255),
"WZ" character varying(255),
"Cluster" character varying(255),
"Disk" character varying(255),
"Storage_Policy" character varying(255),
"Disk_Allocated" double precision,
"Disk_Used" double precision,
"VM_is_ON" double precision,
"Object_UUID" uuid NOT NULL,
ts timestamp with time zone NOT NULL
);
ALTER TABLE vmreports.storage OWNER TO dbo;
--
-- TOC entry 720 (class 1259 OID 9816984)
-- Name: host; Type: TABLE; Schema: vmreports; Owner: smishchuk
--
CREATE TABLE vmreports.host (
"Name" character varying(255),
"Model" character varying(255),
"Cluster" character varying(255),
"Maintenance" character varying(255),
"CPU_Cores" double precision,
"CPU_Sockets" double precision,
"CPU_Speed" double precision,
"RAM_Total" double precision,
"RAM_Used" double precision,
"CPU_Used" double precision,
"CPU_Used_AVG" double precision,
"CPU_Contention_95" double precision,
"Object_UUID" uuid NOT NULL,
ts timestamp with time zone NOT NULL,
"Cluster_v2" character varying(255),
"HT_Active" boolean,
"CPUtoVM" double precision,
"RAMtoVM" double precision
);
ALTER TABLE vmreports.host OWNER TO dbo;
--
-- TOC entry 733 (class 1259 OID 11093092)
-- Name: vm_gpu; Type: TABLE; Schema: vmreports; Owner: smishchuk
--
CREATE TABLE vmreports.vm_gpu (
"Name" character varying(255),
"ClientID" character varying(255),
"VCD" character varying(255),
"Org" character varying(255),
"OrgVDC" character varying(255),
"vCenter" character varying(255),
"GPU" jsonb,
"OrgStatus" character varying(255),
"Object_UUID" uuid NOT NULL,
ts timestamp with time zone NOT NULL
);
ALTER TABLE vmreports.vm_gpu OWNER TO dbo;
--
-- TOC entry 732 (class 1259 OID 11093087)
-- Name: vm_power_state; Type: TABLE; Schema: vmreports; Owner: smishchuk
--
CREATE TABLE vmreports.vm_power_state (
"Object_UUID" uuid NOT NULL,
ts timestamp with time zone NOT NULL,
value double precision
);
ALTER TABLE vmreports.vm_power_state OWNER TO dbo;
--
-- TOC entry 4761 (class 2606 OID 9816998)
-- Name: compute pk_compute; Type: CONSTRAINT; Schema: vmreports; Owner: smishchuk
--
ALTER TABLE ONLY vmreports.compute
ADD CONSTRAINT pk_compute PRIMARY KEY (ts, "Object_UUID");
--
-- TOC entry 4759 (class 2606 OID 9816990)
-- Name: host pk_host; Type: CONSTRAINT; Schema: vmreports; Owner: smishchuk
--
ALTER TABLE ONLY vmreports.host
ADD CONSTRAINT pk_host PRIMARY KEY (ts, "Object_UUID");
--
-- TOC entry 4763 (class 2606 OID 9817010)
-- Name: storage pk_storage; Type: CONSTRAINT; Schema: vmreports; Owner: smishchuk
--
ALTER TABLE ONLY vmreports.storage
ADD CONSTRAINT pk_storage PRIMARY KEY (ts, "Object_UUID");
--
-- TOC entry 4767 (class 2606 OID 11093098)
-- Name: vm_gpu pk_vm_gpu; Type: CONSTRAINT; Schema: vmreports; Owner: smishchuk
--
ALTER TABLE ONLY vmreports.vm_gpu
ADD CONSTRAINT pk_vm_gpu PRIMARY KEY (ts, "Object_UUID");
--
-- TOC entry 4765 (class 2606 OID 11093091)
-- Name: vm_power_state pk_vm_power_state; Type: CONSTRAINT; Schema: vmreports; Owner: smishchuk
--
ALTER TABLE ONLY vmreports.vm_power_state
ADD CONSTRAINT pk_vm_power_state PRIMARY KEY ("Object_UUID", ts);
--
-- TOC entry 5168 (class 0 OID 0)
-- Dependencies: 10
-- Name: SCHEMA vmreports; Type: ACL; Schema: -; Owner: dbo
--
GRANT ALL ON SCHEMA vmreports TO nifi;
GRANT ALL ON ALL TABLES IN SCHEMA vmreports TO nifi;
GRANT SELECT, REFERENCES ON ALL TABLES IN SCHEMA vmreports TO analytics, analyst;
+128
View File
@@ -0,0 +1,128 @@
CREATE SCHEMA IF NOT EXISTS vmreports;
ALTER SCHEMA vmreports OWNER TO dbo;
GRANT USAGE ON SCHEMA vmreports TO nifi;
GRANT ALL ON ALL TABLES IN SCHEMA vmreports TO nifi;
GRANT ALL ON ALL SEQUENCES IN SCHEMA vmreports TO nifi;
--ALTER DEFAULT PRIVILEGES IN SCHEMA vmreports GRANT ALL ON TABLES TO nifi;
--ALTER DEFAULT PRIVILEGES IN SCHEMA vmreports GRANT ALL ON SEQUENCES TO nifi;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo, postgres IN SCHEMA vmreports GRANT ALL ON TABLES TO nifi;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo, postgres IN SCHEMA vmreports GRANT SELECT ON TABLES TO appserver;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo, postgres IN SCHEMA vmreports GRANT ALL ON SEQUENCES TO nifi;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo, postgres IN SCHEMA vmreports GRANT USAGE ON SEQUENCES TO appserver;
drop table if exists vmreports.host;
create table vmreports.host(
"Name" varchar(255) NULL,
"Model" varchar(255) NULL,
"Cluster" varchar(255) NULL,
"Cluster_v2" varchar(255) NULL,
"Maintenance" varchar(255) NULL,
"CPU_Cores" float NULL, -- per host, not per socket
"CPU_Sockets" float NULL, -- not used in caclulations
"CPU_Speed" float NULL,
"RAM_Total" float NULL,
"RAM_Used" float NULL,
"CPU_Used" float NULL,
"CPU_Used_AVG" float NULL,
"CPU_Contention_95" float NULL,
"HT_Active" boolean NULL,
"CPUtoVM" float NULL,
"RAMtoVM" float NULL,
"Object_UUID" uuid NOT NULL,
ts timestamptz NOT NULL
,CONSTRAINT PK_host PRIMARY KEY (ts, "Object_UUID")
);
--grant all on vmreports.host to nifi;
drop table if exists vmreports.compute;
create table vmreports.compute (
"Name" varchar(255) NULL,
"UUID_VM" varchar(255) NULL,
"VDC" varchar(255) NULL,
"Organization_UUID" uuid NULL,
"Organization" varchar(255) NULL,
"WZ" varchar(255) NULL,
"Cluster" varchar(255) NULL,
"Host" varchar(255) NULL,
"Host_Model" varchar(255) NULL,
"CPU_Model" varchar(255) NULL,
"Host_CPU_Cores" float NULL,
"Host_CPU_Sockets" float NULL,
"Host_CPU_Speed" float NULL,
"Host_RAM" float NULL,
"Host_CPU_Used" float NULL,
"Host_CPU_Used_Avg" float NULL,
"VDC_CPU_Reserv" float NULL,
"VDC_CPU_Speed" float NULL,
"VM_vCPU_count" float NULL,
"VM_CPU_Used" float NULL,
"VM_CPU_Used_AVG" float NULL,
"VM_CPU_Reserv_GHz" float NULL,
"VM_CPU_Reserv_perc" float NULL,
"VM_RAM_Allocated" float NULL,
"VM_RAM_Used" float NULL,
"VM_is_ON" float NULL,
--"Summary": false,
--"Grand Total": false,
--"Group UUID": null,
"Object_UUID" uuid NOT NULL,
ts timestamptz NOT NULL
,CONSTRAINT PK_compute PRIMARY KEY (ts, "Object_UUID")
);
--grant all on vmreports.compute to nifi;
drop table if exists vmreports.storage;
create table vmreports.storage (
"Name" varchar(255) NULL,
"VM_UUID" varchar(255) NULL,
"VDC" varchar(255) NULL,
"Organization_UUID" uuid NULL,
"Organization" varchar(255) NULL,
"WZ" varchar(255) NULL,
"Cluster" varchar(255) NULL,
"Disk" varchar(255) NULL,
"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")
);
--grant all on vmreports.storage to nifi;
drop table if exists vmreports.vm_gpu;
create table vmreports.vm_gpu (
"Name" varchar(255) NULL,
"ClientID" varchar(255) NULL, --WZ
"VCD" varchar(255) NULL, --vcloud
"Org" varchar(255) NULL,
"OrgVDC" varchar(255) NULL,
"vCenter" varchar(255) NULL,
"GPU" jsonb NULL,
"OrgStatus" varchar(255) NULL,
"Object_UUID" uuid NOT NULL,
ts timestamptz NOT NULL
,CONSTRAINT PK_vm_gpu PRIMARY KEY (ts, "Object_UUID")
);
--grant all on vmreports.vm_gpu to nifi;
DROP TABLE IF EXISTS vmreports.vm_power_state;
CREATE TABLE IF NOT EXISTS vmreports.vm_power_state
(
"Object_UUID" uuid NOT NULL,
ts timestamp with time zone NOT NULL,
value float,
CONSTRAINT PK_vm_power_state PRIMARY KEY ("Object_UUID", ts)
);
--grant all on vmreports.vm_power_state to nifi;
+204
View File
@@ -0,0 +1,204 @@
--
-- PostgreSQL database dump
--
--
-- TOC entry 10 (class 2615 OID 9816969)
-- Name: vmreports; Type: SCHEMA; Schema: -; Owner: dbo
--
CREATE SCHEMA IF NOT EXISTS vmreports;
ALTER SCHEMA vmreports OWNER TO dbo;
GRANT USAGE ON SCHEMA vmreports TO nifi,appserver;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT SELECT ON TABLES TO appserver;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT ALL ON TABLES TO nifi;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT USAGE ON SEQUENCES TO nifi, appserver;
SET default_tablespace = '';
SET default_table_access_method = heap;
--
-- TOC entry 721 (class 1259 OID 9816992)
-- Name: compute; Type: TABLE; Schema: vmreports; Owner: smishchuk
--
CREATE TABLE vmreports.compute (
"Name" character varying(255),
"UUID_VM" character varying(255),
"VDC" character varying(255),
"Organization_UUID" uuid,
"Organization" character varying(255),
"WZ" character varying(255),
"Cluster" character varying(255),
"Host" character varying(255),
"Host_Model" character varying(255),
"CPU_Model" character varying(255),
"Host_CPU_Cores" double precision,
"Host_CPU_Sockets" double precision,
"Host_CPU_Speed" double precision,
"Host_RAM" double precision,
"Host_CPU_Used" double precision,
"Host_CPU_Used_Avg" double precision,
"VDC_CPU_Reserv" double precision,
"VDC_CPU_Speed" double precision,
"VM_vCPU_count" double precision,
"VM_CPU_Used" double precision,
"VM_CPU_Used_AVG" double precision,
"VM_CPU_Reserv_GHz" double precision,
"VM_CPU_Reserv_perc" double precision,
"VM_RAM_Allocated" double precision,
"VM_RAM_Used" double precision,
"VM_is_ON" double precision,
"Object_UUID" uuid NOT NULL,
ts timestamp with time zone NOT NULL
);
ALTER TABLE vmreports.compute OWNER TO dbo;
--
-- TOC entry 722 (class 1259 OID 9817004)
-- Name: storage; Type: TABLE; Schema: vmreports; Owner: smishchuk
--
CREATE TABLE vmreports.storage (
"Name" character varying(255),
"VM_UUID" character varying(255),
"VDC" character varying(255),
"Organization_UUID" uuid,
"Organization" character varying(255),
"WZ" character varying(255),
"Cluster" character varying(255),
"Disk" character varying(255),
"Storage_Policy" character varying(255),
"Disk_Allocated" double precision,
"Disk_Used" double precision,
"VM_is_ON" double precision,
"Object_UUID" uuid NOT NULL,
ts timestamp with time zone NOT NULL
);
ALTER TABLE vmreports.storage OWNER TO dbo;
--
-- TOC entry 720 (class 1259 OID 9816984)
-- Name: host; Type: TABLE; Schema: vmreports; Owner: smishchuk
--
CREATE TABLE vmreports.host (
"Name" character varying(255),
"Model" character varying(255),
"Cluster" character varying(255),
"Maintenance" character varying(255),
"CPU_Cores" double precision,
"CPU_Sockets" double precision,
"CPU_Speed" double precision,
"RAM_Total" double precision,
"RAM_Used" double precision,
"CPU_Used" double precision,
"CPU_Used_AVG" double precision,
"CPU_Contention_95" double precision,
"Object_UUID" uuid NOT NULL,
ts timestamp with time zone NOT NULL,
"Cluster_v2" character varying(255),
"HT_Active" boolean,
"CPUtoVM" double precision,
"RAMtoVM" double precision
);
ALTER TABLE vmreports.host OWNER TO dbo;
--
-- TOC entry 733 (class 1259 OID 11093092)
-- Name: vm_gpu; Type: TABLE; Schema: vmreports; Owner: smishchuk
--
CREATE TABLE vmreports.vm_gpu (
"Name" character varying(255),
"ClientID" character varying(255),
"VCD" character varying(255),
"Org" character varying(255),
"OrgVDC" character varying(255),
"vCenter" character varying(255),
"GPU" jsonb,
"OrgStatus" character varying(255),
"Object_UUID" uuid NOT NULL,
ts timestamp with time zone NOT NULL
);
ALTER TABLE vmreports.vm_gpu OWNER TO dbo;
--
-- TOC entry 732 (class 1259 OID 11093087)
-- Name: vm_power_state; Type: TABLE; Schema: vmreports; Owner: smishchuk
--
CREATE TABLE vmreports.vm_power_state (
"Object_UUID" uuid NOT NULL,
ts timestamp with time zone NOT NULL,
value double precision
);
ALTER TABLE vmreports.vm_power_state OWNER TO dbo;
--
-- TOC entry 4761 (class 2606 OID 9816998)
-- Name: compute pk_compute; Type: CONSTRAINT; Schema: vmreports; Owner: smishchuk
--
ALTER TABLE ONLY vmreports.compute
ADD CONSTRAINT pk_compute PRIMARY KEY (ts, "Object_UUID");
--
-- TOC entry 4759 (class 2606 OID 9816990)
-- Name: host pk_host; Type: CONSTRAINT; Schema: vmreports; Owner: smishchuk
--
ALTER TABLE ONLY vmreports.host
ADD CONSTRAINT pk_host PRIMARY KEY (ts, "Object_UUID");
--
-- TOC entry 4763 (class 2606 OID 9817010)
-- Name: storage pk_storage; Type: CONSTRAINT; Schema: vmreports; Owner: smishchuk
--
ALTER TABLE ONLY vmreports.storage
ADD CONSTRAINT pk_storage PRIMARY KEY (ts, "Object_UUID");
--
-- TOC entry 4767 (class 2606 OID 11093098)
-- Name: vm_gpu pk_vm_gpu; Type: CONSTRAINT; Schema: vmreports; Owner: smishchuk
--
ALTER TABLE ONLY vmreports.vm_gpu
ADD CONSTRAINT pk_vm_gpu PRIMARY KEY (ts, "Object_UUID");
--
-- TOC entry 4765 (class 2606 OID 11093091)
-- Name: vm_power_state pk_vm_power_state; Type: CONSTRAINT; Schema: vmreports; Owner: smishchuk
--
ALTER TABLE ONLY vmreports.vm_power_state
ADD CONSTRAINT pk_vm_power_state PRIMARY KEY ("Object_UUID", ts);
--
-- TOC entry 5168 (class 0 OID 0)
-- Dependencies: 10
-- Name: SCHEMA vmreports; Type: ACL; Schema: -; Owner: dbo
--
GRANT ALL ON SCHEMA vmreports TO nifi;
+101
View File
@@ -0,0 +1,101 @@
ALTER TABLE IF EXISTS vmreports.storage
OWNER to dbo;
REVOKE ALL ON TABLE vmreports.storage FROM analyst;
REVOKE ALL ON TABLE vmreports.storage FROM analytics;
REVOKE ALL ON TABLE vmreports.storage FROM appserver;
GRANT SELECT ON TABLE vmreports.storage TO analyst;
GRANT SELECT ON TABLE vmreports.storage TO analytics;
GRANT SELECT ON TABLE vmreports.storage TO appserver;
GRANT ALL ON TABLE vmreports.storage TO dbo;
GRANT ALL ON TABLE vmreports.storage TO designer;
GRANT ALL ON TABLE vmreports.storage TO nifi;
---------------
ALTER TABLE IF EXISTS vmreports.compute
OWNER to dbo;
REVOKE ALL ON TABLE vmreports.compute FROM analyst;
REVOKE ALL ON TABLE vmreports.compute FROM analytics;
REVOKE ALL ON TABLE vmreports.compute FROM appserver;
GRANT SELECT ON TABLE vmreports.compute TO analyst;
GRANT SELECT ON TABLE vmreports.compute TO analytics;
GRANT SELECT ON TABLE vmreports.compute TO appserver;
GRANT ALL ON TABLE vmreports.compute TO dbo;
GRANT ALL ON TABLE vmreports.compute TO designer;
GRANT ALL ON TABLE vmreports.compute TO nifi;
--------------------
ALTER TABLE IF EXISTS vmreports.host
OWNER to dbo;
REVOKE ALL ON TABLE vmreports.host FROM analyst;
REVOKE ALL ON TABLE vmreports.host FROM analytics;
REVOKE ALL ON TABLE vmreports.host FROM appserver;
GRANT SELECT ON TABLE vmreports.host TO analyst;
GRANT SELECT ON TABLE vmreports.host TO analytics;
GRANT SELECT ON TABLE vmreports.host TO appserver;
GRANT ALL ON TABLE vmreports.host TO dbo;
GRANT ALL ON TABLE vmreports.host TO designer;
GRANT ALL ON TABLE vmreports.host TO nifi;
------------------------------------
ALTER TABLE IF EXISTS vmreports.vm_gpu
OWNER to dbo;
REVOKE ALL ON TABLE vmreports.vm_gpu FROM analyst;
REVOKE ALL ON TABLE vmreports.vm_gpu FROM analytics;
REVOKE ALL ON TABLE vmreports.vm_gpu FROM appserver;
GRANT SELECT ON TABLE vmreports.vm_gpu TO analyst;
GRANT SELECT ON TABLE vmreports.vm_gpu TO analytics;
GRANT SELECT ON TABLE vmreports.vm_gpu TO appserver;
GRANT ALL ON TABLE vmreports.vm_gpu TO dbo;
GRANT ALL ON TABLE vmreports.vm_gpu TO designer;
GRANT ALL ON TABLE vmreports.vm_gpu TO nifi;
-----------------
ALTER TABLE IF EXISTS vmreports.vm_power_state
OWNER to dbo;
REVOKE ALL ON TABLE vmreports.vm_power_state FROM analyst;
REVOKE ALL ON TABLE vmreports.vm_power_state FROM analytics;
REVOKE ALL ON TABLE vmreports.vm_power_state FROM appserver;
GRANT SELECT ON TABLE vmreports.vm_power_state TO analyst;
GRANT SELECT ON TABLE vmreports.vm_power_state TO analytics;
GRANT SELECT ON TABLE vmreports.vm_power_state TO appserver;
GRANT ALL ON TABLE vmreports.vm_power_state TO dbo;
GRANT ALL ON TABLE vmreports.vm_power_state TO designer;
GRANT ALL ON TABLE vmreports.vm_power_state TO nifi;
+65
View File
@@ -0,0 +1,65 @@
SET ROLE dbo;
DROP SCHEMA IF EXISTS vmreports CASCADE;
CREATE SCHEMA IF NOT EXISTS vmreports AUTHORIZATION dbo;
GRANT USAGE ON SCHEMA vmreports TO appserver;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT SELECT ON TABLES TO appserver;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT USAGE ON SEQUENCES TO appserver;
GRANT USAGE ON SCHEMA vmreports TO nifi,analytics,analyst;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT SELECT ON TABLES TO nifi,analytics,analyst;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT USAGE ON SEQUENCES TO nifi,analytics,analyst;
/*
конструкция осложняется тем, что
нужен один пользователь-владелец и второй читатель
Это вызывает множество телодвижений при создании.
Необходимость указывать пароль при локальном коннекте не очень радует
(ведь пользователь локальный, прописан на этом же сервере)
*/
--DROP FOREIGN TABLE IF EXISTS host;
--CREATE FOREIGN TABLE vmreports.host () SERVER billing OPTIONS (schema_name 'vmreports', table_name 'host'); -- вместо этого лучше импортировать схему
DROP SERVER IF EXISTS billing CASCADE;
CREATE SERVER billing
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'localhost', port '5432', dbname 'billing', updatable 'false');
-- для импорта схемы мы создаем маппинг владельцу
DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER billing;
CREATE USER MAPPING FOR PUBLIC SERVER billing OPTIONS (user 'appserver', password '***');
IMPORT FOREIGN SCHEMA vmreports FROM SERVER billing INTO vmreports;
--GRANT SELECT ON vmreports.host to appserver;
--DROP USER MAPPING IF EXISTS FOR dbo SERVER billing; -- мы импортировали схему, маппинг больше не нужен.
-- Но нам все равно пришлось указать пароль дважды
--DROP USER MAPPING IF EXISTS FOR appserver SERVER billing;
--CREATE USER MAPPING FOR appserver SERVER billing OPTIONS (user 'nifi', password '***');
--проверка
--set role appserver; select * from vmreports.host;
-- это если интересно, не разрешен ли локальный коннект
--SELECT * FROM pg_hba_file_rules;
/*
Довольно тонкий нюанс, легко ускользающий
ALTER DEFAULT PRIVILEGES FOR ROLE target_creator_role
IN SCHEMA schema_name
GRANT privilege_type ON TABLES TO recipient_role;
Recommended Strategy for "Any User"Because there is no "FOR ALL ROLES" option, the best practice is to have all users create objects as a shared group role.Create a group role (e.g., schema_owner_role).Grant that role to all relevant users.Instruct users to run SET ROLE schema_owner_role; before creating objects.Run ALTER DEFAULT PRIVILEGES FOR ROLE schema_owner_role ... once to cover all objects created under that role.
*/
/*
Best Practices for Role Owners:Dedicated Schema Owner: Create a dedicated, non-login role for managing database schema objects (DDL) and make it the owner of the database/schemas.Separation of Duties: Separate the application user (DML - SELECT, INSERT, UPDATE) from the owner role (DDL - CREATE, ALTER, DROP).Use ALTER DEFAULT PRIVILEGES: Set up default privileges for new objects to ensure that user roles automatically gain access to new tables or functions created by the owner role.Leverage Role Hierarchy: Create group roles (e.g., app_reader, app_writer) and grant these to individual user roles for easier permission management.Avoid Public Schema: Create specific schemas and revoke CREATE privileges on the public schema to prevent accidental object creation.Secure SUPERUSER Usage: Only use the default postgres superuser role for initial setup and critical administrative tasks. Never use it for applications.Example Implementation:sql-- Create a schema owner
CREATE ROLE ddl_user NOLOGIN;
-- Create an app user
CREATE ROLE dml_user LOGIN PASSWORD 'secure_password';
-- Assign ownership
ALTER DATABASE mydb OWNER TO ddl_user;
ALTER SCHEMA public OWNER TO ddl_user;
-- Grant future access
ALTER DEFAULT PRIVILEGES FOR ROLE ddl_user IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO dml_user;
*/
+37
View File
@@ -0,0 +1,37 @@
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO dbo;
SET ROLE dbo;
DROP SCHEMA IF EXISTS vmreports CASCADE;
CREATE SCHEMA IF NOT EXISTS vmreports AUTHORIZATION dbo;
GRANT USAGE ON SCHEMA vmreports TO nifi,analytics,analyst;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT SELECT ON TABLES TO nifi,analytics,analyst; -- это работает и для внешних таблиц
--ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT USAGE ON SEQUENCES TO nifi,analytics,analyst;
/*
конструкция осложняется тем, что
нужен один пользователь-владелец и второй читатель
Это вызывает множество телодвижений при создании.
Необходимость указывать пароль при локальном коннекте не очень радует
(ведь пользователь локальный, прописан на этом же сервере)
*/
DROP SERVER IF EXISTS billing CASCADE;
CREATE SERVER billing
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'localhost', port '5432', dbname 'billing', updatable 'false');
DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER billing;
CREATE USER MAPPING FOR PUBLIC SERVER billing OPTIONS (user 'nifi', password '***');
IMPORT FOREIGN SCHEMA vmreports FROM SERVER billing INTO vmreports;
--select * from vmreports.host;
/*
план миграции
Забекапить схему vmreports
Восстановить схему vmreports в базу биллинг
Поменять целевую базу импорта НиФи (для прода - втащить новый обмен из репозитория)
Выполнить скрипт в базе DWH - он удалит физические таблицы и создаст внешние таблицы на базу billing
*/
+113
View File
@@ -0,0 +1,113 @@
-- выполнить в базе dwh для создания внешних таблиц схемы vmreports
-- перед выполнением забекапить схему vmreports для восстановления в БД billing
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
GRANT USAGE ON FOREIGN DATA WRAPPER postgres_fdw TO dbo;
SET ROLE dbo;
--DROP SCHEMA IF EXISTS vmreports CASCADE;
CREATE SCHEMA IF NOT EXISTS vmreports AUTHORIZATION dbo;
GRANT USAGE ON SCHEMA vmreports TO nifi,analytics,analyst;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT SELECT ON TABLES TO nifi,analytics,analyst; -- это работает и для внешних таблиц
--ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT USAGE ON SEQUENCES TO nifi,analytics,analyst;
/*
конструкция осложняется тем, что
нужен один пользователь-владелец и второй читатель
Это вызывает множество телодвижений при создании.
Необходимость указывать пароль при локальном коннекте не очень радует
(ведь пользователь локальный, прописан на этом же сервере)
А при смене его пароля все сломается, и поиск ошибки будет неочевиден
*/
DO $$
DECLARE
rec RECORD;
BEGIN
FOR rec IN
SELECT tablename
FROM pg_tables
WHERE schemaname = 'vmreports'
LOOP
EXECUTE format('ALTER TABLE vmreports.%I RENAME TO %I',
rec.tablename,
'del_' || rec.tablename);
END LOOP;
END $$;
DROP SERVER IF EXISTS billing CASCADE;
CREATE SERVER billing
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'localhost', port '5432', dbname 'billing', updatable 'false');
DROP USER MAPPING IF EXISTS FOR PUBLIC SERVER billing;
CREATE USER MAPPING FOR PUBLIC SERVER billing OPTIONS (user 'nifi', password '***');
IMPORT FOREIGN SCHEMA vmreports FROM SERVER billing INTO vmreports;
--select * from vmreports.host;
/*
план миграции - черновик
Забекапить схему vmreports
Восстановить схему vmreports из бэкапа в базу биллинг
Поменять целевую базу импорта НиФи (для прода - втащить новый обмен из репозитория)
Выполнить скрипт в базе DWH - он удалит (переименует) физические таблицы и создаст внешние таблицы на базу billing
*/
/*
Исходно данные host, compute, storage, (gpu)
загружались в dwh.vmreports,
для анализа capacity
В текущей конфигурации,
чтобы избежать дублирования
таблиц, данных и обменов NiFi
Направляем загрузку данных из VROPS в схему billing.vmreports
и в базе dwh (схема vmreports) создаем Foreign Tables на billing.vmreports.
-----------------------------------------------
Миграция на проде:
убедиться, что новый обмен VROPS->billing.vmreports (billing/vmReports) загружен в NiFi Registry
найти пароль nifi (под ним коннект к localhost)
остановить загрузку из VROPS на nifi-01 (billing/vmReports),
сделать бэкап схемы dwh.vmreports (займет минут 10 ... заняло 215 сек),
Восстановить схему vmreports в БД billing - запустить восстановление (Clean Before Restore) (РИСК: восстановление может занять много времени и завершиться с ошибками, обычно так бывает)
select count (*) from vmreports.storage
Выполнить этот скрипт (заменив пароль nifi на актуальный перед выполнением)
переименовать таблицы из схемы vmreports для удаления (есть в скрипте выше)
создать Foreign Tables (коннект проверится при импорте схемы)
Проверить, что права назначились коррректно
Обновить обмен billing/vmReports на продуктиве, запустить
-----------------------------------------------------------------
Данные из billing.vmreports также реплицируются
(процессорами группы LK)
в буферную базу rpt
*/
/*
17:04:49 MSKERROR0dff1f93-153d-3a90-ed64-55d23960b88c
EvaluateJsonPath[id=0dff1f93-153d-3a90-ed64-55d23960b88c] FlowFile FlowFile[filename=06c68bec-d824-4114-913b-bda34cb4fb1d] did not have valid JSON content.
-- со второй попытки отработало
17:05:15 MSKERROR239db407-76f2-3660-7fad-c4bedff6a4f8
InvokeHTTP[id=239db407-76f2-3660-7fad-c4bedff6a4f8] Request Processing failed: FlowFile[filename=3474c340-f360-4a1e-a27e-283be41fc264]: java.net.SocketTimeoutException: timeout
- Caused by: java.net.SocketException: Socket closed
https://payg.adl.nubes.ru/payg.cfm с первого раза таймаут, со второго посчиталось
*/
/*
теперь ищу в нифи примеры с кроном
17:25:05 MSKERROR16efa0f6-f1e7-3755-04fc-5be0383244ec
PutDatabaseRecord[id=16efa0f6-f1e7-3755-04fc-5be0383244ec] Failed to put Records to database for FlowFile[filename=11f8176f-2a98-4199-932e-22e2fc2073b1]. Routing to failure.: org.apache.nifi.serialization.MalformedRecordException: Record does not have a value for the UpdateKey column 'obj_uuid'
17:25:05 MSKERROR16efa0f6-f1e7-3755-04fc-5be0383244ec
PutDatabaseRecord[id=16efa0f6-f1e7-3755-04fc-5be0383244ec] Record does not have a value for the UpdateKey column 'obj_uuid'
17:25:05 MSKERROR16efa0f6-f1e7-3755-04fc-5be0383244ec
PutDatabaseRecord[id=16efa0f6-f1e7-3755-04fc-5be0383244ec] Failed to put Records to database for FlowFile[filename=5039a3b6-2244-4451-81fc-deb8074f3cc7]. Routing to failure.: org.apache.nifi.serialization.MalformedRecordException: Record does not have a value for the UpdateKey column 'obj_uuid'
17:25:05 MSKERROR16efa0f6-f1e7-3755-04fc-5be0383244ec
PutDatabaseRecord[id=16efa0f6-f1e7-3755-04fc-5be0383244ec] Record does not have a value for the UpdateKey column 'obj_uuid'
17:25:05 MSKERROR16efa0f6-f1e7-3755-04fc-5be0383244ec
PutDatabaseRecord[id=16efa0f6-f1e7-3755-04fc-5be0383244ec] Failed to put Records to database for FlowFile[filename=8c6e9efb-198d-4303-897a-53fcfdf4d0f6]. Routing to failure.: org.apache.nifi.serialization.MalformedRecordException: Record does not have a value for the UpdateKey column 'obj_uuid'
*/
+47
View File
@@ -0,0 +1,47 @@
SET ROLE dbo;
DROP SCHEMA IF EXISTS vmreports CASCADE;
CREATE SCHEMA IF NOT EXISTS vmreports AUTHORIZATION dbo;
--GRANT ALL ON SCHEMA vmreports TO postgres;
BEGIN
GRANT USAGE ON SCHEMA vmreports TO appserver;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT SELECT ON TABLES TO appserver;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT USAGE ON SEQUENCES TO appserver;
EXCEPTION END;
BEGIN
GRANT USAGE ON SCHEMA vmreports TO nifi,analytics,analyst;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT SELECT ON TABLES TO nifi,analytics,analyst;
ALTER DEFAULT PRIVILEGES FOR ROLE dbo IN SCHEMA vmreports GRANT USAGE ON SEQUENCES TO nifi,analytics,analyst;
EXCEPTION WHEN others THEN END;
/*
конструкция осложняется тем, что
нужен один пользователь-владелец и второй читатель
Это вызывает множество телодвижений при создании.
Необходимость указывать пароль при локальном коннекте не очень радует
(ведь пользователь локальный, прописан на этом же сервере)
*/
--DROP FOREIGN TABLE IF EXISTS host;
--CREATE FOREIGN TABLE vmreports.host () SERVER billing OPTIONS (schema_name 'vmreports', table_name 'host'); -- вместо этого лучше импортировать схему
DROP SERVER IF EXISTS billing CASCADE;
CREATE SERVER billing
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'localhost', port '5432', dbname 'billing');
-- для импорта схемы мы создаем маппинг владельцу
DROP USER MAPPING IF EXISTS FOR dbo SERVER billing;
CREATE USER MAPPING FOR dbo SERVER billing OPTIONS (user 'appserver', password '***');
IMPORT FOREIGN SCHEMA vmreports FROM SERVER billing INTO vmreports;
GRANT SELECT ON vmreports.host to appserver;
DROP USER MAPPING IF EXISTS FOR dbo SERVER billing; -- мы импортировали схему, маппинг больше не нужен.
-- Но нам все равно пришлось указать пароль дважды
DROP USER MAPPING IF EXISTS FOR appserver SERVER billing;
CREATE USER MAPPING FOR appserver SERVER billing OPTIONS (user 'appserver', password '***');
--проверка
--set role appserver; select * from vmreports.host;
-- это если интересно, не разрешен ли локальный коннект
--SELECT * FROM pg_hba_file_rules;
+52
View File
@@ -0,0 +1,52 @@
SET ROLE dbo;
DROP TABLE IF EXISTS vmreports.vsan_storage CASCADE;
CREATE TABLE vmreports.vsan_storage (
"Name" varchar(255) NULL,
"ClusterNew" varchar(255) NULL,
"Total" float NULL,
"Usable" float NULL,
"Used" float NULL,
"EffectiveFree" float NULL,
"Object_UUID" uuid NOT NULL,
ts timestamptz NOT NULL
,CONSTRAINT PK_vsan_storage PRIMARY KEY (ts, "Object_UUID")
);
--ALTER TABLE vmreports.vsan_storage OWNER TO dbo;
GRANT ALL ON vmreports.vsan_storage TO nifi;
--select * from vmreports.vsan_storage;
/*
{
"type": "record",
"name": "vmreports.vsan_storage",
"namespace": "org.apache.nifi",
"fields": [
{"name":"Object_UUID", "type":{"type": "string", "logicalType": "uuid"}},
{"name":"ts","type":{"type": "long", "logicalType": "local-timestamp-millis"}},
{"name":"Name", "type":["string" ,"null"]},
{"name":"ClusterNew", "type":["string" ,"null"]},
{"name":"Total", "type":["float" ,"null"]},
{"name":"Usable", "type":["float" ,"null"]},
{"name":"Used", "type":["float" ,"null"]},
{"name":"EffectiveFree", "type":["float" ,"null"]}
]
}
*/
/*
{
"*": {
"objId":"[&1].Name",
"1":"[&1].ClusterNew",
"2":"[&1].Total",
"3":"[&1].Usable",
"4":"[&1].Used",
"5":"[&1].EffectiveFree",
"objUUID":"[&1].Object_UUID"
}
}
*/
+633
View File
@@ -0,0 +1,633 @@
{
"companyId": "1",
"contracts": [
{
"contractId": "01983721-6049-72dd-a682-a5af1bd72420",
"contractInsideId": "02319_1",
"contractOutsideId": "02319_1",
"contractStartDate": "20250801",
"isContractTerminated": false,
"CalculationPeriod": "20260701",
"additionalAgreements": [
{
"additionalAgreementId": "019b6ec1-1984-78fc-9e48-0984aa1f3ca7",
"additionalAgreementIndex": 3,
"additionalAgreementName": "Доп соглашение 3, к сделке №3768 Сделка на изменения договора",
"additionalAgreementStartDate": "",
"additionalAgreementBaseBy": "019a5ef8-eebe-76b3-acb9-b5c37748f4ef",
"isActual": true,
"CalculationPeriod": "20260701",
"services": [
{
"components": [
{
"paramId": 118,
"componentCode": "paas.s3.ceph.vol",
"component": "Дисковое пространство: Объем хранения",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.8300000000000001,
"discount": 0,
"atomicCode": "paas.s3.ceph.vol"
},
{
"paramId": 69,
"componentCode": "paas.s3.ceph.trf",
"component": "Исходящий трафик",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.5249999999999999,
"discount": 0,
"atomicCode": "paas.s3.ceph.trf"
},
{
"paramId": 68,
"componentCode": "paas.s3.ceph.get",
"component": "Запросы GET, HEAD, OPTIONS",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 0.3538,
"discount": 0,
"atomicCode": "paas.s3.ceph.get"
},
{
"paramId": 70,
"componentCode": "paas.s3.ceph.put",
"component": "Запросы PUT, POST, PATCH, LIST",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 4.5750000000000002,
"discount": 0,
"atomicCode": "paas.s3.ceph.put"
}
],
"staticId": "e57bb0bd-8946-4603-95e8-c9c331378bce",
"serviceId": "2e1681d7-542e-489c-9169-de562bdca83a",
"serviceHash": "e57bb0bd-8946-4603-95e8-c9c331378bce3",
"LineCRMID": "019b6ec6-a779-7902-abeb-d2f7e746d64e",
"serviceCode": "paas.s3.ceph",
"endCustomerName": "",
"endCustomerINN": "",
"catalogName": "Объектное хранилище",
"discount": 0.24125452,
"quantity": 1,
"dateStartProvision": "20260101",
"dateEndProvision": "",
"serviceStart": {
"currentTimeZoneName": "UTC",
"ts": "2026-01-01T00:00:00.000Z"
},
"serviceCustomName": "Объектное хранилище: S3 CEPH, в составе:\n Дисковое пространство: Объем хранения - 1 ГБайт\nИсходящий трафик - 1 ГБайт\nЗапросы GET, HEAD, OPTIONS - 1 10тыс.шт.\nЗапросы PUT, POST, PATCH, LIST - 1 10тыс.шт.\n",
"serviceType": 3,
"service_is_easy": false,
"vat": 22,
"vatRate": 0.22,
"vatPerc": 22,
"vatFree": 0,
"priceWVat": "",
"serviceVersionGuid": "2e1681d7-542e-489c-9169-de562bdca83a",
"dealIndex": 3768,
"CalculationPeriod": "20260701"
},
{
"components": [
{
"paramId": 118,
"componentCode": "paas.s3.cphc.vol",
"component": "Дисковое пространство: Объем хранения",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.026833333333,
"discount": 0,
"atomicCode": "paas.s3.cphc.vol"
},
{
"paramId": 69,
"componentCode": "paas.s3.cphc.trf",
"component": "Исходящий трафик",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.5249999999999999,
"discount": 0,
"atomicCode": "paas.s3.cphc.trf"
},
{
"paramId": 68,
"componentCode": "paas.s3.cphc.get",
"component": "Запросы GET, HEAD, OPTIONS",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 0.97599999999999998,
"discount": 0,
"atomicCode": "paas.s3.cphc.get"
},
{
"paramId": 70,
"componentCode": "paas.s3.cphc.put",
"component": "Запросы PUT, POST, PATCH, LIST",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 11.834,
"discount": 0,
"atomicCode": "paas.s3.cphc.put"
}
],
"staticId": "2031f3ac-e921-4816-b91f-9b5b1cf5bf94",
"serviceId": "5271dc39-2ec5-4f20-b91e-fdffb4f363b9",
"serviceHash": "2031f3ac-e921-4816-b91f-9b5b1cf5bf943",
"LineCRMID": "019b6ec6-a82c-7902-93a3-48ba4e25d71d",
"serviceCode": "paas.s3.cphc",
"endCustomerName": "",
"endCustomerINN": "",
"catalogName": "Объектное хранилище",
"discount": 0,
"quantity": 1,
"dateStartProvision": "20260101",
"dateEndProvision": "",
"serviceStart": {
"currentTimeZoneName": "UTC",
"ts": "2026-01-01T00:00:00.000Z"
},
"serviceCustomName": "Объектное хранилище: S3 CEPH Холодное, в составе:\n Дисковое пространство: Объем хранения - 1 ГБайт\nИсходящий трафик - 1 ГБайт\nЗапросы GET, HEAD, OPTIONS - 1 10тыс.шт.\nЗапросы PUT, POST, PATCH, LIST - 1 10тыс.шт.\n",
"serviceType": 3,
"service_is_easy": false,
"vat": 22,
"vatRate": 0.22,
"vatPerc": 22,
"vatFree": 0,
"priceWVat": "",
"serviceVersionGuid": "5271dc39-2ec5-4f20-b91e-fdffb4f363b9",
"dealIndex": 3768,
"CalculationPeriod": "20260701"
},
{
"components": [
{
"paramId": 175,
"componentCode": "paas.s3.cphc.volq",
"component": "Дисковое пространство: Квота",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 2.7999999999999998,
"discount": 0,
"atomicCode": "paas.s3.cphc.volq"
}
],
"staticId": "f216f0ac-6c61-42aa-a454-56a93c707d11",
"serviceId": "776a3490-cb3a-4721-b521-7bf97073e104",
"serviceHash": "f216f0ac-6c61-42aa-a454-56a93c707d112",
"LineCRMID": "019b6ec6-a86d-7902-9ba2-defe46f2a036",
"serviceCode": "paas.s3.cphc",
"endCustomerName": "",
"endCustomerINN": "",
"catalogName": "Объектное хранилище",
"discount": 63.928570999999998,
"quantity": 1000,
"dateStartProvision": "20250826",
"dateEndProvision": "20250930",
"serviceStart": {
"currentTimeZoneName": "UTC",
"ts": "2025-08-26T00:00:00.000Z"
},
"serviceEnd": {
"currentTimeZoneName": "UTC",
"ts": "2025-09-30T00:00:00.000Z"
},
"serviceCustomName": "Объектное хранилище: S3 CEPH Холодное (Квота), в составе:\n Дисковое пространство: Квота - 1 ГБайт\n",
"serviceType": 2,
"service_is_easy": false,
"vat": 20,
"vatRate": 0.20000000000000001,
"vatPerc": 20,
"vatFree": 0,
"priceWVat": "",
"serviceVersionGuid": "776a3490-cb3a-4721-b521-7bf97073e104",
"dealIndex": 3768,
"CalculationPeriod": "20260701"
},
{
"components": [
{
"paramId": 175,
"componentCode": "paas.s3.cphc.volq",
"component": "Дисковое пространство: Квота",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 2.7999999999999998,
"discount": 0,
"atomicCode": "paas.s3.cphc.volq"
}
],
"staticId": "78707549-a675-46e0-bc5b-790ef4d21b6d",
"serviceId": "2e9369bc-7f3e-4c31-bb72-3d19e979964e",
"serviceHash": "78707549-a675-46e0-bc5b-790ef4d21b6d2",
"LineCRMID": "019b6ec6-a8ab-7902-af73-1034bf013a16",
"serviceCode": "paas.s3.cphc",
"endCustomerName": "",
"endCustomerINN": "",
"catalogName": "Объектное хранилище",
"discount": 63.928570999999998,
"quantity": 40000,
"dateStartProvision": "20250826",
"dateEndProvision": "20250930",
"serviceStart": {
"currentTimeZoneName": "UTC",
"ts": "2025-08-26T00:00:00.000Z"
},
"serviceEnd": {
"currentTimeZoneName": "UTC",
"ts": "2025-09-30T00:00:00.000Z"
},
"serviceCustomName": "Объектное хранилище: S3 CEPH Холодное (Квота), в составе:\n Дисковое пространство: Квота - 1 ГБайт\n",
"serviceType": 2,
"service_is_easy": false,
"vat": 20,
"vatRate": 0.20000000000000001,
"vatPerc": 20,
"vatFree": 0,
"priceWVat": "",
"serviceVersionGuid": "2e9369bc-7f3e-4c31-bb72-3d19e979964e",
"dealIndex": 3768,
"CalculationPeriod": "20260701"
},
{
"components": [
{
"paramId": 118,
"componentCode": "paas.s3.cphc.vol",
"component": "Дисковое пространство: Объем хранения",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.01,
"discount": 0,
"atomicCode": "paas.s3.cphc.vol"
},
{
"paramId": 69,
"componentCode": "paas.s3.cphc.trf",
"component": "Исходящий трафик",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.5,
"discount": 0,
"atomicCode": "paas.s3.cphc.trf"
},
{
"paramId": 68,
"componentCode": "paas.s3.cphc.get",
"component": "Запросы GET, HEAD, OPTIONS",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 0.95999999999999996,
"discount": 0,
"atomicCode": "paas.s3.cphc.get"
},
{
"paramId": 70,
"componentCode": "paas.s3.cphc.put",
"component": "Запросы PUT, POST, PATCH",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 11.640000000000001,
"discount": 0,
"atomicCode": "paas.s3.cphc.put"
}
],
"staticId": "20240911T125603868",
"serviceId": "a564eda1-b716-42ca-9ae7-efc49b112a58",
"serviceHash": "20240911T1256038683",
"LineCRMID": "019b6ec6-a949-7902-b473-8741f2a18efa",
"serviceCode": "paas.s3.cphc",
"endCustomerName": "",
"endCustomerINN": "",
"catalogName": "Объектное хранилище",
"discount": 0,
"quantity": 1,
"dateStartProvision": "20250826",
"dateEndProvision": "20250930",
"serviceStart": {
"currentTimeZoneName": "UTC",
"ts": "2025-08-26T00:00:00.000Z"
},
"serviceEnd": {
"currentTimeZoneName": "UTC",
"ts": "2025-09-30T00:00:00.000Z"
},
"serviceCustomName": "Объектное хранилище: S3 CEPH Холодное, в составе:\n Дисковое пространство: Объем хранения - 1 ГБайт\nИсходящий трафик - 1 ГБайт\nЗапросы GET, HEAD, OPTIONS - 1 10тыс.шт.\nЗапросы PUT, POST, PATCH - 1 10тыс.шт.\n",
"serviceType": 3,
"service_is_easy": false,
"vat": 20,
"vatRate": 0.20000000000000001,
"vatPerc": 20,
"vatFree": 0,
"priceWVat": "",
"serviceVersionGuid": "a564eda1-b716-42ca-9ae7-efc49b112a58",
"dealIndex": 3768,
"CalculationPeriod": "20260701"
},
{
"components": [
{
"paramId": 118,
"componentCode": "paas.s3.ceph.vol",
"component": "Дисковое пространство: Объем хранения",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.8,
"discount": 0,
"atomicCode": "paas.s3.ceph.vol"
},
{
"paramId": 69,
"componentCode": "paas.s3.ceph.trf",
"component": "Исходящий трафик",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.5,
"discount": 0,
"atomicCode": "paas.s3.ceph.trf"
},
{
"paramId": 68,
"componentCode": "paas.s3.ceph.get",
"component": "Запросы GET, HEAD, OPTIONS",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 0.34999999999999998,
"discount": 0,
"atomicCode": "paas.s3.ceph.get"
},
{
"paramId": 70,
"componentCode": "paas.s3.ceph.put",
"component": "Запросы PUT, POST, PATCH",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 4.5,
"discount": 0,
"atomicCode": "paas.s3.ceph.put"
}
],
"staticId": "20241015T124353835",
"serviceId": "5371b7cb-6f6c-4c7d-85b4-609eb52f994a",
"serviceHash": "20241015T1243538353",
"LineCRMID": "019b6ec6-aa03-7902-9c40-06f6d5cf6e13",
"serviceCode": "paas.s3.ceph",
"endCustomerName": "",
"endCustomerINN": "",
"catalogName": "Объектное хранилище",
"discount": 0,
"quantity": 1,
"dateStartProvision": "20250801",
"dateEndProvision": "20250930",
"serviceStart": {
"currentTimeZoneName": "UTC",
"ts": "2025-08-01T00:00:00.000Z"
},
"serviceEnd": {
"currentTimeZoneName": "UTC",
"ts": "2025-09-30T00:00:00.000Z"
},
"serviceCustomName": "Объектное хранилище: S3 CEPH, в составе:\n Дисковое пространство: Объем хранения - 1 ГБайт\nИсходящий трафик - 1 ГБайт\nЗапросы GET, HEAD, OPTIONS - 1 10тыс.шт.\nЗапросы PUT, POST, PATCH - 1 10тыс.шт.\n",
"serviceType": 3,
"service_is_easy": false,
"vat": 20,
"vatRate": 0.20000000000000001,
"vatPerc": 20,
"vatFree": 0,
"priceWVat": "",
"serviceVersionGuid": "5371b7cb-6f6c-4c7d-85b4-609eb52f994a",
"dealIndex": 3768,
"CalculationPeriod": "20260701"
},
{
"components": [
{
"paramId": 69,
"componentCode": "paas.s3.ceph.trf",
"component": "Исходящий трафик",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.5,
"discount": 0,
"atomicCode": "paas.s3.ceph.trf"
},
{
"paramId": 70,
"componentCode": "paas.s3.ceph.put",
"component": "Запросы PUT, POST, PATCH, LIST",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 4.5,
"discount": 0,
"atomicCode": "paas.s3.ceph.put"
},
{
"paramId": 118,
"componentCode": "paas.s3.ceph.vol",
"component": "Дисковое пространство: Объем хранения",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.8,
"discount": 0,
"atomicCode": "paas.s3.ceph.vol"
},
{
"paramId": 68,
"componentCode": "paas.s3.ceph.get",
"component": "Запросы GET, HEAD, OPTIONS",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 0.34999999999999998,
"discount": 0,
"atomicCode": "paas.s3.ceph.get"
}
],
"staticId": "e57bb0bd-8946-4603-95e8-c9c331378bce",
"serviceId": "09fd617a-9ffc-4b98-ad66-3fedac52c7b9",
"serviceHash": "e57bb0bd-8946-4603-95e8-c9c331378bce3",
"LineCRMID": "019a732b-22a2-7545-91ce-819207c779a0",
"catalogName": "Объектное хранилище",
"endCustomerName": "",
"endCustomerINN": "",
"serviceCode": "paas.s3.ceph",
"discount": 0,
"quantity": 1,
"dateStartProvision": "20251001",
"CalculationPeriod": "20260701",
"dateEndProvision": "20251231",
"serviceStart": {
"currentTimeZoneName": "UTC",
"ts": "2025-10-01T00:00:00.000Z"
},
"serviceEnd": {
"currentTimeZoneName": "UTC",
"ts": "2025-12-31T00:00:00.000Z"
},
"serviceCustomName": "Объектное хранилище: S3 CEPH, в составе:\n Исходящий трафик - 1 ГБайт\nЗапросы PUT, POST, PATCH, LIST - 1 10тыс.шт.\nДисковое пространство: Объем хранения - 1 ГБайт\nЗапросы GET, HEAD, OPTIONS - 1 10тыс.шт.\n",
"serviceType": 3,
"service_is_easy": false,
"vat": 20,
"vatRate": 0.20000000000000001,
"vatPerc": 20,
"vatFree": 0,
"priceWVat": "",
"serviceVersionGuid": "09fd617a-9ffc-4b98-ad66-3fedac52c7b9",
"dealIndex": 3274
},
{
"components": [
{
"paramId": 118,
"componentCode": "paas.s3.cphc.vol",
"component": "Дисковое пространство: Объем хранения",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.01,
"discount": 0,
"atomicCode": "paas.s3.cphc.vol"
},
{
"paramId": 69,
"componentCode": "paas.s3.cphc.trf",
"component": "Исходящий трафик",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.5,
"discount": 0,
"atomicCode": "paas.s3.cphc.trf"
},
{
"paramId": 68,
"componentCode": "paas.s3.cphc.get",
"component": "Запросы GET, HEAD, OPTIONS",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 0.95999999999999996,
"discount": 0,
"atomicCode": "paas.s3.cphc.get"
},
{
"paramId": 70,
"componentCode": "paas.s3.cphc.put",
"component": "Запросы PUT, POST, PATCH, LIST",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 11.640000000000001,
"discount": 0,
"atomicCode": "paas.s3.cphc.put"
}
],
"staticId": "2031f3ac-e921-4816-b91f-9b5b1cf5bf94",
"serviceId": "539d210f-2e5a-45e2-b148-b0b0834c138c",
"serviceHash": "2031f3ac-e921-4816-b91f-9b5b1cf5bf943",
"LineCRMID": "019a732b-22d4-7545-83af-18b7f8867eaa",
"catalogName": "Объектное хранилище",
"endCustomerName": "",
"endCustomerINN": "",
"serviceCode": "paas.s3.cphc",
"discount": 0,
"quantity": 1,
"dateStartProvision": "20251001",
"CalculationPeriod": "20260701",
"dateEndProvision": "20251231",
"serviceStart": {
"currentTimeZoneName": "UTC",
"ts": "2025-10-01T00:00:00.000Z"
},
"serviceEnd": {
"currentTimeZoneName": "UTC",
"ts": "2025-12-31T00:00:00.000Z"
},
"serviceCustomName": "Объектное хранилище: S3 CEPH Холодное, в составе:\n Дисковое пространство: Объем хранения - 1 ГБайт\nИсходящий трафик - 1 ГБайт\nЗапросы GET, HEAD, OPTIONS - 1 10тыс.шт.\nЗапросы PUT, POST, PATCH, LIST - 1 10тыс.шт.\n",
"serviceType": 3,
"service_is_easy": false,
"vat": 20,
"vatRate": 0.20000000000000001,
"vatPerc": 20,
"vatFree": 0,
"priceWVat": "",
"serviceVersionGuid": "539d210f-2e5a-45e2-b148-b0b0834c138c",
"dealIndex": 3274
},
{
"components": [
{
"paramId": 118,
"componentCode": "paas.s3.cphc.vol",
"component": "Дисковое пространство: Объем хранения",
"measureShort": "ГБайт",
"quantity": 40000,
"priceWVat": 1.01,
"discount": 0,
"atomicCode": "paas.s3.cphc.vol"
},
{
"paramId": 68,
"componentCode": "paas.s3.cphc.get",
"component": "Запросы GET, HEAD, OPTIONS",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 0.95999999999999996,
"discount": 0,
"atomicCode": "paas.s3.cphc.get"
},
{
"paramId": 69,
"componentCode": "paas.s3.cphc.trf",
"component": "Исходящий трафик",
"measureShort": "ГБайт",
"quantity": 1,
"priceWVat": 1.5,
"discount": 0,
"atomicCode": "paas.s3.cphc.trf"
},
{
"paramId": 70,
"componentCode": "paas.s3.cphc.put",
"component": "Запросы PUT, POST, PATCH",
"measureShort": "10тыс.шт.",
"quantity": 1,
"priceWVat": 11.640000000000001,
"discount": 0,
"atomicCode": "paas.s3.cphc.put"
}
],
"staticId": "20240911T125603868",
"serviceId": "67dffa20-a69e-449a-b02c-edf78d9ff192",
"serviceHash": "20240911T1256038683",
"LineCRMID": "01983726-3501-7496-b7f3-6f58f4d7f209",
"catalogName": "Объектное хранилище",
"endCustomerName": "",
"endCustomerINN": "",
"serviceCode": "paas.s3.cphc",
"discount": 0,
"quantity": 1,
"dateStartProvision": "20250801",
"CalculationPeriod": "20260701",
"dateEndProvision": "20250825",
"serviceStart": {
"currentTimeZoneName": "UTC",
"ts": "2025-08-01T00:00:00.000Z"
},
"serviceEnd": {
"currentTimeZoneName": "UTC",
"ts": "2025-08-25T00:00:00.000Z"
},
"serviceCustomName": "Объектное хранилище: S3 CEPH Холодное, в составе:\n Дисковое пространство: Объем хранения - 40000 ГБайт\nЗапросы GET, HEAD, OPTIONS - 1 10тыс.шт.\nИсходящий трафик - 1 ГБайт\nЗапросы PUT, POST, PATCH - 1 10тыс.шт.\n",
"serviceType": 3,
"service_is_easy": false,
"vat": 20,
"vatRate": 0.20000000000000001,
"vatPerc": 20,
"vatFree": 0,
"priceWVat": "",
"serviceVersionGuid": "67dffa20-a69e-449a-b02c-edf78d9ff192",
"dealIndex": 1450
}
]
}
]
}
]
}
+116 -68
View File
@@ -1,25 +1,33 @@
<cfset msStartAt=getTickCount()/> <cfset msStartAt=getTickCount()/>
<cfparam name="dt_finish" type="date" default=#createDateTime(year(Now()),month(Now()),1,0,0,0)#/> <cfparam name="dt_finish" type="date" default=#dateAdd('d',-1,createDateTime(year(Now()),month(Now()),1,0,0,0))#/>
<cfparam name="dt_start" type="date" default=#dateAdd('m',-1,dt_finish)#/> <cfparam name="dt_start" type="date" default=#dateAdd('m',-1,createDateTime(year(Now()),month(Now()),1,0,0,0))#/>
<cfset hours=dateDiff('h',dt_start,dt_finish)/> <cfset hours=dateDiff('h', dt_start, dateAdd('d', 1, dt_finish))/>
<cfset tenant_wz_index=val(mid(request.auth.wz,3,5))/><!--- cut off WZ ---> <cfset tenant_wz_index=val(mid(request.auth.wz,3,5))/><!--- cut off WZ --->
<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" <cfscript>
returntype="string" function gaussianRound(numeric number, numeric precision = 0) {
output="false" var factor = 10 ^ precision;
hint="convert camel style name to snake style name"> var scaledNumber = number * factor;
<cfargument name="camel" type="string" required="true" /> var absScaled = abs(scaledNumber);
<cfreturn #reReplace(ARGUMENTS.camel,"([A-Z])","_\l\1","ALL")#/> var sign = sgn(scaledNumber);
</cffunction> var floored = int(absScaled);
// If it's not ending in exactly .5, use standard rounding or precision scaling
if (absScaled - floored != 0.5) {
return round(number * factor) / factor;
}
// If it is exactly .5, round towards the nearest even number
if (floored % 2 != 0) {
// Nearest even is UP
return (floored + 1) * sign / factor;
} else {
// Nearest even is DOWN
return floored * sign / factor;
}
}
</cfscript>
<cffunction name="iso8601dtFormat" <cffunction name="iso8601dtFormat"
returntype="string" returntype="string"
@@ -30,65 +38,105 @@
</cffunction> </cffunction>
<cffunction name="query2json" <cfmodule template="mod/compute_payg.cfm"
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>
<cfmodule template="mod/payg.cfm"
dt_finish=#dt_finish# dt_finish=#dt_finish#
dt_start=#dt_start# dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index# tenant_wz_index=#tenant_wz_index#
output="report" output="report"
debug=#isDefined("DEBUG")# debug=#isDefined("DEBUG")#
/> />
<cfset out={ <!--- "dtStart"="#dateFormat(dt_start, "yyyy-mm-dd")#"
"appVersion"=#request.APP_VERSION#, ,"dtFinish"="#dateFormat(dt_finish, "yyyy-mm-dd")#" --->
"hours"=#hours#, <cfset temp.out=[
"dtStart="=#iso8601dtFormat(dt_start)#, "dtStart="=#iso8601dtFormat(dt_start)#
"dtFinish"=#iso8601dtFormat(dt_finish)#, ,"dtFinish"=#iso8601dtFormat(dt_finish)#
"vcdComputingLoadedAt"="#iso8601dtFormat(report.qComputingAge.dt_load)#", ,"hours"=#hours#
"vcdStorageLoadedAt"="#iso8601dtFormat(report.qStorageAge.dt_load)#", ,"tenantWzIndex"="#tenant_wz_index#"
"s3StorageLoadedAt"="#iso8601dtFormat(report.qComputingAge.dt_load)#", ,"wz"=#request.auth.wz#
"s3OpsTrfLoadedAt"="#iso8601dtFormat(report.qS3OpsTrfAge.dt_load)#", ,"login"=#request.auth.login#
"records"=#report.qCharge.recordCount#, ,"appVersion"=#request.APP_VERSION#
"queryDurationMs"=#(getTickCount() - msStartAt)#, ,"vcdComputingLoadedAt"="#iso8601dtFormat(report.qComputingAge.dt_load)#"
"wz"=#request.auth.wz#, ,"vcdStorageLoadedAt"="#iso8601dtFormat(report.qStorageAge.dt_load)#"
"login"=#request.auth.login#, ,"s3StorageLoadedAt"="#iso8601dtFormat(report.qComputingAge.dt_load)#"
"report"=#report.qCharge.reduce((result, row) => { ,"s3OpsTrfLoadedAt"="#iso8601dtFormat(report.qS3OpsTrfAge.dt_load)#"
result.append(row); ,"records"=#report.qCharge.recordCount#
return result; ,"queryDurationMs"=#(getTickCount() - msStartAt)#
}, [])# ,"periods"=[]
}/> ]/><!--- using [] instead of {} for struct literals to obtain linked (ordered) structures: keys order preserved --->
<!--- beware nulls --->
<cfoutput query="report.qCharge" group="year">
<cfoutput group="month">
<cfset temp.period=[
"year"=#year#
,"month"=#month#
,"services"=[]
]/>
<cfset arrayAppend(temp.out.periods,temp.period)/>
<cfoutput group="svc">
<cfset var accum_cost_svc = 0/>
<!--- <cfset var accum_metric_component = 0/> --->
<cfset "temp.service"=["svc"="#svc#"
,"serviceCode"="#service_code#"
,"serviceCustomName"="#serviceCustomName#"
<!--- ,"note"="#(serviceHash EQ '')?'не найдено в договоре':''#" --->
,"hash"="#serviceHash#"
<!--- ,"metric"=#accum_metric_component# --->
,"components"=[]
]/>
<cfoutput group="code">
<cfoutput group="serviceStart">
<!--- <cfset accum_metric_component += isNumeric(metric) ? metric : 0/> --->
<cfset accum_metric_dimension_1 = 0/>
<cfset "temp.component"=["code"="#code#"
,"codeMetered"="#m_code#"
,"component"="#component#"
,"unit"="#unit#"
,"freeTier"="#free_tier#"
,"price"="#gaussianRound(discounted_price,2)#"
,"details"=[]
]/>
<cfoutput group="dimension_1">
<cfset accum_metric_dimension_1 += isNumeric(metric) ? metric : 0/>
<cfset temp.dimension=["dimension1name"="#dimension_1_name#"
,"dimension1value"="#dimension_1#"
,"dtBilledFrom"="#dateFormat(dt_billed_from,'YYYY-MM-DD')#"
,"dtBilledTo"="#dateFormat(dt_billed_to,'YYYY-MM-DD')#"
,"metric"=#(metric NEQ 0 AND isNumeric(metric))?round(metric,3):javaCast("null",0)#
,"unit"="#unit#"
<!--- ,"price"="#gaussianRound(discounted_price,2)#" --->
]/>
<cfset arrayAppend(temp.component.details, temp.dimension)/>
</cfoutput><!--- dimension_1 --->
<!--- считаем, что free_tier NOT NULL (вместо NULL 0) --->
<cfset var payable_metric_dimension_1 = (isNumeric(accum_metric_dimension_1))? max(ceiling(accum_metric_dimension_1) - free_tier, 0):javaCast("null",0)/>
<cfset var cost_dimension_1 = precisionEvaluate("gaussianRound(payable_metric_dimension_1*gaussianRound(discounted_price, 2), 2)")/>
<cfset accum_cost_svc = precisionEvaluate("accum_cost_svc + cost_dimension_1")/>
<cfset "temp.component.metric"=#(accum_metric_dimension_1 NEQ 0 AND isNumeric(accum_metric_dimension_1))? round(accum_metric_dimension_1,3):javaCast("null",0)#/>
<cfset "temp.component.metricPayable"=#payable_metric_dimension_1#/>
<cfset "temp.component.cost"=#cost_dimension_1#/>
<cfset arrayAppend(temp.service.components, temp.component)/>
</cfoutput><!--- serviceStart (component version in specification, that is svc version joined down to the component) --->
<cfset "temp.service.cost"=#accum_cost_svc#/>
</cfoutput><!--- code (component) --->
<cfset arrayAppend(temp.period.services, temp.service)/>
</cfoutput><!--- svc --->
</cfoutput><!--- month --->
</cfoutput><!--- year --->
<cfset "temp.out.durationMs"=(getTickCount()-request.startTickCount)/>
<cfcontent <cfcontent
type="application/json" type="application/json"
/><cfoutput>#serializeJSON(out)#</cfoutput> /><cfoutput>#serializeJSON(temp.out)#</cfoutput>
+242 -104
View File
@@ -6,11 +6,16 @@
<cfparam name="ATTRIBUTES.output" type="string" default="report"/> <cfparam name="ATTRIBUTES.output" type="string" default="report"/>
<cfparam name="ATTRIBUTES.debug" type="boolean" default=false/> <cfparam name="ATTRIBUTES.debug" type="boolean" default=false/>
<cfparam name="ATTRIBUTES.tenant_wz_index" type="string"/> <cfparam name="ATTRIBUTES.tenant_wz_index" type="string"/>
<cfparam name="ATTRIBUTES.restrict_to_own_instances" type="boolean" default=true/>
<cfset hours=dateDiff('h', ATTRIBUTES.dt_start, ATTRIBUTES.dt_finish)/> <!--- *** нужно для каждого фрагмента --->
<cfset hours=dateDiff('h', ATTRIBUTES.dt_start, dateAdd("d",1,ATTRIBUTES.dt_finish))/> <!--- *** нужно для каждого фрагмента --->
<cfif hours LE 0>
<cfthrow message="Некорректный период" detail="Период должен быть больше 0"/>
</cfif>
<!--- <!---
*** ВНИМАНИЕ! Вычитать минимальный лимит нужно после агрегации! *** ВНИМАНИЕ! Вычитать минимальный бесплатный лимит (free tier) нужно после агрегации!
Исходная последовательность расчетов сначала делает агрегацию по месяцу, при расчете по дням нужно менять порядок Исходная последовательность расчетов сначала делает агрегацию по месяцу, при расчете по дням нужно менять порядок
---> --->
<!--- <!---
@@ -21,13 +26,12 @@
<!--- *** Проверить, что у нас не биллится закрытый период (закрытый следующим) - возможно, с учетом line_key <!--- *** Проверить, что у нас не биллится закрытый период (закрытый следующим) - возможно, с учетом line_key
- насколько вижу, прошлый период закрывается, причем концы отрезки закрытые (конец включается). Считаем, что этой проблемы нет - насколько вижу, прошлый период закрывается, причем концы отрезки закрытые (конец включается). Считаем, что этой проблемы нет
---> --->
<!--- QoQ does not support join syntax, and of course outer joins, at least of Lucee 5.4---> <!--- QoQ does not support join syntax, and of course outer joins, at least of Lucee 5.4--->
<!--- а внешние джойны хорошо подсвечивают ошибки, особенно помогает full ---> <!--- а внешние джойны хорошо подсвечивают ошибки в данных, особенно помогает full --->
<cfmodule template="crm_contract.cfm" wz="WZ01348" out="qSpecification"/><!--- WZ01200 ---> <cfmodule template="crm_contract.cfm" wz="WZ#numberFormat(ATTRIBUTES.tenant_wz_index,'00000')#" out="qSpecification"/>
<cfif (ATTRIBUTES.debug)> <cfif (ATTRIBUTES.debug)>
qSpecification (from CRM)
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput> <cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
<cfdump var=#qSpecification#/> <cfdump var=#qSpecification#/>
<cfflush/> <cfflush/>
@@ -77,87 +81,137 @@
-- бесплатный лимит есть только для S3 HOT (в месяц в единицах измерения) ---> -- бесплатный лимит есть только для S3 HOT (в месяц в единицах измерения) --->
<!--- здесь нужно организовать пересечение версионной спецификации с анализируемым диапазоном дат ---> <!--- ******************************** здесь нужно организовать пересечение версионной спецификации с анализируемым диапазоном дат --->
<cfquery name="qSpec" dbtype="query"> <!--- Проверка: включаем дату смены цены в диапазон и смотрим --->
select s.catalogName as svc, s.component, s.fullCode as code, s.measureShort as unit <!--- Важно: если в периоде было 2+ цены, то строки должны быть замножены, а цена за период перестает иметь смысл (не показывать же среднее арифметическое, это неправильно) --->
, s.priceWVat as price, (s.discount + s.serviceDiscount - s.discount*s.serviceDiscount/100) as discount <!--- Left Outer Join with Free Tier --->
,s.serviceStart, s.serviceEnd, s.serviceHash, s.serviceCustomName, f.free_tier <cfquery name="qSpec" dbtype="query">
from qSpecification s, qFreeTier f select s.catalogName as svc, s.component, s.serviceCode as service_code, s.fullCode as code, s.measureShort as unit
WHERE s.fullCode=f.code , s.priceWVat as price, (s.discount + s.serviceDiscount - s.discount*s.serviceDiscount/100) as discount
,s.serviceStart, s.serviceEnd, s.serviceHash, s.serviceCustomName, f.free_tier
from qSpecification s, qFreeTier f
WHERE s.fullCode=f.code
AND s.serviceType = 3
UNION UNION
select s.catalogName as svc, s.component, s.fullCode as code, s.measureShort as unit 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 , s.priceWVat as price, (s.discount + s.serviceDiscount - s.discount*s.serviceDiscount/100) as discount
,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">)
</cfquery> AND s.serviceType = 3
</cfquery>
<!--- <cfdump var=#qSpec# abort=true/> ---> <cfif (ATTRIBUTES.debug)>
qSpec (Left Outer Join with Free Tier)
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
<cfdump var=#qSpec#/>
<cfflush/>
</cfif>
<!--- <cfloop from=#attributes.dt_start# to=#attributes.dt_finish# step="#CreateTimeSpan( 1, 0, 0, 0 )#" index="local.dt"> <!--- -- 1. Create a specialized expression index matching your date filters
<cfdump var=#dateFormat(local.dt,"YYYY-MM-DD")#/>---> CREATE INDEX idx_capacity_storage_perf_test ON ngcloud_ru.capacity_storage (
vdc_id,
(timestamp::date),
storage_profile_types_id
)
INCLUDE (limit, used);
<!--- *** Нужно еще реализовать в модели данных политики бесплатного минимума, сейчас пробито гвоздями ---> -- 2. Optional: Standardize tenant scanning since it currently falls back to Seq Scan
CREATE INDEX idx_tenant_wzcode ON ngcloud_ru.tenant (wzcode); --->
<!--- *** Нужно еще реализовать в модели данных (каталоге) политики бесплатного минимума (free tier), сейчас прибито гвоздями --->
<cfquery name="qComputing"> <cfquery name="qComputing">
select 'WZ'||to_char(tenant.wzcode,'FM00000') as wz, vdc.name as vdc_name, select 'WZ'||to_char(tenant.wzcode,'FM00000') 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'
when vdc.name like '%-v1cl4%' then 'iaas.ngc.a28' WHEN vdc.name LIKE '%-v1cl4%' THEN 'iaas.ngc.a28'
when vdc.name like '%-v1cl6%' then 'iaas.ngc.i28' WHEN vdc.name LIKE '%-v1cl5%' THEN 'iaas.ngc.a45'
else '['||vdc.name||']' end as code, WHEN vdc.name LIKE '%-v1cl6%' THEN 'iaas.ngc.i28'
WHEN vdc.name LIKE '%-v1cl7%' THEN 'iaas.ngc.a40' -- *** смапить все кластера
ELSE '['||vdc.name||']' end as code,
sum(cpu_used/cpu_speed)/4. as core_h, sum(cpu_used/cpu_speed)/4. as core_h,
sum(mem_used)/1024/4. as gb_h, sum(mem_used)/1024/4. as gb_h,
capacity_resource.timestamp::date as dt capacity_resource.timestamp::date as dt,
vdc.name as dimension_1,
null as dimension_2
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.wzcode = <cfqueryparam cfsqltype="cf_sql_integer" value=#ATTRIBUTES.tenant_wz_index#/>
AND capacity_resource.timestamp::date >= <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::date <= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#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.wzcode, vdc.name, capacity_resource.timestamp::date
order by tenant.wzcode, vdc.name, capacity_resource.timestamp::date; order by tenant.wzcode, vdc.name, capacity_resource.timestamp::date;
</cfquery> </cfquery>
<cfif (ATTRIBUTES.debug)> <cfif (ATTRIBUTES.debug)>
qComputing
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput> <cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
<cfdump var=#qComputing#/> <cfdump var=#qComputing#/>
<cfflush/> <cfflush/>
</cfif> </cfif>
<!--- если захочется сгенерить литерал таблицы соответствия
with a as (
select * from ngcloud_ru.storage_profile_types
)
select '('||id||', '''|| name||''', '''||
case
when name ilike '%fastssd%' then 'fstssd'
when name ilike '%ssd%' then 'ssd'
when name ilike '%sata%' then 'sata'
else '' end
||''''||')'
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 'WZ'||to_char(tenant.wzcode,'FM00000') 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'
WHEN vdc.name LIKE '%-v1cl4%' THEN 'iaas.ngc.a28' WHEN vdc.name LIKE '%-v1cl4%' THEN 'iaas.ngc.a28'
WHEN vdc.name LIKE '%-v1cl5%' THEN 'iaas.ngc.a45'
WHEN vdc.name LIKE '%-v1cl6%' THEN 'iaas.ngc.i28' WHEN vdc.name LIKE '%-v1cl6%' THEN 'iaas.ngc.i28'
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 '%-SSD%' THEN '.ssd-m' WHEN storage_profile_types.name ILIKE '%FASTSSD%' THEN '.fstssd-m'
WHEN storage_profile_types.name ILIKE '%-SAS' THEN '.sas-m' WHEN storage_profile_types.name ILIKE '%SSD%' THEN '.ssd-m'
WHEN storage_profile_types.name ILIKE '%-SATA' THEN '.sata-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 ELSE '.['|| storage_profile_types.name ||']' END
as code, as code,
storage_profile_types.name, -- *** это не тип, а профайл storage_profile_types.name, -- *** это не тип, а профайл
sum("limit")/1024./4 as gb_h_limit, -- 4 потому, что сбор раз в 15 минут sum("limit")/1024./4./720 as gb_m_limit,
sum(used)/1024./4 as gb_h_used, sum(used)/1024./4./720 as gb_m_used,
capacity_storage.timestamp::date as dt capacity_storage.timestamp::date as dt,
vdc.name as dimension_1,
storage_profile_types.name as dimension_2
from ngcloud_ru.capacity_storage from ngcloud_ru.capacity_storage
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#/> where tenant.wzcode = <cfqueryparam cfsqltype="cf_sql_integer" value=#ATTRIBUTES.tenant_wz_index#/> --AND 1=0
AND capacity_storage.timestamp::date >= <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::date <= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#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.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; order by tenant.wzcode, 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); --->
<cfif (ATTRIBUTES.debug)>
qStorage
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
<cfdump var=#qStorage#/>
<cfflush/>
</cfif>
<!--- <!---
"placement" "size_gb" "get_10k" "put_10k" "bytes_sent_gb" "placement" "size_gb" "get_10k" "put_10k" "bytes_sent_gb"
@@ -166,11 +220,12 @@ WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsql
<!--- надо дисконтировать не ГБ-часы а ГБ-месяцы ---> <!--- надо дисконтировать не ГБ-часы а ГБ-месяцы --->
<cfquery name="qS3Vol"> <cfquery name="qS3Vol">
select /* *** ниже дублирование кода агрегации - round ceil etc, обратить внимание при правке */ select /* *** ниже дублирование кода агрегации - round ceil etc, обратить внимание при правке */
(sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>) as vol_B /* *** переделать на avg - сначала агрегировать по бакетам*/
,ceil(sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>/1024/1024/1024) as vol_GB (sum(bucket_stat.usage_rgw_main_size_actual)/720) as vol_B
,greatest(ceil(sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/> ,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)- /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 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'
@@ -178,18 +233,25 @@ WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsql
--,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
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 dimension_1
,null as dimension_2
from s3billing.bucket_stat from s3billing.bucket_stat
join s3billing.placement ON s3billing.placement.id=bucket_stat.placement_id join s3billing.placement ON s3billing.placement.id=bucket_stat.placement_id
join s3billing.bucket_info ON bucket_info.id=bucket_stat.bucket_id join s3billing.bucket_info ON bucket_info.id=bucket_stat.bucket_id
where bucket_stat.placement_id in (1,2) where bucket_stat.placement_id in (1,2)
AND bucket_stat.owner = <cfqueryparam cfsqltype="cf_sql_varchar" value=#ATTRIBUTES.tenant_wz_index#/> AND bucket_stat.owner = <cfqueryparam cfsqltype="cf_sql_varchar" value=#ATTRIBUTES.tenant_wz_index#/>
AND bucket_stat.timestamp_addition::date >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#attributes.dt_start#/> AND bucket_stat.timestamp_addition >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#attributes.dt_start#/>
AND bucket_stat.timestamp_addition::date <= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#attributes.dt_finish#/> AND bucket_stat.timestamp_addition < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dateAdd("d",1,attributes.dt_finish)#/>
group by bucket_stat.owner, bucket_stat.placement_id, 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_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
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput> <cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
<cfdump var=#qS3Vol#/> <cfdump var=#qS3Vol#/>
<cfflush/> <cfflush/>
@@ -212,14 +274,14 @@ WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsql
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( /*,greatest(ceil(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) - CASE WHEN bucket_info.placement_id = 1 THEN 10 ELSE 0 END, 0 )/10000) - CASE WHEN bucket_info.placement_id = 1 THEN 10 ELSE 0 END, 0
) as get_10k_free_tier_subtracted ) as get_10k_free_tier_subtracted*/
,sum( ,sum(
CASE WHEN CASE WHEN
@@ -237,7 +299,7 @@ WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsql
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( /*,greatest(ceil(sum(
CASE WHEN CASE WHEN
(category.name LIKE 'put%' (category.name LIKE 'put%'
OR category.name LIKE 'post%' OR category.name LIKE 'post%'
@@ -245,12 +307,12 @@ WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsql
OR category.name LIKE 'list%' OR category.name LIKE 'list%'
) THEN ops ELSE 0 END ) THEN ops ELSE 0 END
)/10000) - CASE WHEN bucket_info.placement_id = 1 THEN 1 ELSE 0 END, 0 )/10000) - CASE WHEN bucket_info.placement_id = 1 THEN 1 ELSE 0 END, 0
) as put_10k_free_tier_subtracted ) 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 ,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 /*,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 ) 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'
@@ -259,6 +321,12 @@ WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsql
--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
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 dimension_1
,null as dimension_2
from s3billing.usage_bucket_by_user from s3billing.usage_bucket_by_user
join s3billing.user_info on usage_bucket_by_user.user_id=user_info.id 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.category on usage_bucket_by_user.category_id=category.id
@@ -266,39 +334,73 @@ WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsql
join s3billing.placement on bucket_info.placement_id=placement.id join s3billing.placement on bucket_info.placement_id=placement.id
where 1=1 where 1=1
AND user_info.name = <cfqueryparam cfsqltype="cf_sql_varchar" value=#ATTRIBUTES.tenant_wz_index#/> AND user_info.name = <cfqueryparam cfsqltype="cf_sql_varchar" value=#ATTRIBUTES.tenant_wz_index#/>
AND usage_bucket_by_user.time::date >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#attributes.dt_start#/> AND usage_bucket_by_user.time >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#attributes.dt_start#/>
AND usage_bucket_by_user.time::date <= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#attributes.dt_finish#/> AND usage_bucket_by_user.time < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dateAdd("d",1,attributes.dt_finish)#/>
group by user_info.name, bucket_info.placement_id, usage_bucket_by_user.time::date group by user_info.name, bucket_info.placement_id, bucket_info.name, usage_bucket_by_user.time::date
order by user_info.name, bucket_info.placement_id, usage_bucket_by_user.time::date; order by user_info.name, bucket_info.placement_id, bucket_info.name, usage_bucket_by_user.time::date;
</cfquery> </cfquery>
<!--- здесь метрики уже сгруппированы по дням. Умножать на цену еще рано, надо сильно позже --->
<cfif (ATTRIBUTES.debug)> <cfif (ATTRIBUTES.debug)>
qS3OpsTrf
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput> <cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
<cfdump var=#qS3OpsTrf#/> <cfdump var=#qS3OpsTrf#/>
<cfflush/> <cfflush/>
</cfif> </cfif>
<!--- собрали все метрики в один резалтсет ---> <!--- собрали все метрики в один резалтсет --->
<!--- Здесь мы знаем единицы измерения, причем надежнее, чем в спецификации --->
<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 from qComputing select dt, wz, code + '.vcpu-m' as code, core_h as raw_metric, core_h as metric,
union all dimension_1, 'vdc' as dimension_1_name, dimension_2, NULL as dimension_2_name, 'ядро-ч' as unit
select dt, wz, code || '.ram-m' as code, gb_h as raw_metric, gb_h as metric from qComputing from qComputing
union all
select dt, wz, code, gb_h_used as raw_metric, gb_h_used as metric from qStorage
union all
select dt, wz, code || '.vol-m', vol_b as raw_metric, vol_gb as metric from qS3Vol
union all
select dt, wz, code || '.get-m', get as raw_metric, get_10k as metric from qS3OpsTrf
union all
select dt, wz, code || '.put-m', put as raw_metric, put_10k as metric from qS3OpsTrf
union all
select dt, wz, code || '.trf-m', bytes_sent as raw_metric, bytes_sent_gb as metric from qS3OpsTrf
order by dt, code union all
select dt, wz, code + '.ram-m' 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
union all
select dt, wz, code, gb_m_used as raw_metric, gb_m_used as metric,
dimension_1, 'vdc' as dimension_1_name, dimension_2, 'storage policy' as dimension_2_name, 'ГБайт-м' as unit
from qStorage
union all
select dt, wz, code + '.vol-m', 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,
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,
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,
dimension_1, 'bucket' as dimension_1_name, dimension_2, NULL as dimension_2_name, 'ГБайт' as unit
from qS3OpsTrf
order by dt, code, dimension_1, dimension_2 <!--- /*dimension_*_name нужно только в качестве заголовка *** потом оптимизировать */ --->
</cfquery> </cfquery>
<!--- clean extra spaces to work around QoQ bug --->
<cfloop query="qUnifiedMetric">
<cfset qUnifiedMetric.dimension_1_name=trim(qUnifiedMetric.dimension_1_name)/>
<cfset qUnifiedMetric.dimension_2_name=trim(qUnifiedMetric.dimension_2_name)/>
<!--- *** возможно, и с другими полями придется сделать то же самое --->
</cfloop>
<!--- This is a known, long-standing quirk in ColdFusion's Query of Queries (QoQ) engine.When you perform a UNION ALL across queries where a character column has a dynamic or varying string length (like your concatenated m.dimension_1_name + 'x'), QoQ identifies the longest string in that column across all result sets. It then treats the column like a fixed-length CHAR database field and pads all shorter values with trailing spaces to match that maximum length. --->
<!--- лучше никогда не связываться с QoQ, но если все исполнять в исходной бд, это скорее мегаселект с большим числом CTE --->
<!--- надо было сразу делать в БД. Причем сводить в одну, чтобы избежать нескольких источников данных... можно было бы и так добиться, но все равно сводим в буферную базу --->
<!--- <cfdump var=#qUnifiedMetric#/> ---> <!--- <cfdump var=#qUnifiedMetric#/> --->
<cfif (ATTRIBUTES.debug)> <cfif (ATTRIBUTES.debug)>
qUnifiedMetric
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput> <cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
<cfdump var=#qUnifiedMetric#/> <cfdump var=#qUnifiedMetric#/>
<cfflush/> <cfflush/>
@@ -318,15 +420,19 @@ WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsql
<!--- Вычитание Free Tier можно начинать с начала месяца - для этого можно сгруппировать по месяцам ---> <!--- Вычитание Free Tier можно начинать с начала месяца - для этого можно сгруппировать по месяцам --->
<!--- Кажется, здесь еще рано агрегировать, если мы хотим OUTER JOIN ---> <!--- Кажется, здесь еще рано агрегировать, если мы хотим OUTER JOIN --->
<!--- агрегировать вообще не надо в этом компоненте, а стоимость вычислять после агрегации --->
<cfquery name="qChargeDaily" dbType="query"> <cfquery name="qChargeDaily" dbType="query">
SELECT SELECT
<d:field_set titleMapOut="titleMap" lengthOut="fieldCount"> <d:field_set titleMapOut="titleMap" lengthOut="fieldCount">
<d:field title="Артикул">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.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="Ед.изм.">s.unit</d:field> <d:field title="Ед.изм.">m.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 as 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 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.raw_metric</d:field>
@@ -337,53 +443,82 @@ WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsql
<d:field title="serviceCustomName">s.serviceCustomName</d:field> <d:field title="serviceCustomName">s.serviceCustomName</d:field>
<d:field title="dt" cfSqlType="CF_SQL_TIMESTAMP">m.dt</d:field> <d:field title="dt" cfSqlType="CF_SQL_TIMESTAMP">m.dt</d:field>
<d:field title="free tier" cfSqlType="CF_SQL_NUMERIC">s.free_tier</d:field> <d:field title="free tier" cfSqlType="CF_SQL_NUMERIC">s.free_tier</d:field>
<d:field>m.dimension_1</d:field>
<d:field>m.dimension_1_name</d:field>
<d:field>m.dimension_2</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 as s, qUnifiedMetric as m
WHERE m.wz=<cfqueryparam cfsqltype="cf_sql_varchar" value=#request.auth.wz#/> WHERE s.code=m.code
AND s.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)
<cfif #attributes.restrict_to_own_instances#>
AND m.wz=<cfqueryparam cfsqltype="cf_sql_varchar" value=#request.auth.wz#/>
</cfif>
<!--- таким замысловатым способом мы реализуем FULL JOIN ---> <!--- таким замысловатым способом мы реализуем FULL JOIN --->
UNION UNION ALL
SELECT NULL, m.code, NULL, NULL, NULL, NULL, NULL, NULL, m.raw_metric, m.metric, NULL, NULL, NULL, NULL, m.dt, 0
FROM qUnifiedMetric as m where m.code NOT IN (<cfqueryparam value="#ValueList(qSpec.code)#" cfsqltype="cf_sql_varchar" list="true">)
UNION SELECT NULL, m.code, NULL, NULL, NULL, NULL, 0,
SELECT s.code, NULL, s.svc, s.component, s.unit, s.price*(100-s.discount)/100, s.price, s.discount, NULL, NULL, s.serviceStart, s.serviceEnd, s.serviceHash, s.serviceCustomName, 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
FROM qSpec as s where s.code NOT IN (<cfqueryparam value="#listRemoveDuplicates(ValueList(qUnifiedMetric.code))#" cfsqltype="cf_sql_varchar" list="true">) FROM qUnifiedMetric as m
WHERE m.code NOT IN (<cfqueryparam value="#ValueList(qSpec.code)#" cfsqltype="cf_sql_varchar" list="true">)
order by m.dt, s.code 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
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
</cfquery> </cfquery>
<!--- диагностика ошибок в QoQ крайне невнятна и вводит в заблуждение --->
<!---<cfdump var=#qChargeDaily# abort=true/> --->
<cfif (ATTRIBUTES.debug)>
qChargeDaily
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
<cfdump var=#qChargeDaily#/>
<cfflush/>
</cfif>
<!--- Из-за детализации придется вынести вычитание free_tier на более поздний этап --->
<!--- Здесь агрегировано за месяц, но детализировано по измерениям --->
<cfquery name="qChargeMonthly" dbType="query"> <cfquery name="qChargeMonthly" dbType="query">
SELECT SELECT
<d:field_set titleMapOut="titleMap" lengthOut="fieldCount"> <d:field_set titleMapOut="chargeTitleMap" lengthOut="chargeFieldCount">
<d:field title="Артикул по договору">code</d:field> <d:field title="Артикул по договору">s_code</d:field>
<d:field title="Артикул по системам">m_code</d:field> <d:field title="Артикул по системам">m_code</d:field>
<d:field title="Услуга по каталогу">svc</d:field><!--- *** добавить пользовательское ---> <d:field title="Артикул">s_code as code</d:field>
<d:field title="Код услуги">service_code</d:field>
<d:field title="Услуга по каталогу">svc</d:field>
<d:field title="serviceCustomName">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 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="Объем к оплате" cfSqlType="CF_SQL_NUMERIC">sum(metric) - free_tier as chargeable_metric</d:field>
<d:field title="Сумма к оплате" cfSqlType="CF_SQL_NUMERIC">(sum(metric) - free_tier)*discounted_price as charge</d:field>
<d:field title="serviceStart" cfSqlType="CF_SQL_TIMESTAMP">serviceStart</d:field> <d:field title="serviceStart" cfSqlType="CF_SQL_TIMESTAMP">serviceStart</d:field>
<d:field title="serviceEnd" cfSqlType="CF_SQL_TIMESTAMP">serviceEnd</d:field> <d:field title="serviceEnd" cfSqlType="CF_SQL_TIMESTAMP">serviceEnd</d:field>
<d:field title="dt_billed_from" cfSqlType="CF_SQL_TIMESTAMP">min(dt) as dt_billed_from</d:field>
<d:field title="dt_billed_to" cfSqlType="CF_SQL_TIMESTAMP">max(dt) as dt_billed_to</d:field>
<d:field title="serviceHash">serviceHash</d:field> <d:field title="serviceHash">serviceHash</d:field>
<d:field title="serviceCustomName">serviceCustomName</d:field>
<d:field title="free tier" cfSqlType="CF_SQL_NUMERIC">free_tier</d:field> <d:field title="free tier" cfSqlType="CF_SQL_NUMERIC">free_tier</d:field>
<d:field>dimension_1</d:field>
<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">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">month(dt) as month</d:field>
</d:field_set> </d:field_set>
from qChargeDaily from qChargeDaily
group by code, m_code, svc, serviceCustomName, component, unit, discounted_price, serviceStart, ServiceEnd, serviceHash, free_tier, year(dt), month(dt) group by s_code, m_code, svc, service_code, serviceCustomName, component, unit, discounted_price, serviceStart, ServiceEnd, serviceHash, free_tier, year(dt), month(dt),
order by year(dt), month(dt), code, m_code dimension_1_name, dimension_1, dimension_2_name, dimension_2
order by year(dt), month(dt), m_code, code, serviceStart, dimension_1, dimension_2
</cfquery> </cfquery>
<!--- в связи с бедностью языка QoQ (нет CASE) грубо патчим резалтсет ---> <!--- в связи с бедностью языка QoQ (нет CASE) грубо патчим резалтсет --->
<cfloop query="qChargeMonthly"> <cfloop query="qChargeMonthly">
<cfset qChargeMonthly.chargeable_metric = (chargeable_metric GT 0) ? chargeable_metric : 0/> <!--- имя в левой части должно быть квалифицировано запросом (резалтсетом) ---> <cfset qChargeMonthly.code = (len(code) GT 0) ? code : m_code/><!--- имя в левой части должно быть квалифицировано запросом (резалтсетом) --->
<cfset qChargeMonthly.charge = (charge GT 0) ? charge : 0/>
</cfloop> </cfloop>
@@ -402,14 +537,15 @@ WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsql
<!--- <cfdump var=#qChargeMonthly#/> ---> <!--- <cfdump var=#qChargeMonthly#/> --->
<!--- *** проверить краевые даты ---> <!--- *** проверить краевые даты --->
<cfif (ATTRIBUTES.debug)> <cfif (ATTRIBUTES.debug)>
qChargeMonthly
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput> <cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
<cfdump var=#qChargeMonthly#/> <cfdump var=#qChargeMonthly#/>
<cfflush/> <cfflush/>
</cfif> </cfif>
<cfoutput>#getTickCount() - request.startTickCount#</cfoutput> <cfoutput>#getTickCount() - request.startTickCount#</cfoutput>
<cfflush/> <!--- <cfflush/> --->
@@ -419,9 +555,11 @@ WHERE s.fullCode NOT IN (<cfqueryparam value="#ValueList(qFreeTier.code)#" cfsql
<cfset report.qStorageAge = qStorageAge/> <cfset report.qStorageAge = qStorageAge/>
<cfset report.qS3VolAge = qS3VolAge/> <cfset report.qS3VolAge = qS3VolAge/>
<cfset report.qS3OpsTrfAge = qS3OpsTrfAge/> <cfset report.qS3OpsTrfAge = qS3OpsTrfAge/>
<cfset report.qSpecification = qSpecification/>
<cfset report.qCharge = qChargeMonthly/> <cfset report.qCharge = qChargeMonthly/>
<cfset report.chargeTitleMap = titleMap/> <cfset report.qFreeTier = qFreeTier/><!--- мы не применяем скидку на уровне детализации, а только после агрегации. На самом деле правильно было бы агрегировать натуральные метрики, а умножать на цену уже агрегаты --->
<cfset report.chargeFieldCount = fieldCount/> <cfset report.chargeTitleMap = chargeTitleMap/><!--- нужен для экспорта в XL --->
<!--- <cfset report.chargeFieldCount = chargeFieldCount/> --->
<cfset "CALLER.#ATTRIBUTES.output#" = report/> <cfset "CALLER.#ATTRIBUTES.output#" = report/>
+20 -5
View File
@@ -2,11 +2,11 @@
<cfimport prefix="d" taglib="../lib/data"/> <cfimport prefix="d" taglib="../lib/data"/>
<cfparam name="ATTRIBUTES.wz"/> <cfparam name="ATTRIBUTES.wz"/>
<cfparam name="ATTRIBUTES.crm_cache_base" default="https://elma-cache.adl.nubes.ru/contract_ls.cfm"/> <!--- <cfparam name="ATTRIBUTES.crm_cache_base" default="https://elma-cache.adl.nubes.ru/contract_ls.cfm"/> --->
<cfparam name="ATTRIBUTES.out" default="qComponent"/> <cfparam name="ATTRIBUTES.out" default="qComponent"/>
<cfhttp method="get" <cfhttp method="get"
url="#ATTRIBUTES.crm_cache_base#?wz=#ATTRIBUTES.wz#" url="#request.crm_cache_base#?wz=#ATTRIBUTES.wz#"
timeout=10 timeout=10
charset="utf-8" charset="utf-8"
result="res" result="res"
@@ -14,9 +14,22 @@
</cfhttp> </cfhttp>
<cfset local={}/> <cfset local={}/>
<!--- --->
<cfset safeJsonString = REReplace(res.fileContent, '(:\s*)(-?\d+\.\d{16,})', '$1"$2"', "ALL")/><!--- очень длинные числа завернуть в кавычки --->
<!--- <cfdump var=#safeJsonString#/> --->
<cftry>
<cfset company=deserializeJson(safeJsonString)/>
<cfcatch type="ANY">
Cannot deserialize CRM response
<cfset company=deserializeJson(res.fileContent)/> result:
<!--- <cfdump var=#company#/> ---> <cfdump var=#res#/>
safeJsonString:
<cfdump var=#safeJsonString#/>
</cfcatch>
</cftry>
<!---<cfset company=deserializeJson(res.fileContent)/> --->
<!---<cfdump var=#company#/> --->
<!--- *** это не слишком надежно, некорректно представленный NULL (обыкновенно представляемый пустой строкой) может все сломать. Да и корректный ---> <!--- *** это не слишком надежно, некорректно представленный NULL (обыкновенно представляемый пустой строкой) может все сломать. Да и корректный --->
<!--- мартышкин труд собирать этот запрос из JSON , может, лучше было динамически ---> <!--- мартышкин труд собирать этот запрос из JSON , может, лучше было динамически --->
<cfset qComponent = queryNew(" <cfset qComponent = queryNew("
@@ -47,7 +60,9 @@
,discount ,discount
,atomicCode ,atomicCode
,fullCode ,fullCode
")/> ",
"varChar,varChar,varChar,varChar,integer,varChar,integer,varChar,varChar,varChar,timestamp,timestamp,decimal,decimal,varChar,integer,varChar,varChar,varChar,decimal,decimal,decimal,varChar,varChar")/>
<!--- если не указывать типы, он пытается их определить сам, потом проблемы с union и арифметикой --->
<!--- this.nullSupport = true in Application.cfc ... no way ---> <!--- this.nullSupport = true in Application.cfc ... no way --->
<cfloop array=#company.contracts# item="local.contract"><!--- имя массива писать в ##, иначе думает, что это строковый литерал ---> <cfloop array=#company.contracts# item="local.contract"><!--- имя массива писать в ##, иначе думает, что это строковый литерал --->
+22
View File
@@ -0,0 +1,22 @@
SQL SELECT
s.code,m.code as m_code,s.service_code as service_code,s.svc,s.component,s.unit,
s.price*(100-s.discount)/100 as discounted_price
,s.price,s.discount,m.raw_metric,m.metric,s.serviceStart,s.serviceEnd,s.serviceHash,s.serviceCustomName,m.dt,s.free_tier,m.dimension_1,m.dimension_1_name ||'x' as dimension_1_name,m.dimension_2,m.dimension_2_name
FROM qSpec as s, qUnifiedMetric as m
WHERE m.wz='62'
AND s.code=m.code
AND s.serviceStart <= m.dt
AND (m.dt <= s.serviceEnd OR s.serviceEnd IS NULL)
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 ||'y', m.dimension_2, m.dimension_2_name
FROM qUnifiedMetric as m where m.code NOT IN ('5','6')
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
FROM qSpec as s where s.code NOT IN ('5','6')
order by m.dt, m.code, dimension_1, dimension_2
+202 -58
View File
@@ -7,34 +7,45 @@
<m:prepare_ls entity="payg" accessObject="" pageInfoOut="pageInfo" trackOut="tr"/> <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_finish" type="date" default=#dateAdd('d',-1,createDateTime(year(Now()),month(Now()),1,0,0,0))#/>
<cfparam name="dt_start" type="date" default=#dateAdd('m',-1,dt_finish)#/> <cfparam name="dt_start" type="date" default=#dateAdd('m',-1,createDateTime(year(Now()),month(Now()),1,0,0,0))#/>
<cfset hours=dateDiff('h',dt_start,dt_finish)/>
<cfset tenant_wz_index=val(mid(request.auth.wz,3,5))/><!--- cut off WZ ---> <cfset hours=dateDiff('h', dt_start, dateAdd('d', 1, dt_finish))/>
<cfset restrict_to_own_instances = true>
<cfparam name="WZ" default=#request.auth.wz#/>
<cfif request.auth.wz EQ 'WZ01112'><!--- грубо --->
<cfset restrict_to_own_instances = false>
<cfelse>
<cfset WZ=request.auth.wz/><!--- без баловства --->
</cfif>
<cfset tenant_wz_index=val(mid(WZ,3,5))/><!--- cut off WZ --->
<cfif isDefined("output_xls")><cfmodule template="mod/payg.cfm" <cfif isDefined("output_xls")><cfmodule template="mod/compute_payg.cfm"
dt_finish=#dt_finish# dt_finish=#dt_finish#
dt_start=#dt_start# dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index# tenant_wz_index=#tenant_wz_index#
output="report" output="report"
debug=#isDefined("DEBUG")# debug=#isDefined("DEBUG")#
/><layout:xml qRead=#report.qCharge# titleMap=#report.chargeTitleMap# filename="#pageInfo.entity#.xml"/> restrict_to_own_instances=#restrict_to_own_instances#
/>
<layout:xml qRead=#report.qCharge# titleMap=#report.chargeTitleMap# filename="#pageInfo.entity#.xml"/>
<cfabort/> <cfabort/>
</cfif><cfif isDefined("output_json")><cfmodule template="mod/payg.cfm" </cfif><cfif isDefined("output_json")><cfmodule template="mod/compute_payg.cfm"
dt_finish=#dt_finish# dt_finish=#dt_finish#
dt_start=#dt_start# dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index# tenant_wz_index=#tenant_wz_index#
output="report" output="report"
debug=#isDefined("DEBUG")# debug=#isDefined("DEBUG")#
restrict_to_own_instances=#restrict_to_own_instances#
/><!--- <cfdump var=#report#/><cfabort/> ---><layout:json qRead=#report.qCharge# titleMap=#report.chargeTitleMap# filename="#pageInfo.entity#.json"/> /><!--- <cfdump var=#report#/><cfabort/> ---><layout:json qRead=#report.qCharge# titleMap=#report.chargeTitleMap# filename="#pageInfo.entity#.json"/>
<cfabort/> <cfabort/>
</cfif><!--- </cfif><!---
---><layout:page section="header" pageInfo=#pageInfo#> ---><layout:page section="header" pageInfo=#pageInfo#>
<layout:attribute name="title"> <layout:attribute name="title">
<cfoutput><b>Отчет по PAYG</b> #request.auth.wz#</cfoutput> <cfoutput><b>Отчет по PAYG</b> #WZ#</cfoutput>
</layout:attribute> </layout:attribute>
<layout:attribute name="controls"> <layout:attribute name="controls">
<!---skip filter link, filter is not implemented---> <!---skip filter link, filter is not implemented--->
@@ -42,58 +53,83 @@
</layout:attribute> </layout:attribute>
</layout:page> </layout:page>
<!--- Внимание! округление вверх при расчете метрик get, put S3. Расчетные суммы могут содержать погрешности округления, поэтому окончательный расчет стоимости (количество * цену) должен выполняться в бухгалтерской программе. <!--- Внимание! округление вверх при расчете метрик get, put S3. Расчетные суммы могут содержать погрешности округления, поэтому окончательный расчет стоимости (количество * цену) выполняeтся в бухгалтерской программе.
<b>Если у клиента больше 1 действующего допника с услугами PAYG, то данные в отчете могут быть замножены, в этом случае считать вручную</b> ---> <b>Если у клиента больше 1 действующего допника с услугами PAYG, то данные в отчете могут быть замножены, в этом случае считать вручную</b> --->
<cfoutput> <cfoutput>
<form method="post" action=""> <form method="post" action="#request.thisPage#" style="display:inline"><!--- не надо делать action="" - параметр, затесавшийся в URL будет затирать параметр формы, и заметить это трудно --->
Период с <input type="text" name="dt_start" value="#dateFormat(dt_start,'YYYY-MM-DD')#"/> Период с <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="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;"/><!--- ********* ---> <cfif request.auth.wz EQ 'WZ01112'><!--- очень грубо --->
WZ <input type="text" name="wz" value="#WZ#"/>
</cfif>
<input type="submit" style="cursor:pointer;"/>
<input type="submit" name="DEBUG" value="DEBUG" style="cursor:pointer;"/><!--- ********* --->
</form> </form>
<cfset month_start = createDate(year(now()), month(now()), 1)/>
<a href="?dt_start=#dateFormat(dateAdd('m', -1, month_start),'YYYY-MM-DD')#&dt_finish=#dateFormat(dateAdd('d', -1, month_start),'YYYY-MM-DD')#&wz=#WZ#">
прошлый месяц
</a>
<a href="?dt_start=#dateFormat(createDate(year(now()), month(now()), 1),'YYYY-MM-DD')#&dt_finish=#dateFormat(createDate(year(now()), month(now()), daysInMonth(now())),'YYYY-MM-DD')#&wz=#WZ#">
текущий месяц
</a>
<br/>
<!--- <cfflush/> ---> <!--- <cfflush/> --->
<cfmodule template="mod/payg.cfm" <cfmodule template="mod/compute_payg.cfm"
dt_finish=#dt_finish# dt_finish=#dt_finish#
dt_start=#dt_start# dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index# tenant_wz_index=#tenant_wz_index#
output="report" output="report"
debug=#isDefined("DEBUG")# debug=#structKeyExists(Form,"DEBUG")#
restrict_to_own_instances=#restrict_to_own_instances#
/> />
Часов в периоде: <b>#hours#</b> Строк в отчете: <b>#report.qCharge.recordCount#</b><br/> Часов в периоде: <b>#hours#</b> Строк в отчете: <b>#report.qCharge.recordCount#</b><br/>
Актуальность данных: Актуальность данных:
VCD Computing: <b>#dateFormat(report.qComputingAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qComputingAge.dt_load,'HH:MM:SS')#</b> VCD Computing: <b<cfif datediff("h",report.qComputingAge.dt_load, Now()) GT 24> class="err"</cfif>>
VCD Storage: <b>#dateFormat(report.qStorageAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qStorageAge.dt_load,'HH:MM:SS')#</b> #dateFormat(report.qComputingAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qComputingAge.dt_load,'HH:MM:SS')#
S3 хранение: <b>#dateFormat(report.qS3VolAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qS3VolAge.dt_load,'HH:MM:SS')#</b> </b>
S3 операции и трафик: <b>#dateFormat(report.qS3OpsTrfAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qS3OpsTrfAge.dt_load,'HH:MM:SS')#</b> VCD Storage: <b<cfif datediff("h",report.qStorageAge.dt_load, Now()) GT 24> class="err"</cfif>>
Цены GPL<br/> #dateFormat(report.qStorageAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qStorageAge.dt_load,'HH:MM:SS')#</b>
S3 хранение: <b<cfif datediff("h",report.qS3VolAge.dt_load, Now()) GT 24> class="err"</cfif>>
#dateFormat(report.qS3VolAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qS3VolAge.dt_load,'HH:MM:SS')#
</b>
S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt_load, Now()) GT 24> class="err"</cfif>>
#dateFormat(report.qS3OpsTrfAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qS3OpsTrfAge.dt_load,'HH:MM:SS')#
</b>
<br/> <br/>
Цены по данным договора из CRM (через elma-cache). Учитываются метрики по S3, vCloud (кроме GPU). Внимание! количество за период *** округляется вверх, но делается это после всех агрегаций.
<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_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> <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> </cfoutput>
<table class="worktable"> <table class="worktable">
<thead> <thead>
<td width="1%"></td> <th width="1%"></th>
<!--- <th width="3%">WZ</th> ---> <th width="7%" colspan="2">Артикул</th>
<th width="7%">Артикул</th> <th width="3%">С</td>
<th width="3%">По</td>
<th width="3%">Кол-во (метрика)</td> <th width="3%">Кол-во (метрика)</td>
<th width="3%">Free Tier</td>
<th width="7%">Оплачиваемый объем</th> <th width="7%">Оплачиваемый объем</th>
<th width="3%">Ед.изм.</th>
<th width="3%">Цена &#8381; с НДС</th> <th width="3%">Цена &#8381; с НДС</th>
<th width="3%">Стоимость &#8381; с НДС</th> <th width="3%">Стоимость &#8381; с НДС</th>
<th width="3%">&nbsp;&nbsp;&nbsp;</th>
<th width="10%">Услуга</th> <th width="10%">Услуга</th>
<th width="10%">Компонент</th> <th width="10%">Компонент</th>
<th width="3%">Ед.изм.</th>
<!--- <td width="10%">Клиентс. назв.</td> ---> <!--- <td width="10%">Клиентс. назв.</td> --->
<!--- <td width="3%">GPL с НДС</td> <!--- <td width="3%">GPL с НДС</td>
@@ -112,63 +148,171 @@ S3 операции и трафик: <b>#dateFormat(report.qS3OpsTrfAge.dt_load,
</thead> </thead>
<!--- *** Обнаружена засада: возможно, группировка (сортировка) отображаемого вывода нужна по размерности детализации, а не по коду
Но: агрегация скорее нужна по коду
Видимо, придется на фронте перетасовывать?
--->
<!--- Еще засада: если я делегирую агрегацию на фронт, мне нужно и вычет free_tier делегировать на фронт (помня еще, что он на месяц, а не на произвольный период - наверно, можно его вычитать всегда, а больше месяца не агрегировать... или умножать на число полных месяцев) --->
<!--- видимо, Free Tier и единицу измерения нужно брать и для строк, которые не найдены в договоре <!--- видимо, Free Tier и единицу измерения нужно брать и для строк, которые не найдены в договоре
(еше можно было бы туда для полноты добавлять GPL цену, но для этого нужен каталог) (еше можно было бы туда для полноты добавлять GPL цену, но для этого нужен каталог)
Добавить реквизиты договора? ---> Добавить реквизиты договора? --->
<cfoutput query="report.qCharge" group="year"> <cfoutput query="report.qCharge" group="year">
<cfoutput group="month"> <cfoutput group="month">
<tr><td colspan="99"><b>#year#-#numberFormat(month,"00")#</b></td></tr> <tr><td colspan="99"><cfif month GT 0><b>#year#-#numberFormat(month,"00")#</b></cfif></td></tr>
<cfoutput group="svc"> <cfoutput group="svc">
<cfset var acc = 0/> <cfset var accum_cost_svc = 0/>
<cfset var accum_metric_svc = 0/>
<tr> <tr>
<td bgcolor="##ccc"></td> <td bgcolor="##ccc"></td>
<td bgcolor="##ccc" colspan="9"> <td bgcolor="##ccc" colspan="12">
<b>#svc#</b> #svc# <b>#service_code#</b>
<cfif len(serviceCustomName)>(#serviceCustomName#)</cfif> <cfif len(serviceCustomName)>(#serviceCustomName#)</cfif>
<cfif serviceHash EQ "">(не найдено в договоре)</cfif> <cfif serviceHash EQ "">(не найдено в договоре)</cfif>
</td> </td>
</tr> </tr>
<cfoutput>
<cfset acc += isNumeric(charge) ? charge : 0/>
<cfoutput group="code">
<cfoutput group="serviceStart"> <!--- может быть, для ясности надо в каждой строке писать точный период --->
<cfset accum_metric_svc += isNumeric(metric) ? metric : 0/><!--- агрегация по метрике возможна только на уровне компонента --->
<tr> <tr>
<td>#currentRow#</a></td> <td></td>
<td colspan="10">#code# <cfif code EQ "">(#m_code#)</cfif></td>
<td>#code# <cfif code EQ "">(#m_code#)</cfif></td>
<td class="r">#metric#</td>
<td class="r" style="font-size:120%; padding:0 1em;">#chargeable_metric#</td>
<td class="r">#discounted_price#</td>
<td class="r">#numberFormat(charge,".00")#</td>
<td><!--- #acc# ---></td>
<td>#svc#</td> <td>#svc#</td>
<td>#component#</td> <td>#component# </td>
</tr>
<cfset accum_metric_dimension_1 = 0/>
<cfoutput group="dimension_1">
<cfset accum_metric_dimension_1 += isNumeric(metric) ? metric : 0/>
<tr class="i">
<td>#currentRow#</td>
<td>#dimension_1_name#</td>
<td>#dimension_1#</td>
<td class="c">#dateFormat(dt_billed_from,"YYYY-MM-DD")#</td>
<td class="c">#dateFormat(dt_billed_to,"YYYY-MM-DD")#</td>
<td class="r"><cfif metric NEQ 0 AND isNumeric(metric)>#round(metric,3)#</cfif></td>
<td><!--- free tier ---></td>
<td class="r" style="font-size:120%; padding:0 1em;"></td>
<td class="c">#unit#</td> <td class="c">#unit#</td>
<!--- <td>#user_description#</td> ---> <td class="r">#gaussianRound(discounted_price,2)#</td>
<td class="r"><!--- здесь не вычисляется стоимость, потому что Free Tier вычитается после агрегации (скидочные политики реализовывать будет непросто) ---></td>
<!--- <td class="r">#price#</td> <td></td>
<td class="r">#discount#</td> ---> <td></td>
</tr> </tr>
</cfoutput> </cfoutput>
<cfset cost_dimension_1 = precisionEvaluate("gaussianRound(max(accum_metric_dimension_1 - free_tier, 0)*gaussianRound(discounted_price, 2), 2)")/>
<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">#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="c">#unit#</td>
<td class="r">#gaussianRound(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>
</tr>
</cfoutput><!--- serviceStart --->
</cfoutput><!--- code --->
<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="4" class="r" style="border-bottom:2px solid gray;"><b>Итого</b> &nbsp;&nbsp;&nbsp; #svc#:</td> <td bgcolor="##eee" colspan="9" class="r" style="border-bottom:2px solid gray;">
<td bgcolor="##eee" colspan="1" class="r b" style="border-bottom:2px solid gray;">#NumFmt(acc,2)#</td> <b>Итого </b> &nbsp;&nbsp;&nbsp;
<td colspan="12" style="border-bottom:2px solid gray;"></td> <cfif len(service_code)>
#svc# <b>#service_code#</b>
<cfelse>
(#m_code#)
</cfif>
<cfif month GT 0>за #year#-#numberFormat(month,"00")#</cfif>:
</td>
<td bgcolor="##eee" class="r b" style="border-bottom:2px solid gray;">#accum_cost_svc#</td>
<td bgcolor="##eee" colspan="99" style="border-bottom:2px solid gray;"></td>
</tr > </tr >
</cfoutput> </cfoutput>
<tr><td colspan="99">&nbsp;</td></tr>
</cfoutput> </cfoutput>
</cfoutput> </cfoutput>
</table> </table>
<!--- <cfdump var=#qCharge#/> --->
<!--- <cfdump var=#report.qSpecification#/> --->
<h3>Спецификация</h3>
<table class="worktable">
<tr>
<th width="5%">Договор</th>
<th width="15%">Доп. соглашение</th>
<th width="3%">Тип оплаты</th>
<th width="20%">Пользовательское наименование</th>
<th width="3%">Кол-во по строке</th>
<th width="3%">Скидка по строке</th>
<th width="5%">НОУ</th>
<th width="5%">КОУ</th>
<th width="10%">Компонент</th>
<th width="5%">Артикул</th>
<th width="5%">Кол-во</th>
<th width="5%">Ед.изм.</th>
<th width="5%">Цена с НДС</th>
<th width="5%">Скидка %</th>
<th width="5%">Цена со скидкой с НДС</th>
</tr>
<cfoutput query="report.qSpecification">
<tr>
<td>#contractOutsideId#</td>
<td>#additionalAgreementName#</td>
<td class="c">#serviceType#</td>
<td>#serviceCustomName#</td>
<td class="c">#serviceQuantity#</td>
<td class="r">#serviceDiscount#</td>
<td class="c">#dateFormat(serviceStart, "YYYY-MM-DD")#</td>
<td class="c">#dateFormat(serviceEnd, "YYYY-MM-DD")#</td>
<td>#component#</td>
<td>#componentCode#</td>
<td class="r">#quantity#</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>
</tr>
</cfoutput>
</table>
<!---<cfdump var=#report.qCharge#/> --->
<!--- <cfdump var=#this.datasources[request.ds].connectionString#/> --->
<layout:page section="footer"/> <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'); ---> <!--- select * from s3billing.billing_per_user_fix_pl('2025-05-01 00:00:00','2025-06-01 00:00:00','1395'); --->
<cfscript>
function gaussianRound(numeric number, numeric precision = 0) {
var factor = 10 ^ precision;
var scaledNumber = number * factor;
var absScaled = abs(scaledNumber);
var sign = sgn(scaledNumber);
var floored = int(absScaled);
// If it's not ending in exactly .5, use standard rounding or precision scaling
if (absScaled - floored != 0.5) {
return round(number * factor) / factor;
}
// If it is exactly .5, round towards the nearest even number
if (floored % 2 != 0) {
// Nearest even is UP
return (floored + 1) * sign / factor;
} else {
// Nearest even is DOWN
return floored * sign / factor;
}
}
</cfscript>