Files
payg/vm_gpu_rpt.cfm
T
2026-09-01 15:28:17 +03:00

406 lines
19 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="vm_gpu" accessObject="" pageInfoOut="pageInfo" trackOut="tr"/>
<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/> --->
<cfparam name="wz" default="WZ01516"/>
<cfparam name="code" 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>
<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)
)
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, 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
,s.probability_pc
,siv.__id::text as siv_uid
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))
--join elma.service_parametrs p on (p.__id = ANY(siv.params))
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)
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 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>
--AND k.id_klienta='WZ01031'
--AND k.id_klienta='WZ01112'
order by k.id_klienta, p.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' --->
<!--- <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> --->
<cfquery name="qGpu" datasource="billing-vc" cachedWithin=#createTimeSpan(0, 0, 25, 0)#>
select
p.ts::date,
count(distinct p.obj_uuid) as unique_vm_cnt,
count(distinct p.obj_uuid) FILTER (WHERE p.value=1) as unique_vm_cnt_on,
round((sum(p.value)/12.)::numeric,1) as vm_h, -- 5 minute sampling
round((sum(p.value)/12.*32.18)::numeric,2) as cost,
<!--- round((sum(p.value)/12.*32.18*(1-0.02*400/32.18))::numeric,2) as cost_wo_disk,
round((count(distinct obj_id)*0.02*400*24)::numeric,2) as disk_cost, -- считаем все ВМ, включавшиеся за данные сутки --->
round((sum(p.value)/12./24)::numeric, 1) as avg_roundclock_vm
from gpu.vm
JOIN gpu.vm_power_state p on (vm.obj_uuid=p.obj_uuid)
where 1=1
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 like <cfqueryparam cfsqltype="cf_sql_varchar" value="%#wz#%"/>
</cfif>
GROUP BY p.ts::date
ORDER BY 1
</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 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>
<!---
<cfset db = new MemoryDbHelper()/>
<cfset db.register("qUnifiedMetric", qUnifiedMetric, "inmem")/>
<cfset db.register("qContragent", qContragent, "inmem")/>
<cfset db.register("qSpec", qSpec, "inmem")/> --->
<!--- <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>/>
WZ <input type="text" name="wz" value="#wz#"/>по вхождению, с регистром
Код <input type="text" name="code" value="#code#"/>по вхождению
<input type="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>
</cfoutput>
<table class="worktable">
<thead>
<td width="1%"></td>
<!--- <th width="3%">WZ</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="3%">Ед.изм.</th>
<th width="3%">Цена &#8381; со скид., с НДС</th>
<th width="3%">Стоимость &#8381; с НДС</th>
<td width="3%">&nbsp;&nbsp;&nbsp;</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%">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="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 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>
<!--- детализация --->
<tr<cfif !len(unit)> class="gray"</cfif>>
<td>#currentRow#</a></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="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"></td>
<td></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">#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><!--- 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>
<td><a href="deal.cfm?__id=#deal_uid#&#tr.fwx#">#deal#</a></td>
<td>#WZE#</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)*current_price, 2)")/>
<cfset accum_cost_svc = precisionEvaluate("accum_cost_svc + cost_dimension_1")/><!--- простое сложение дает ошибку округления и лишние знаки, но и precisionEvaluate не выглядит слишком надежным --->
<tr>
<td colspan="4"></td>
<td colspan="2" class="b r">Итого #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">#current_price#</td>
<td class="b r">#gaussianRoundSafe(cost_dimension_1,2)#</td>
<td></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="10" 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; font-size:130%;">#accum_cost_svc#</td>
<td bgcolor="##eee" style="border-bottom:2px solid gray;"></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#/>
<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>