Files
payg/payg.cfm
T
2026-08-14 18:08:36 +03:00

660 lines
32 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<cfsilent>
<cfimport prefix="m" taglib="lib"/>
<cfimport prefix="c" taglib="lib/controls"/>
<cfimport prefix="d" taglib="lib/data"/>
<cfimport prefix="layout" taglib="layout"/>
</cfsilent>
<m:prepare_ls entity="payg" accessObject="" pageInfoOut="pageInfo" trackOut="tr"/>
<!---
<cfparam name="dt_finish" type="date" default=#createDateTime(year(Now()),month(Now()),1,0,0,0)#/>
<cfparam name="dt_start" type="date" default=#dateAdd('m',-1,dt_finish)#/> --->
<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,createDateTime(year(Now()),month(Now()),1,0,0,0))#/>
<cfset hours=dateDiff('h', dt_start, dateAdd('d', 1, dt_finish))/>
<cfparam name="only_articles_in_spec" type="boolean" default=false/>
<!--- *** коды лучше взять из каталога --->
<cfquery name="qCode" datasource="dwh" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select distinct p.code
from elma.deals d
join elma.deals_services siv on (d.__id=ANY(siv.deal))
join elma.service_parametrs p on (p.__id = ANY(siv.params))
join elma.companies k on (d._companies=k.__id)
join elma.additional_agreements aa on (aa.__id = ANY(siv.additional_agreement))
join elma.aggrements a on (aa.contract_uid = a.__id)
where p.type=3 AND aa.is_actual
order by p.code
</cfquery>
<cfoutput>qCode:#getTickCount()-request.startTickCount#</cfoutput>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<!--- <cfdump var=#qCode#/> --->
<cfquery name="qContragent" datasource="dwh" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select k.id_klienta as wz, k.__id::text as contragent_uid, k.__name as contragent
from elma.companies k
where k."__deletedAt" IS NULL
</cfquery>
<cfoutput>qContragent:#getTickCount()-request.startTickCount#</cfoutput>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<!--- <cfif isDefined("DEBUG")>
<cfdump var=#qContragent#/>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
</cfif> --->
<!--- нужны компании, которые есть в пейге --->
<!--- <cfset qFreeTier=queryNew("code,free_tier","CF_SQL_VARCHAR,CF_SQL_NUMERIC",[['paas.s3.ceph.vol-m',1],['paas.s3.ceph.get-m',10],['paas.s3.ceph.put-m',1],['paas.s3.ceph.trf-m',100]])/> --->
<cfquery name="qSpec" datasource="dwh" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select k.id_klienta as wz, k.__id::text as contragent_uid,
p.code,
SUBSTRING(p.code FROM 1 FOR LENGTH(p.code) - 2) as sku_code,
p.price, p.discount, k.__name as contragent, a.__name as contract, a.__id::text as contract_uid, d.__name as deal, d.__id::text as deal_uid, siv.abstractive_service as abstract_service, siv.modifier, siv.user_description, siv.hash as line_key
,siv.date_nop as serviceStart, siv.date_end as serviceEnd, siv.discount as service_discount, siv.cnt as service_quantity
/*,p.__name as line_key*/, p.ed_izm, p.name as component, p.type, p.cnt*coalesce(siv.cnt,1) as component_quantity,
aa.__name as additional_agreement, aa.__id::text as additional_agreement_uid
--, aa.is_actual
,siv.end_customer as end_customer_uid
,z.id_klienta as end_customer_wz
,z.__name as end_customer_name
,ft.free_tier
from elma.deals d
join elma.deals_services siv on (d.__id=ANY(siv.deal))
join elma.service_parametrs p on (p.__id = ANY(siv.params))
join elma.companies k on (d._companies=k.__id)
join elma.additional_agreements aa on (aa.__id = ANY(siv.additional_agreement))
join elma.aggrements a on (aa.contract_uid = a.__id)
left outer join elma.companies z on (siv.end_customer=z.__id)
left outer join (values ('paas.s3.ceph.vol-m',1),('paas.s3.ceph.get-m',10),('paas.s3.ceph.put-m',1),('paas.s3.ceph.trf-m',100)) as ft(code,free_tier) ON (p.code=ft.code)
where p.type IN (2,3) AND aa.is_actual
AND siv.date_nop < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
AND (siv.date_end > <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/> OR siv.date_end IS NULL)/*****/
--AND k.id_klienta='WZ01031'
--AND k.id_klienta='WZ01112'
order by k.id_klienta, p.code
</cfquery>
<!--- здесь имеем список кодов с ценами и WZ. Каждому коду можно поставить в соответствие запрос, который даст по нему выборку. Изредка один запрос может вывести 2 кода, но разбить на 2 запроса легко. Кроме того, запросы можно объединить через union в один - может быть, так будет лучше читаться (быстрее выполняться не будет) --->
<cfoutput>qSpec:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfif isDefined("DEBUG")>
<cfdump var=#qSpec#/>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
</cfif>
<!--- siv means specification item version --->
<cfquery name="qSivAge" datasource="dwh" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select max(dt_load) as dt_load from elma.deals_services
</cfquery>
<cfoutput>qSivAge:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfquery name="qComputing" datasource="billing-vc" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select m.clientid as wz, vdc.name as vdc,
case
WHEN vdc.name LIKE '%-v1cl1%' THEN 'iaas.ngc.i29'
WHEN vdc.name LIKE '%-v1cl2%' THEN 'iaas.ngc.i31'
WHEN vdc.name LIKE '%-v1cl4%' THEN 'iaas.ngc.a28'
WHEN vdc.name LIKE '%-v1cl5%' THEN 'iaas.ngc.a45'
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(mem_used)/1024/4. as gb_h
from ngcloud_ru.capacity_resource
join ngcloud_ru.vdc on capacity_resource.vdc_id=vdc.id
join ngcloud_ru.tenant t on (t.id=vdc.tenant_id)
left outer join
(select tenant_id, ts, metadata_id,
ROW_NUMBER() OVER (PARTITION BY tenant_id ORDER BY ts desc, metadata_id desc) as rn
from ngcloud_ru.tenant_metadata
) tm on (t.id = tm.tenant_id AND tm.rn = 1)
left outer join ngcloud_ru.metadata m on (tm.metadata_id=m.id)
where length(m.clientid) > 0
AND capacity_resource.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
AND capacity_resource.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dateAdd("d",1,dt_finish)#/>
group by m.clientid, vdc.name
order by m.clientid, vdc.name;
</cfquery>
<cfoutput>qComputing:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfif isDefined("DEBUG")>
<cfdump var=#qComputing#/>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
</cfif>
<cfquery name="qComputingAge" datasource="billing-vc" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select max(capacity_resource.timestamp) as dt_load from ngcloud_ru.capacity_resource
</cfquery>
<cfoutput>qComputingAge:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<!--- переделать storage_profile_types с расшифровкой ssd, sata, можно прямо до каталожного кода --->
<cfquery name="qStorage" datasource="billing-vc" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select m.clientid as wz, vdc.name as vdc,
case
WHEN vdc.name LIKE '%-v1cl1%' THEN 'iaas.ngc.i29'
WHEN vdc.name LIKE '%-v1cl2%' THEN 'iaas.ngc.i31'
WHEN vdc.name LIKE '%-v1cl4%' THEN 'iaas.ngc.a28'
WHEN vdc.name LIKE '%-v1cl5%' THEN 'iaas.ngc.a45'
WHEN vdc.name LIKE '%-v1cl6%' THEN 'iaas.ngc.i28'
WHEN vdc.name LIKE '%-v1cl7%' THEN 'iaas.ngc.a40' -- *** смапить все кластера
ELSE '['||vdc.name||']' END
|| CASE
WHEN storage_profile_types.name ILIKE '%FASTSSD%' THEN '.fstssd'
WHEN storage_profile_types.name ILIKE '%SSD%' THEN '.ssd'
WHEN storage_profile_types.name ILIKE '%SAS%' THEN '.sas'
WHEN storage_profile_types.name ILIKE '%SATA%' THEN '.sata'
ELSE '.['|| storage_profile_types.name ||']' END
as code,
storage_profile_types.name as profile,
sum("limit")/1024./4./<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/> as gb_m_limit, -- 4 потому, что сбор раз в 15 минут
sum(used)/1024./4./<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/> as gb_m_used
from ngcloud_ru.capacity_storage
join ngcloud_ru.vdc on capacity_storage.vdc_id=vdc.id
join ngcloud_ru.tenant t on t.id=vdc.tenant_id
left outer join
(select tenant_id, ts, metadata_id,
ROW_NUMBER() OVER (PARTITION BY tenant_id ORDER BY ts desc, metadata_id desc) as rn
from ngcloud_ru.tenant_metadata
) tm on (t.id = tm.tenant_id AND tm.rn = 1)
left outer join ngcloud_ru.metadata m on (tm.metadata_id=m.id)
join ngcloud_ru.storage_profile_types on capacity_storage.storage_profile_types_id=storage_profile_types.id
where length(m.clientid) > 0
AND capacity_storage.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
AND capacity_storage.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dateAdd("d",1,dt_finish)#/>
group by m.clientid, vdc.name, storage_profile_types.name
order by m.clientid, vdc.name, storage_profile_types.name;
</cfquery>
<cfoutput>qStorage:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfif isDefined("DEBUG")>
<cfdump var=#qStorage#/>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
</cfif>
<cfquery name="qStorageAge" datasource="billing-vc" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select max(capacity_storage.timestamp) as dt_load from ngcloud_ru.capacity_storage
</cfquery>
<cfoutput>qStorageAge:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<!---
"placement" "size_gb" "get_10k" "put_10k" "bytes_sent_gb"
HOT_FREE_LIMIT 1 10 1 100
--->
<!--- надо дисконтировать не ГБ-часы а ГБ-месяцы --->
<cfquery name="qS3Vol" datasource="billing-s3" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select /* *** ниже дублирование кода агрегации - round ceil etc, обратить внимание при правке */
(sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>) as vol_B
,sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>/1024/1024/1024 as vol_GB
,case
when bucket_stat.placement_id = 1 then 'paas.s3.ceph'
when bucket_stat.placement_id = 2 then 'paas.s3.cphc'
else '['|| TO_CHAR(bucket_stat.placement_id,'FM9') ||']' end as code
,bucket_stat.placement_id
,'WZ'||lpad(bucket_stat.owner, 5, '0') as wz -- *** здесь не число, а строка, поэтому другая формула обратной сборки WZ
,bucket_info.name as bucket
from s3billing.bucket_stat
join s3billing.placement ON s3billing.placement.id=bucket_stat.placement_id
join s3billing.bucket_info ON bucket_info.id=bucket_stat.bucket_id
where bucket_stat.placement_id in (1,2)
AND bucket_stat.timestamp_addition >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
AND bucket_stat.timestamp_addition < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dateAdd("d",1,dt_finish)#/>
group by bucket_stat.owner, bucket_stat.placement_id, bucket_info.name
order by bucket_stat.owner, bucket_stat.placement_id, bucket_info.name;
</cfquery>
<cfoutput>qS3Vol:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfif isDefined("DEBUG")>
<cfdump var=#qS3Vol#/>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
</cfif>
<cfquery name="qS3VolAge" datasource="billing-s3" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select max(timestamp_addition) as dt_load from s3billing.bucket_stat
</cfquery>
<cfoutput>qS3VolAge:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfquery name="qS3OpsTrf" datasource="billing-s3">
select
sum(
CASE WHEN
(category.name LIKE 'get%'
OR category.name LIKE 'head%'
OR category.name LIKE 'options%'
) THEN ops ELSE 0 END
) as get
,ceil(sum(
CASE WHEN
(category.name LIKE 'get%'
OR category.name LIKE 'head%'
OR category.name LIKE 'options%'
) THEN ops ELSE 0 END
)/10000) as get_10k
,greatest(ceil(sum(
CASE WHEN
(category.name LIKE 'get%'
OR category.name LIKE 'head%'
OR category.name LIKE 'options%'
) THEN ops ELSE 0 END
)/10000) - CASE WHEN bucket_info.placement_id = 1 THEN 10 ELSE 0 END, 0
) as get_10k_free_tier_subtracted
,sum(
CASE WHEN
(category.name LIKE 'put%'
OR category.name LIKE 'post%'
OR category.name LIKE 'patch%'
OR category.name LIKE 'list%'
) THEN ops ELSE 0 END
) as put
,ceil(sum(
CASE WHEN
(category.name LIKE 'put%'
OR category.name LIKE 'post%'
OR category.name LIKE 'patch%'
OR category.name LIKE 'list%'
) THEN ops ELSE 0 END
)/10000) as put_10k
,greatest(ceil(sum(
CASE WHEN
(category.name LIKE 'put%'
OR category.name LIKE 'post%'
OR category.name LIKE 'patch%'
OR category.name LIKE 'list%'
) THEN ops ELSE 0 END
)/10000) - CASE WHEN bucket_info.placement_id = 1 THEN 1 ELSE 0 END, 0
) as put_10k_free_tier_subtracted
,(sum(bytes_sent)) as bytes_sent
,ceil(sum(bytes_sent)/1024/1024/1024) as bytes_sent_GB
,greatest(ceil(sum(bytes_sent)/1024/1024/1024) - CASE WHEN bucket_info.placement_id = 1 THEN 100 ELSE 0 END, 0
) as bytes_sent_GB_free_tier_subtracted
,case
when bucket_info.placement_id = 1 then 'paas.s3.ceph'
when bucket_info.placement_id = 2 then 'paas.s3.cphc'
else '['|| TO_CHAR(bucket_info.placement_id,'FM9') ||']' end as code --*** зачем делать там ключ bigint
,bucket_info.placement_id
--category.name
, bucket_info.name as bucket
,'WZ'||lpad(user_info.name, 5, '0') as wz
from s3billing.usage_bucket_by_user
join s3billing.user_info on usage_bucket_by_user.user_id=user_info.id
join s3billing.category on usage_bucket_by_user.category_id=category.id
join s3billing.bucket_info on usage_bucket_by_user.bucketid=bucket_info.id
join s3billing.placement on bucket_info.placement_id=placement.id
where 1=1
AND usage_bucket_by_user.time >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
AND usage_bucket_by_user.time < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dateAdd("d",1,dt_finish)#/>
group by user_info.name, bucket_info.placement_id, bucket_info.name
order by user_info.name, bucket_info.placement_id, bucket_info.name;
</cfquery>
<cfoutput>qS3OpsTrf:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfif isDefined("DEBUG")>
<cfdump var=#qS3OpsTrf#/>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
</cfif>
<cfquery name="qS3OpsTrfAge" datasource="billing-s3" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select max(time) as dt_load from s3billing.usage_bucket_by_user
</cfquery>
<cfoutput>qS3OpsTrfAge:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfquery name="qUnifiedMetric" dbType="query">
select wz, code || '.vcpu' as code, core_h as raw_metric, core_h as metric, 'core-h' as unit, vdc as dimension1
from qComputing
union all
select wz, code || '.ram', gb_h as raw_metric, gb_h as metric, 'GByte-h' as unit, vdc as dimension1
from qComputing
union all
select wz, code, gb_m_used as raw_metric, gb_m_used as metric, 'GByte-m' as unit, vdc ||':'|| profile as dimension1
from qStorage
union all
select wz, code || '.vol', vol_b as raw_metric, vol_gb as metric, 'GByte-m' as unit, bucket as dimension1
from qS3Vol
union all
select wz, code || '.get', get as raw_metric, get_10k as metric, '10k' as unit, bucket as dimension1
from qS3OpsTrf
union all
select wz, code || '.put', put as raw_metric, put_10k as metric, '10k' as unit, bucket as dimension1
from qS3OpsTrf
union all
select wz, code || '.trf', bytes_sent as raw_metric, bytes_sent_gb as metric, 'GByte' as unit, bucket as dimension1
from qS3OpsTrf
order by wz, code
</cfquery>
<!--- <cfdump var=#qSpec#/>
<cfdump var=#qUnifiedMetric#/> --->
<cfoutput>qUnifiedMetric:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfif isDefined("DEBUG")>
<cfdump var=#qUnifiedMetric#/>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
</cfif>
<cfset db = new MemoryDbHelper()/>
<cfset db.register("qUnifiedMetric", qUnifiedMetric, "inmem")/>
<cfset db.register("qContragent", qContragent, "inmem")/>
<cfset db.register("qSpec", qSpec, "inmem")/>
<cfquery name="qCharge" datasource="inmem">
SELECT
<d:field_set titleMapOut="titleMap" lengthOut="fieldCount">
<d:field title="WZ">coalesce(coalesce(c.wz, m.wz), s.wz) as wz</d:field>
<!--- <d:field title="WZ">f.wz as wzf</d:field> --->
<d:field title="WZ">s.wz as wzs</d:field>
<d:field title="WZ">c.wz as wzc</d:field>
<!--- <d:field title="WZ">cs.wz as wzcs</d:field> --->
<d:field title="WZ">m.wz as wzm</d:field>
<d:field title="WZE">s.end_customer_wz as wze</d:field>
<d:field title="Артикул">coalesce(s.code, m.code) as code</d:field>
<d:field title="Артикул по спец.">s.code as s_code</d:field>
<d:field title="SKU code">s.sku_code as sku_code</d:field>
<d:field title="Артикул по метрикам">m.code as m_code</d:field>
<d:field title="Кол-во компонента по спец.">s.component_quantity</d:field>
<d:field title="Кол-во сервиса по спец.">s.service_quantity</d:field>
<d:field title="Скидка на сервис">s.service_discount</d:field>
<d:field title="Free Tier">coalesce(s.free_tier,0) as free_tier</d:field>
<d:field>m.dimension1</d:field>
<d:field title="Тип по спец.">s.type</d:field>
<!--- <d:field title="Кол-во к оплате" cfSqlType="CF_SQL_NUMERIC">ceiling(m.chargeable_metric) as chargeable_metric</d:field> --->
<d:field title="Ед.изм.">m.unit</d:field>
<d:field title="Ед.изм.">s.ed_izm</d:field>
<d:field title="Цена &##8381; с НДС" cfSqlType="CF_SQL_NUMERIC">coalesce(s.service_quantity*(100.-s.service_discount)/100.*s.price*(100.-s.discount)/100., 0.) as discounted_price</d:field>
<!--- <d:field title="Стоимость &##8381; с НДС" cfSqlType="CF_SQL_NUMERIC">CASE WHEN s.type=3 THEN s.service_quantity*(100-s.service_discount)/100*s.price*(100-s.discount)/100*ceiling(m.chargeable_metric) ELSE NULL END as charge</d:field> --->
<d:field title="Дата НОУ" cfSqlType="CF_SQL_TIMESTAMP">s.serviceStart</d:field>
<d:field title="Дата оконч." cfSqlType="CF_SQL_TIMESTAMP">s.serviceEnd</d:field>
<d:field title="Ключ строки">s.line_key</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">m.raw_metric</d:field>
<d:field title="Приведенная метрика" cfSqlType="CF_SQL_NUMERIC">m.metric</d:field>
<d:field title="Контрагент">c.contragent</d:field>
<d:field title="contragent_uid">c.contragent_uid</d:field>
<d:field title="Договор">s.contract</d:field>
<d:field title="contract_uid">s.contract_uid</d:field>
<d:field title="Допник">s.additional_agreement</d:field>
<d:field title="additional_agreement_uid">s.additional_agreement_uid</d:field>
<d:field title="Сделка">s.deal</d:field>
<d:field title="deal_uid">s.deal_uid</d:field>
<d:field title="Абстрактная услуга">s.abstract_service</d:field>
<d:field title="Модификатор">s.modifier</d:field>
<d:field title="Компонент">s.component</d:field>
<d:field title="Клиентс. назв.">s.user_description</d:field>
</d:field_set>
FROM qUnifiedMetric m
FULL JOIN qSpec s ON (m.wz=s.wz AND m.code=s.sku_code) -- сервис или компонент, но не цена с типом тарификации
LEFT JOIN qContragent c ON (trim(coalesce(s.wz,m.wz))=trim(c.wz)) -- пробелы тут стоили нескольких часов работы
<!--- LEFT JOIN qContragent cs ON (s.wz=cs.wz) -- неуклюже, но иначе контрагент теряется --->
WHERE 1=1 --AND (s.type=3 OR NOT m.metric IS NULL)
<cfif only_articles_in_spec>
AND EXISTS(select * from qSpec qs where qs.wz=m.wz AND qs.type=3)
<!--- вообще-то есть смысл добавить и все строки, где что-то намеряли --->
</cfif>
--AND s.wz='WZ01031'
--AND s.wz='WZ01762'
<!---
AND 1=0
--->
ORDER BY coalesce(coalesce(c.wz, m.wz), s.wz), coalesce(s.code, m.code)
</cfquery>
<cfdump var=#qCharge#/>
<cfoutput>qCharge:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfif isDefined("DEBUG")>
<cfdump var=#qCharge#/>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
</cfif>
<cfif isDefined("output_xls")>
<layout:xml qRead=#qCharge# titleMap=#titleMap# filename="#pageInfo.entity#.xml"/>
<cfabort/>
</cfif><cfif isDefined("output_json")>
<layout:json qRead=#qCharge# titleMap=#titleMap# filename="#pageInfo.entity#.json"/>
<cfabort/>
</cfif><!---
---><layout:page section="header" pageInfo=#pageInfo#>
<layout:attribute name="title">
<cfoutput><b>Отчет по PAYG</b></cfoutput>
</layout:attribute>
<layout:attribute name="controls">
<!---skip filter link, filter is not implemented--->
<!---<layout:language_switch/>--->
</layout:attribute>
</layout:page>
Факт потребления по основной базе биллинга, спецификации из DWH. Без детализации по бакетам, VDC и т.д.
Все запросы кэшированы на 25 минут.<br/>
Внимание! округление вверх при расчете метрик get, put S3. Расчетные суммы могут содержать погрешности округления, поэтому окончательный расчет стоимости (количество * цену) должен выполняться в бухгалтерской программе.
<b>Если у клиента больше 1 действующего допника с услугами PAYG, то данные в отчете могут быть замножены, в этом случае считать вручную</b>
<br/>
Даты актуальности пока проверять вручную. Обращать внимание на замножение строк.
<cfoutput>
<form method="post" action="">
Период с <input type="text" name="dt_start" value="#dateFormat(dt_start,'YYYY-MM-DD')#"/>
по <input type="text" name="dt_finish" value="#dateFormat(dt_finish,'YYYY-MM-DD')#"/>
Только PAYG <input type="checkbox" name="only_articles_in_spec" value="1"<cfif only_articles_in_spec> checked</cfif>/>
<input type="submit" style="cursor:pointer;"/>
<input type="submit" name="DEBUG" value="DEBUG" style="cursor:pointer;"/>
</form>
Часов в периоде: <b>#hours#</b> Строк в отчете: <b>#qCharge.recordCount#</b><br/>
Актуальность данных:
VCD Computing: <b>#dateFormat(qComputingAge.dt_load,'YYYY-MM-DD')# #timeFormat(qComputingAge.dt_load,'HH:MM:SS')#</b>
VCD Storage: <b>#dateFormat(qStorageAge.dt_load,'YYYY-MM-DD')# #timeFormat(qStorageAge.dt_load,'HH:MM:SS')#</b>
S3 хранение: <b>#dateFormat(qS3VolAge.dt_load,'YYYY-MM-DD')# #timeFormat(qS3VolAge.dt_load,'HH:MM:SS')#</b>
S3 операции и трафик: <b>#dateFormat(qS3OpsTrfAge.dt_load,'YYYY-MM-DD')# #timeFormat(qS3OpsTrfAge.dt_load,'HH:MM:SS')#</b>
<br/>
<a href="#request.thisPage#?output_xls" title="экспорт в Excel" style="margin-left:.5em; height:100%;" target="_blank"><img src="img/xls.gif" style="vertical-align:text-bottom;"/></a>
<a href="#request.thisPage#?output_json" title="экспорт в json" style="margin-left:.5em; height:100%;" target="_blank"><img src="img/json.svg" style="vertical-align:text-bottom;" width="13" height="13"/></a>
</cfoutput>
<table class="worktable">
<thead>
<td width="1%"></td>
<!--- <th width="3%">WZ</th> --->
<th width="7%">Артикул специф.</th>
<th width="5%">Кол-во специф.</th>
<th width="5%">Артикул метр.</th>
<th width="5%">Детализация</th>
<th width="5%">Кол-во метр.</th>
<th width="5%">Free Tier.</th>
<th width="5%">Опл. объем</th>
<th width="3%">Ед.изм.</th>
<th width="3%">Цена &#8381; со скид., с НДС</th>
<th width="3%">Стоимость &#8381; с НДС</th>
<td width="3%">&nbsp;&nbsp;&nbsp;</td>
<td width="15%">Услуга</td>
<td width="15%">Компонент</td>
<td width="15%">Клиентс. назв.</td>
<td width="3%">GPL с НДС</td>
<td width="3%">Скидка%</td>
<td width="3%">Дата НОУ ***</td>
<td width="3%">Дата оконч.</td>
<td width="5%">Ключ строки</td>
<td width="3%">Сырая метрика</td>
<td width="3%">Приведен&shy;ная метрика</td>
<td width="3%">Цена &#8381; до ск. с НДС</td>
<td width="3%">Цена &#8381; co ск. с НДС без окр.</td>
<td width="3%">Service Quantity</td>
<td width="3%">Service Discount</td>
<td width="3%">Component Quantity</td>
<td width="3%">Component Discount</td>
<td width="5%">Договор</td>
<td width="10%">Допник</td>
<td width="10%">Сделка</td>
<td width="3%">Конечник</td>
</thead>
<cfoutput query="qCharge" group="wz">
<cfset var accum_cost_svc = 0/>
<cfset var accum_metric_svc = 0/>
<cfset var acc=0/>
<tr>
<td bgcolor="##ccc"></td>
<td bgcolor="##ccc" colspan="5" class="b">
<a href="contragent.cfm?__id=#contragent_uid#&#tr.fwx#">#wz#</a>
#contragent#
<a href="contragent_rpt.cfm?__id=#contragent_uid#&#tr.fwx#">(сводка)</a>
</td>
<td></td>
<td bgcolor="##ccc" colspan="99"></td>
</tr>
<cfoutput group="code">
<cfoutput group="serviceStart">
<cfset accum_metric_svc += isNumeric(metric) ? metric : 0/>
<cfset accum_metric_dimension_1 = 0/>
<cfoutput><!--- dimensions --->
<cfif type EQ 3>
<cfset accum_metric_dimension_1 += isNumeric(metric) ? metric : 0/>
</cfif>
<!--- детализация --->
<tr<cfif !len(unit)> class="gray"</cfif>>
<td>#currentRow#</a></td>
<td<cfif type EQ 3> style="background:lightgreen;color:black;"</cfif>>#s_code#</td>
<td class=<cfif type EQ 3 AND component_quantity NEQ 1>"err r"<cfelse>"r"</cfif>>#component_quantity#</td>
<td>#m_code#</td>
<td>#dimension1#</td>
<td class="r" style="font-size:110%; padding:0 1em;">#request.roundSafe(metric,3)#</td>
<td class="r">#free_tier#</td>
<td></td>
<td style="font-size:90%;">#unit# <cfif len(ed_izm)>[#ed_izm#]</cfif></td><!--- #len(unit)?unit:'['& ed_izm &']'# --->
<td class="r">#gaussianRound(discounted_price,2)#</td>
<td class="r"><!---#gaussianRoundSafe(charge,2)# <cfif type EQ 3></cfif> ---></td>
<td></td>
<td>#abstract_service# #modifier#</td>
<td>#component#</td>
<td>#user_description#</td>
<td class="r">#gaussianRoundSafe(price,2)#</td>
<td class="r">#discount#</td>
<td class="r">#dateFormat(serviceStart,'YYYY-MM-DD')#</td>
<td class="r">#dateFormat(serviceEnd,'YYYY-MM-DD')#</td>
<td class="r">#line_key#</td>
<td class="r" style="color:##bbb">#request.roundSafe(raw_metric,3)#</td>
<td class="r">#request.roundSafe(metric,3)#</td>
<!--- сырые исходные данные без округлений --->
<td class="r">#price#</td>
<td class="r">#discounted_price#</td>
<td class="r">#service_quantity#</td>
<td class="r">#service_discount#</td>
<td class="r">#component_quantity#</td>
<td class="r">#discount#</td>
<td><a href="contract.cfm?__id=#contract_uid#&#tr.fwx#">#contract#</a></td>
<td><a href="additional_agreement.cfm?__id=#additional_agreement_uid#&#tr.fwx#">#additional_agreement#</a></td>
<td><a href="deal.cfm?__id=#deal_uid#&#tr.fwx#">#deal#</a></td>
<td>#WZE#</a></td>
</tr>
</cfoutput>
<cfset final_metric_dimension_1 = ceiling(accum_metric_dimension_1)/>
<cfset cost_dimension_1 = precisionEvaluate("gaussianRound(max(final_metric_dimension_1 - free_tier, 0)*gaussianRound(discounted_price, 2), 2)")/>
<cfset accum_cost_svc = precisionEvaluate("accum_cost_svc + cost_dimension_1")/><!--- простое сложение дает ошибку округления и лишние знаки, но и precisionEvaluate не выглядит слишком надежным --->
<tr>
<td colspan="3"></td>
<td colspan="2" class="b">#code#</td>
<td class="b r" style="font-size:110%; padding:0 1em;"><cfif final_metric_dimension_1 NEQ 0 AND isNumeric(final_metric_dimension_1)>#round(final_metric_dimension_1,3)#</cfif></td>
<td class="b r">#free_tier#</td>
<td class="b r" style="font-size:120%; padding:0 1em;"><cfif final_metric_dimension_1 NEQ 0 AND isNumeric(final_metric_dimension_1)>#max(ceiling(final_metric_dimension_1) - free_tier, 0)#</cfif></td>
<td class="b c">#unit# <cfif len(ed_izm)>[#ed_izm#]</cfif></td>
<td class="b r">#gaussianRoundSafe(discounted_price,2)#</td>
<td class="b r">#gaussianRoundSafe(cost_dimension_1,2)#</td>
<td colspan="100"></td>
</tr>
</cfoutput><!--- serviceStart --->
</cfoutput><!--- code --->
<tr>
<td bgcolor="##eee" style="border-bottom:2px solid gray;"></td>
<td bgcolor="##eee" colspan="9" class="r" style="border-bottom:2px solid gray;"><b>Итого</b> <a href="contragent.cfm?__id=#contragent_uid#&#tr.fwx#">#wz#</a> #contragent#:</td>
<td bgcolor="##eee" colspan="1" class="r b nw" style="border-bottom:2px solid gray; font-size:130%; padding:0 1em;">#accum_cost_svc#</td>
<td colspan="99" style="border-bottom:2px solid gray;"></td>
</tr >
</cfoutput><!--- wz --->
</table>
<!--- <cfdump var=#qCharge#/> --->
<cfdump var=#this.datasources[request.ds].connectionString#/>
<cfdump var=#this.datasources["billing-vc"].connectionString#/>
<cfdump var=#this.datasources["billing-s3"].connectionString#/>
<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 t.id as tenant_id,
from ngcloud_ru.tenant t
left outer join
(select tenant_id, ts, metadata_id,
ROW_NUMBER() OVER (PARTITION BY tenant_id ORDER BY ts desc, metadata_id desc)
as rn
from ngcloud_ru.tenant_metadata) tm
on (t.id = tm.tenant_id AND tm.rn = 1)
left outer join ngcloud_ru.metadata m on (tm.metadata_id=m.id) --->
<cfscript>
string function gaussianRoundSafe(any num, numeric precision = 0, string default="") {
return (isNumeric(arguments.num)) ? gaussianRound(arguments.num, arguments.precision) : arguments.default;
}
function gaussianRound(numeric number, numeric precision = 0) {
var factor = 10 ^ precision;
var scaledNumber = number * factor;
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>