Files
payg/payg.cfm
T

973 lines
50 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"/>
<!--- проблема: пустой чек-бокс не передается, невозможно отличить "чек-бокс очищен" от "параметров нет, использовать дефолт" (например, при обращении к странице по GET)
решение: если передается форма, то отсутствие чек-бокса однозначно означает, что он очищен.
Передачу формы можно надежно обозначить скрытым полем. Можно для экономии использовать имя кнопки, но кнопок у меня 2, сабмит и дебаг
NB при передаче формы GET структура FORM пустая
мне подсказывают, что удобно передавать qwery or filter гетом, тогда можно урлу сохранить --->
<cfparam name="submit" default=""/>
<cfparam name="debug" default=""/>
<cfif len(submit) or len(debug)>
<!--- считаем, что отправлена форма и отсутствие чекбокса нужно трактовать как FALSE --->
<cfparam name="only_articles_in_spec" default=0/>
<cfparam name="inner_join" default=0/>
<cfelse>
<!--- это обращение по неквалифицированному URL и отсутствие чекбокса означает USE DEFAULT --->
<cfparam name="only_articles_in_spec" default=1/>
<cfparam name="inner_join" default=1/>
</cfif>
<cfparam name="code" default=""/>
<!---
<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="wz" default=""/>
<!--- *** коды лучше взять из каталога --->
<cfquery name="qCode" datasource="dwh" cachedWithin=#createTimeSpan(0, 0, 20, 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, 22, 0)#>
select upper(trim(k.id_klienta)) as wz, k.__id::text as contragent_uid, k.__name as contragent,
c.__name as responsible, c.email as responsible_email, c.__status_status as responsible_status
from elma.companies k
LEFT OUTER JOIN elma.comp_users_ext c on (k.responsible=c.__id)
where k."__deletedAt" IS NULL
<cfif len(wz)>
AND k.id_klienta like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#wz#%"/>
</cfif>
</cfquery>
<cfoutput>qContragent:#getTickCount()-request.startTickCount#</cfoutput>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfquery name="qSpec" datasource="dwh" cachedWithin=#createTimeSpan(0, 0, 23, 0)#>
WITH
status(status, status_id, probability_pc)
AS (VALUES
('Закрыта неуспешно',7,0),('Закрыта успешно',6,100),('Договор подписан',5,100),('Договор на подписании',15,90),('Договор на согласовании',12,70),
('Тестирование',8,50),('Отправлено ТКП',10,20),('Внутреннее согласование',37,10),('Проработка решения',4,10),('Сбор потребностей',1,0)
),
line as (
select
upper(trim(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 as component_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,
d.__index AS deal_index,
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.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
,siv.end_customer::text as end_customer_uid
,z.id_klienta as end_customer_wz
,z.__name as end_customer_name
,s.probability_pc
,siv.__id::text as siv_uid
,CASE WHEN p.type=3 THEN coalesce(siv.cnt*(100.-siv.discount)/100.*p.price*(100.-p.discount)/100., 0.) ELSE NULL END as discounted_price
from elma.deals d
JOIN STATUS s ON (d.__status_status = s.status_id)
join elma.deals_services siv on (d.__id=ANY(siv.deal))
LEFT JOIN LATERAL unnest(siv.params) AS arr(param_id) ON TRUE
JOIN elma.service_parametrs p ON (p.__id = arr.param_id)
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)
WHERE p.type IN (2,3) AND aa.is_actual
AND aa."__deletedAt" IS NULL
AND a."__deletedAt" IS NULL
AND d."__deletedAt" IS NULL
AND siv."__deletedAt" IS NULL
AND p."__deletedAt" IS NULL
AND p.cnt <> 0 --похоже, только так можно отфильтровать паразитные строки с нулем
<cfif len(wz)>
AND k.id_klienta like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#wz#%"/>
</cfif>
<cfif len(code)>
AND p.code like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#code#%"/>
</cfif>
<cfif only_articles_in_spec>
AND p.type=3
</cfif>
),
stair
AS (
SELECT *
FROM (
SELECT *
,MIN(serviceStart) OVER (
PARTITION BY line_key ORDER BY deal_index DESC
) running_min_dt
,COALESCE((
LEAD(serviceStart) OVER (
PARTITION BY line_key ORDER BY serviceStart
,deal_index
)
), (
SELECT CURRENT_TIMESTAMP + INTERVAL '1 day' --*** not sure
)) dt_next
FROM line
WHERE probability_pc = 100
) t
WHERE t.serviceStart = t.running_min_dt
)
select d.* from stair d
WHERE d.serviceStart < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
AND (d.serviceEnd > <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/> OR d.serviceEnd IS NULL)
order by wz, type DESC, code
</cfquery>
<!--- select * from elma.deals_services where __id='019edf9c-c1e8-7acd-8b63-5369e5be5bf5'
union all
select * from elma.deals_services where __id='019edf9c-c23d-7ad6-a990-643dd09579b8' --->
<!--- здесь имеем список кодов с ценами и WZ. Каждому коду можно поставить в соответствие запрос, который даст по нему выборку. Изредка один запрос может вывести 2 кода, но разбить на 2 запроса легко. Кроме того, запросы можно объединить через union в один - может быть, так будет лучше читаться (быстрее выполняться не будет) --->
<cfoutput>qSpec:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfif len(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 upper(trim(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 JOIN LATERAL (
SELECT m.clientid
FROM ngcloud_ru.tenant_metadata tm
JOIN ngcloud_ru.metadata m ON tm.metadata_id = m.id
WHERE tm.tenant_id = t.id
ORDER BY tm.ts DESC, tm.metadata_id DESC
LIMIT 1
) m ON true
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)#/>
<cfif len(wz)>
AND m.clientid like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#wz#%"/>
</cfif>
group by m.clientid, vdc.name
order by m.clientid, vdc.name;
</cfquery>
<!--- recommended CREATE INDEX idx_tenant_metadata_tenant_ts_desc ON ngcloud_ru.tenant_metadata (tenant_id, ts DESC, metadata_id DESC); --->
<cfoutput>qComputing:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfif len(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, 27, 0)#>
select upper(trim(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 JOIN LATERAL (
SELECT m.clientid
FROM ngcloud_ru.tenant_metadata tm
JOIN ngcloud_ru.metadata m ON tm.metadata_id = m.id
WHERE tm.tenant_id = t.id
ORDER BY tm.ts DESC, tm.metadata_id DESC
LIMIT 1
) m ON true
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)#/>
<cfif len(wz)>
AND m.clientid like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#wz#%"/>
</cfif>
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 len(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, 27, 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, 29, 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_stat.owner as s3_user_name
,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)#/>
<cfif len(wz)><!--- *** --->
AND 'WZ'||lpad(bucket_stat.owner, 5, '0') like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#wz#%"/> /*выглядит очень неэффективным запросом*/
</cfif>
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 len(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, 29, 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" cachedWithin=#createTimeSpan(0, 0, 30, 0)#>
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
,(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
,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
,(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
,(sum(bytes_sent)) as bytes_sent
,(sum(bytes_sent)/1024/1024/1024) as bytes_sent_GB
,case
when bucket_info.placement_id = 1 then 'paas.s3.ceph'
when bucket_info.placement_id = 2 then 'paas.s3.cphc'
else '['|| TO_CHAR(bucket_info.placement_id,'FM9') ||']' end as code --*** зачем делать там ключ bigint
,bucket_info.placement_id
--category.name
,bucket_info.name as bucket
,user_info.name as s3_user_name
--,'dummy' 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)#/>
<cfif len(wz)>
AND 'WZ'||lpad(user_info.name, 5, '0') like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#wz#%"/> /*выглядит очень неэффективным запросом*/
</cfif>
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 len(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, 30, 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>
<!--- /*прибегаем к cmdb для разрешения инстанса, нейминг некорректен и ненадежен*/ --->
<cfquery name="qS3User" datasource="cmdb" cachedWithin=#createTimeSpan(0, 0, 31, 0)#>
WITH current_state AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY instance_uid ORDER BY version DESC) as rn
FROM instance_state
)
SELECT s.instance_data->'job'->>'contragentCode' as wz, s.instance_data->'job'->>'userId' AS user_id, e.display_name
FROM current_state s
JOIN instance e on (s.instance_uid=e.instance_uid)
WHERE rn = 1 AND e.service_id=12
AND NOT cast(s.instance_data->>'isDeleted' as boolean)
</cfquery>
<cfoutput>qS3User:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfif len(debug)>
<cfdump var=#qS3User#/>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
</cfif>
<!--- выданы права на схему и таблицы, чтобы без толку не плодить источники данных --->
<!--- считаются часы работы ВМ с GPU --->
<cfquery name="qGpu" datasource="billing-vc" cachedWithin=#createTimeSpan(0, 0, 31, 0)#>
WITH card (model, cluster, code) as (VALUES
('A100', 'msk1-pub02-i31', 'iaas.ngc.i31.gpu80'),
('H100', 'msk1-pub07-a40', 'iaas.ngc.a40.gpuh100'), -- h200 нет в отчете
('RTX 4090', 'msk1-pcs05-a45', 'iaas.ngc.a45.gpu4090'), -- нет такого в каталоге
('RTX 5090', 'msk1-pcs05-a45', 'iaas.ngc.a45.gpu5090'),
('RTX 6000', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu6k'),
('nvidia_a16-16q', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu16'),
('nvidia_a16-16q', 'msk1-pub01-i29', 'iaas.ngc.i29.gpu16'),
('nvidia_a16-1b', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu1'),
('nvidia_a16-2q', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu2'),
('nvidia_a16-4q', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu4'),
('nvidia_rtx_pro_6000_blackwell_dc-4q', 'msk1-pub07-a40', 'iaas.ngc.a40.gpu6k4')
)
SELECT item.key AS gpu_model, c.code
,SUM(item.value::int*p.value)/12. AS gpu_h -- 5 minute sampling
,vm.obj_id, upper(trim(vm.client_id)) as WZ
FROM gpu.vm
CROSS JOIN LATERAL jsonb_each(vm.gpu) AS item(key, value)
LEFT OUTER JOIN card c ON (item.key=c.model)
JOIN gpu.vm_power_state p ON (vm.obj_uuid=p.obj_uuid)
WHERE NOT vm.is_template
AND p.ts >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
AND p.ts < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dateAdd("d",1,dt_finish)#/>
<cfif len(wz)>
AND vm.client_id ilike <cfqueryparam cfsqltype="cf_sql_varchar" value="%#wz#%"/>
</cfif>
GROUP BY vm.client_id, item.key, c.code, vm.obj_id
ORDER BY vm.client_id, item.key, c.code, vm.obj_id;
</cfquery>
<cfoutput>qGpu:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfif len(debug)>
<cfdump var=#qGpu#/>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
</cfif>
<cfquery name="qGpuAge" datasource="billing-vc" cachedWithin=#createTimeSpan(0, 0, 31, 0)#>
select max(dt_load) as dt_load from gpu.vm
</cfquery>
<cfoutput>qGpuAge:#getTickCount()-request.startTickCount#</cfoutput><cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
<cfset db = new MemoryDbHelper()/>
<cfset db.register("qComputing", qComputing, "inmem")/>
<cfset db.register("qStorage", qStorage, "inmem")/>
<cfset db.register("qS3Vol", qS3Vol, "inmem")/>
<cfset db.register("qS3OpsTrf", qS3OpsTrf, "inmem")/>
<cfset db.register("qGpu", qGpu, "inmem")/>
<cfset db.register("qS3User", qS3User, "inmem")/>
<cfquery name="qUnifiedMetric" datasource="inmem">
select wz, code || '.vcpu' as code, core_h as raw_metric, core_h as metric, 'core-h' as unit, vdc as dimension1
from qComputing
<cfif len(code)>
where code || '.vcpu' like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#code#%"/>
</cfif>
union all
select wz, code || '.ram', gb_h as raw_metric, gb_h as metric, 'GByte-h' as unit, vdc as dimension1
from qComputing
<cfif len(code)>
where code || '.ram' like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#code#%"/>
</cfif>
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
<cfif len(code)>
where code like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#code#%"/>
</cfif>
union all
select wz, code || '.vol',vol_b as raw_metric, vol_gb as metric, 'GByte-m' as unit, bucket as dimension1
from qS3Vol s
left outer join qS3User u ON (s.s3_user_name = u.user_id)
<cfif len(code)>
where code || '.vol' like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#code#%"/>
</cfif>
union all
select wz, code || '.get', get as raw_metric, get_10k as metric, '10k' as unit, bucket as dimension1
from qS3OpsTrf s
left outer join qS3User u ON (s.s3_user_name = u.user_id)
<cfif len(code)>
where code || '.get' like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#code#%"/>
</cfif>
union all
select wz, code || '.put', put as raw_metric, put_10k as metric, '10k' as unit, bucket as dimension1
from qS3OpsTrf s
left outer join qS3User u ON (s.s3_user_name = u.user_id)
<cfif len(code)>
where code || '.put' like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#code#%"/>
</cfif>
union all
select wz, code || '.trf', bytes_sent as raw_metric, bytes_sent_gb as metric, 'GByte' as unit, bucket as dimension1
from qS3OpsTrf s
left outer join qS3User u ON (s.s3_user_name = u.user_id)
<cfif len(code)>
where code || '.trf' like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#code#%"/>
</cfif>
union all
select wz, code, gpu_h as raw_metric, gpu_h as metric, 'h' as unit, obj_id as dimension1
from qGpu
<cfif len(code)>
where code like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#code#%"/>
</cfif>
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 len(debug)>
<cfdump var=#qUnifiedMetric#/>
<cfif !isDefined("output_xls") AND !isDefined("output_json")><cfflush/></cfif>
</cfif>
<!--- загружаем рекордсеты в inmemory db, вместо чтобы мучиться с QoQ --->
<cfset db.register("qUnifiedMetric", qUnifiedMetric, "inmem")/>
<cfset db.register("qContragent", qContragent, "inmem")/>
<cfset db.register("qSpec", qSpec, "inmem")/>
<!--- Сопоставляем спецификации с метриками
В спецификации у нас есть контрагент (WZ) - это могут быть многие спецификации из многих договоров
проследить за версионностью спецификаций. в payg-report мы делаем отчет по дням, и для каждого дня проверяем актуальность спецификации для него
в метрике у нас только WZ контрагента (потому что нет объекта биллинга и даже контракта) и расшифрованный код без суффикса типа тарификации (который относится у нас не к продукту, а к цене)
FULL JOIN, потому что нам нужны бесхозные записи с той и другой стороны
При этом важно именно как мы обрабатываем бесхозные записи с каждой стороны
Они образуются за счет несовпадения кодов (нет в договоре - метрика есть, строчки нет. в том числе, если в договоре неправильный артикул)
Частичное совпадение кодов: есть фикс на тот же артикул. Показываем, но учитываем с нулевой ценой (потому что цена на фикс на другие единицы измерения, на месяц)
Отдельный вопрос: как мы обрабатываем Fixed PAYG и что с ним придумать
Контрагента со спекой нельзя INNER JOIN - есть вариант WZ с метрикой, но без спеки. В порядке ошибки может быть метрика без спеки и контрагента (orphan WZ). Либо осознанно записали вместо WZ что-то по своему усмотрению, благо там текстовое поле
Но нас тут совершенно не интересуют контрагенты без спек и метрик
Группировка: по контрагенту,
--->
<!--- <cfquery name="qSpecTest" datasource="inmem">
select * from qSpec
</cfquery>
<cfdump var=#qSpecTest#/> --->
<cfquery name="qCharge" datasource="inmem">
/*EXPLAIN PLAN FOR*/
WITH
metric as (select count(*) as cnt, wz, code, dimension1, max(unit) as unit, max(raw_metric) as raw_metric, max(metric) as metric
from qUnifiedMetric
group by wz,code, dimension1
),
spec as (select count(*) as cnt, wz, sku_code,
max(end_customer_wz) as wze,
max(code) as code,
max(component_quantity) as component_quantity,
max(service_quantity) as service_quantity,
max(service_discount) as service_discount,
max(discounted_price) as discounted_price,
max(type) as type,
max(ed_izm) as ed_izm,
--max(coalesce(service_quantity*(100.-service_discount)/100.*price*(100.-component_discount)/100., 0.)) as discounted_price,
max(serviceStart) as serviceStart,
max(serviceEnd) as serviceEnd,
max(line_key) as line_key,
max(price) as price,
max(component_discount) as component_discount,
max(contract) as contract,
max(contract_uid) as contract_uid,
max(additional_agreement) as additional_agreement,
max(additional_agreement_uid) as additional_agreement_uid,
max(deal) as deal,
max(deal_uid) as deal_uid,
max(abstract_service) as abstract_service,
max(modifier) as modifier,
max(component) as component,
max(user_description) as user_description,
max(CASE type WHEN 1 THEN 'install' WHEN 2 THEN 'fix' WHEN 3 THEN 'payg' ELSE NULL END) as pricing_model
from qSpec
group by wz, sku_code
)
SELECT
<d:field_set titleMapOut="titleMap" lengthOut="fieldCount">
<d:field>s.cnt as occurrences_spec</d:field>
<d:field>m.cnt as occurrences_metric</d:field>
<d:field title="Точное соотв. специф. - метр.">CASE WHEN (m.cnt=1 AND s.cnt=1) THEN 1 ELSE 0 END as exact_match</d:field>
<d:field title="Диагностика">
CASE
WHEN (m.cnt=1 AND s.cnt=1) THEN 'EXACT MATCH'
WHEN (m.cnt>1 AND s.cnt>1) THEN 'CRITICAL: Many-to-Many Explosion (Duplicates on BOTH sides)'
WHEN (s.cnt>1) THEN 'DUP: Multiple entries in Spec'
WHEN (m.cnt>1) THEN 'DUP: Multiple entries in Metrics'
WHEN (coalesce(m.cnt,0)=0 AND s.cnt>0) THEN 'ORPHAN: Only in Spec'
WHEN (m.cnt>0 AND coalesce(s.cnt,0)=0) THEN 'ORPHAN: Only in Metrics'
ELSE 'Other Discrepancy'
END as diag
</d:field>
<d:field title="WZ">coalesce(coalesce(c.wz, m.wz), s.wz) as wz</d:field>
<d:field title="WZ spec">s.wz as wzs</d:field>
<d:field title="WZ contragent">c.wz as wzc</d:field>
<d:field title="WZ metric">m.wz as wzm</d:field>
<d:field title="WZ конечника">s.wze</d:field>
<d:field title="Артикул">coalesce(s.sku_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="Кол-во компонента по спец." cfSqlType="CF_SQL_NUMERIC">s.component_quantity</d:field>
<d:field title="Кол-во сервиса по спец." cfSqlType="CF_SQL_NUMERIC">s.service_quantity</d:field>
<!--- <d:field title="Скидка на сервис" cfSqlType="CF_SQL_NUMERIC">s.service_discount</d:field> --->
<d:field title="Тип по спец.">s.type</d:field>
<d:field title="Ед.изм.">s.ed_izm</d:field>
<d:field title="Цена &##8381; с НДС" cfSqlType="CF_SQL_NUMERIC">
CASE --inline implementation of gaussian round to 2 decimal places
-- Isolate the 3rd decimal place using MOD instead of %, hsql syntax
WHEN ABS(MOD(TRUNCATE(s.discounted_price * 1000, 0), 10)) = 5
AND ABS(MOD(TRUNCATE(s.discounted_price * 100, 0), 2)) = 0
-- If it's a perfect .xx5 tie and the 2nd digit is EVEN, shave off the 5
THEN TRUNCATE(s.discounted_price, 2)
-- In all other cases, standard mathematical rounding applies safely
ELSE ROUND(s.discounted_price, 2)
END as discounted_price
</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.component_discount</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 title="Pricing Model">CASE s.type WHEN 1 THEN 'install' WHEN 2 THEN 'fix' WHEN 3 THEN 'payg' ELSE NULL END as pricing_model</d:field>
<d:field title="Артикул по метрикам">m.code as m_code</d:field><!--- без суффикса --->
<d:field>m.dimension1</d:field>
<d:field title="Ед.изм.">m.unit</d:field>
<d:field title="Сырая метрика" cfSqlType="CF_SQL_NUMERIC">m.raw_metric</d:field>
<d:field title="Приведенная метрика" cfSqlType="CF_SQL_NUMERIC">round(m.metric,3) as metric</d:field>
<d:field title="Free Tier" cfSqlType="CF_SQL_NUMERIC">coalesce(ft.free_tier,0) as free_tier</d:field>
<d:field title="Контрагент">c.contragent</d:field>
<d:field title="contragent_uid">c.contragent_uid</d:field>
<d:field title="Ответственный">c.responsible</d:field>
<d:field title="Email отв-го">c.responsible_email</d:field>
<d:field title="Статус отв-го">c.responsible_status</d:field>
</d:field_set>
<!--- --,'***' as delim,s.* --->
FROM metric m
<cfif inner_join>
INNER
<cfelse>
FULL
</cfif>
JOIN spec s ON (m.wz=s.wz AND m.code=s.sku_code) -- сервис или компонент, но не цена с типом тарификации, суффикс типа тарификации отрезан
--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 (m.code=ft.code)
left outer join (values ('paas.s3.ceph.vol',1),('paas.s3.ceph.get',10),('paas.s3.ceph.put',1),('paas.s3.ceph.trf',100)) as ft(code,free_tier) ON (m.code=ft.code)
LEFT JOIN qContragent c ON ((coalesce(s.wz,m.wz))=(c.wz)) -- пробелы в составе WZ стоили нескольких часов работы
<!--- --(поскольку trim слева в джойне катастрофически замедляет работу, делаем чистку выше при формировании исходных резалтсетов) --->
WHERE 1=1
<cfif only_articles_in_spec>
AND EXISTS(select * from spec qs where qs.wz=m.wz AND qs.type=3)
<!--- вообще-то есть смысл добавить и все строки, где что-то намеряли --->
</cfif>
<cfif len(wz)>
AND (
m.wz like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#wz#%"/>
OR s.wz like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#wz#%"/>
OR c.wz like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#wz#%"/>
)
<!--- вообще-то есть смысл добавить и все строки, где что-то намеряли --->
</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 len(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="get" action="">
Период с <input type="text" name="dt_start" value="#dateFormat(dt_start,'YYYY-MM-DD')#"/>
по <input type="text" name="dt_finish" value="#dateFormat(dt_finish,'YYYY-MM-DD')#"/>
<input type="checkbox" name="only_articles_in_spec" id="only_articles_in_spec" value="1"<cfif only_articles_in_spec> checked</cfif>/><label for="only_articles_in_spec">Только PAYG </label>
<input type="checkbox" name="inner_join" id="inner_join" value="1"<cfif inner_join> checked</cfif>/><label for="inner_join">Только найденные в договоре</label>
&nbsp;&nbsp;&nbsp;
WZ <input type="text" name="wz" value="#wz#"/>по вхождению, с регистром
&nbsp;&nbsp;&nbsp;
Код <input type="text" name="code" value="#code#"/>по вхождению
&nbsp;&nbsp;&nbsp;
<input type="submit" name="submit" style="cursor:pointer;"/>
<input type="submit" name="debug" value="DEBUG" style="cursor:pointer;"/><!--- #dateFormat( input, "yyyy-mm-dd" )#T#timeFormat( input, "HH:mm:ss.lXX" )# --->
</form>
Часов в периоде: <b>#hours#</b> Строк в отчете: <b>#qCharge.recordCount#</b><br/>
Актуальность данных:
VCD Computing: <b<cfif datediff("h",qComputingAge.dt_load, Now()) GT 24> class="err"</cfif>>#dateFormat(qComputingAge.dt_load,'YYYY-MM-DD')# #timeFormat(qComputingAge.dt_load,'HH:mm:ss.lXX')#</b>
VCD Storage: <b<cfif datediff("h",qStorageAge.dt_load, Now()) GT 24> class="err"</cfif>>#dateFormat(qStorageAge.dt_load,'YYYY-MM-DD')# #timeFormat(qStorageAge.dt_load,'HH:mm:ss.lXX')#</b>
S3 хранение: <b<cfif datediff("h",qS3VolAge.dt_load, Now()) GT 24> class="err"</cfif>>#dateFormat(qS3VolAge.dt_load,'YYYY-MM-DD')# #timeFormat(qS3VolAge.dt_load,'HH:mm:ss.lXX')#</b>
S3 операции и трафик: <b<cfif datediff("h",qS3OpsTrfAge.dt_load, Now()) GT 24> class="err"</cfif>>#dateFormat(qS3OpsTrfAge.dt_load,'YYYY-MM-DD')# #timeFormat(qS3OpsTrfAge.dt_load,'HH:mm:ss.lXX')#</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> --->
<a href="#request.thisPage#?output_xls&submit=1&dt_start=#dateFormat(dt_start,'YYYY-MM-DD')#&dt_finish=#dateFormat(dt_finish,'YYYY-MM-DD')#&inner_join=#inner_join#&only_articles_in_spec=#only_articles_in_spec#&code=#code#&wz=#wz#" 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&submit=1&dt_start=#dateFormat(dt_start,'YYYY-MM-DD')#&dt_finish=#dateFormat(dt_finish,'YYYY-MM-DD')#&inner_join=#inner_join#&only_articles_in_spec=#only_articles_in_spec#&code=#code#&wz=#wz#" 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>
<i><b>(перед выгрузкой отчета на произвольную дату или с параметрами сначала просмотрите его в браузере)</b></i>
<br/>
<span class="gray">серая строка</span> - нет метрик; <span class="err">красное</span> число в поле "Кол-во специф." - для PAYG в спецификации кол-во не 1
</cfoutput>
<table class="worktable">
<thead>
<td width="1%"></td>
<th width="3%">Match</th>
<th width="7%">Услуга</th>
<th width="7%">Артикул специф.</th>
<th width="5%">Кол-во специф.</th>
<th width="5%">Артикул метр.</th>
<th width="10%">Детализация</th>
<th width="5%">Кол-во метр.</th>
<th width="5%">Free Tier.</th>
<th width="5%">Опл. объем</th>
<th width="2%">Ед.изм.</th>
<th width="3%">Цена &#8381; со скид., с НДС</th>
<th width="3%">Стоимость &#8381; с НДС</th>
<td width="10%">Сделка</td>
<td width="3%">Конечник</td>
<td width="10%">Компонент</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%">Кол-во сервиса</td> --->
<!--- <td width="3%">Скидка на сервис</td> --->
<!--- <td width="3%">Кол-во комп-та</td> --->
<!--- <td width="3%">Скидка на комп-т %</td> --->
<!--- <td width="5%">Договор</td>
<td width="10%">Допник</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="99">
<b>
<a href="contragent.cfm?__id=#contragent_uid#&#tr.fwx#">#wz#</a>
#contragent#
<a href="contragent_rpt.cfm?__id=#contragent_uid#&#tr.fwx#">(сводка)</a>
</b>
ответственный:
<span <cfif responsible_status NEQ 2> class="err"</cfif>>#responsible# <a href="mailto:#responsible_email#">#responsible_email#</a></span>
</td>
</tr>
<cfoutput group="code">
<cfoutput group="serviceStart">
<cfset accum_metric_svc += isNumeric(metric) ? metric : 0/>
<cfset accum_metric_dimension_1 = 0/>
<cfset accum_metric_dimension_1_all = 0/><!--- не различая PAYG/FIX --->
<cfset current_price=0/>
<cfoutput><!--- dimensions --->
<cfif type EQ 3>
<cfset accum_metric_dimension_1 += isNumeric(metric) ? metric : 0/>
<cfset current_price=gaussianRound(discounted_price, 2)/>
</cfif>
<cfset accum_metric_dimension_1_all += isNumeric(metric) ? metric : 0/>
<!--- детализация --->
<tr<cfif !len(unit)> class="gray"</cfif>>
<td>#currentRow#</a></td>
<td<cfif exact_match GT 0> style="color:green"<cfelse> style="color:red"</cfif>>
#occurrences_spec#|#occurrences_metric#
#diag#
</td>
<td>#abstract_service#<cfif len(modifier)>: #modifier#</cfif></a></td>
<td><div<cfif type EQ 3> style="background:lightgreen;color:black;"</cfif>>#s_code#</div></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="padding:0 1em;">#metric#</td>
<td class="r"><!--- #free_tier# ---></td>
<td></td>
<td class="c" style="font-size:80%;">#unit# <cfif len(ed_izm)>[#ed_izm#]</cfif></td><!--- #len(unit)?unit:'['& ed_izm &']'# --->
<td class="r">#discounted_price#</td>
<td class="r"></td>
<td><a href="deal.cfm?__id=#deal_uid#&#tr.fwx#">#deal#</a></td>
<td><a href="contragent.cfm?id_klienta=#WZE#&#tr.fwx#">#WZE#</a></td>
<td>#component#</td>
<td>#user_description#</td>
<!--- <td class="r">#gaussianRoundSafe(price,2)#</td>
<td class="r">#component_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">#raw_metric#</td>
<td class="r">#metric#</td>
<!--- сырые исходные данные без округлений --->
<!--- <td class="r">#price#</td> --->
<!--- <td class="r">#discounted_price#</td> ---><!--- style="word-wrap:break-word" --->
<!--- <td class="r">#service_quantity#</td> --->
<!--- <td class="r">#service_discount#</td> --->
<!--- <td class="r">#component_quantity#</td> --->
<!--- <td class="r">#component_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> --->
</tr>
</cfoutput><!--- /dimensions --->
<cfset final_metric_dimension_1 = ceiling(accum_metric_dimension_1)/>
<cfset final_metric_dimension_1_all = ceiling(accum_metric_dimension_1_all)/>
<cfset cost_dimension_1 = precisionEvaluate("gaussianRound(max(final_metric_dimension_1 - free_tier, 0)*current_price, 2)")/>
<cfset accum_cost_svc = precisionEvaluate("accum_cost_svc + cost_dimension_1")/><!--- простое сложение дает ошибку округления и лишние знаки, но и precisionEvaluate не выглядит слишком надежным --->
<tr>
<td colspan="5"></td>
<td colspan="2" class="b r">Итого #code#:</td>
<td class="b r" style="padding:0 1em;"><!--- #accum_metric_dimension_1# ---><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="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">#current_price#</td>
<td class="b r">#gaussianRoundSafe(cost_dimension_1,2)#</td>
<td colspan="7"></td>
<td class="b r" title="сумма корректна, если нет замножения строк из-за наличия PAYG и FIX по одному артикулу"><!--- #request.roundSafe(accum_metric_dimension_1_all,3)# --->#final_metric_dimension_1_all#</td>
</tr>
</cfoutput><!--- serviceStart --->
</cfoutput><!--- code --->
<tr>
<td bgcolor="##eee" style="border-bottom:2px solid gray;"></td>
<td bgcolor="##eee" colspan="11" class="r b" style="border-bottom:2px solid gray;">Итого <a href="contragent.cfm?__id=#contragent_uid#&#tr.fwx#">#wz#</a> #contragent#:</td>
<td bgcolor="##eee" class="r b nw" style="border-bottom:2px solid gray;">#accum_cost_svc#</td>
<td bgcolor="##eee" 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#/>
<cfdump var=#this.datasources["cmdb"].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>