Files
2026-07-28 12:38:02 +03:00

319 lines
15 KiB
Plaintext
Raw Permalink 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=#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))/>
<cfset restrict_to_own_instances = true>
<cfparam name="WZ" default=#request.auth.wz#/>
<cfif request.auth.wz EQ 'WZ01112'><!--- грубо --->
<cfset restrict_to_own_instances = false>
<cfelse>
<cfset WZ=request.auth.wz/><!--- без баловства --->
</cfif>
<cfset tenant_wz_index=val(mid(WZ,3,5))/><!--- cut off WZ --->
<cfif isDefined("output_xls")><cfmodule template="mod/compute_payg.cfm"
dt_finish=#dt_finish#
dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index#
output="report"
debug=#isDefined("DEBUG")#
restrict_to_own_instances=#restrict_to_own_instances#
/>
<layout:xml qRead=#report.qCharge# titleMap=#report.chargeTitleMap# filename="#pageInfo.entity#.xml"/>
<cfabort/>
</cfif><cfif isDefined("output_json")><cfmodule template="mod/compute_payg.cfm"
dt_finish=#dt_finish#
dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index#
output="report"
debug=#isDefined("DEBUG")#
restrict_to_own_instances=#restrict_to_own_instances#
/><!--- <cfdump var=#report#/><cfabort/> ---><layout:json qRead=#report.qCharge# titleMap=#report.chargeTitleMap# filename="#pageInfo.entity#.json"/>
<cfabort/>
</cfif><!---
---><layout:page section="header" pageInfo=#pageInfo#>
<layout:attribute name="title">
<cfoutput><b>Отчет по PAYG</b> #WZ#</cfoutput>
</layout:attribute>
<layout:attribute name="controls">
<!---skip filter link, filter is not implemented--->
<!---<layout:language_switch/>--->
</layout:attribute>
</layout:page>
<!--- Внимание! округление вверх при расчете метрик get, put S3. Расчетные суммы могут содержать погрешности округления, поэтому окончательный расчет стоимости (количество * цену) выполняeтся в бухгалтерской программе.
<b>Если у клиента больше 1 действующего допника с услугами PAYG, то данные в отчете могут быть замножены, в этом случае считать вручную</b> --->
<cfoutput>
<form method="post" action="#request.thisPage#" style="display:inline"><!--- не надо делать action="" - параметр, затесавшийся в URL будет затирать параметр формы, и заметить это трудно --->
Период с <input type="text" name="dt_start" value="#dateFormat(dt_start,'YYYY-MM-DD')#"/>
по <input type="text" name="dt_finish" value="#dateFormat(dt_finish,'YYYY-MM-DD')#"/>
<cfif request.auth.wz EQ 'WZ01112'><!--- очень грубо --->
WZ <input type="text" name="wz" value="#WZ#"/>
</cfif>
<input type="submit" style="cursor:pointer;"/>
<input type="submit" name="DEBUG" value="DEBUG" style="cursor:pointer;"/><!--- ********* --->
</form>
<cfset month_start = createDate(year(now()), month(now()), 1)/>
<a href="?dt_start=#dateFormat(dateAdd('m', -1, month_start),'YYYY-MM-DD')#&dt_finish=#dateFormat(dateAdd('d', -1, month_start),'YYYY-MM-DD')#&wz=#WZ#">
прошлый месяц
</a>
<a href="?dt_start=#dateFormat(createDate(year(now()), month(now()), 1),'YYYY-MM-DD')#&dt_finish=#dateFormat(createDate(year(now()), month(now()), daysInMonth(now())),'YYYY-MM-DD')#&wz=#WZ#">
текущий месяц
</a>
<br/>
<!--- <cfflush/> --->
<cfmodule template="mod/compute_payg.cfm"
dt_finish=#dt_finish#
dt_start=#dt_start#
tenant_wz_index=#tenant_wz_index#
output="report"
debug=#structKeyExists(Form,"DEBUG")#
restrict_to_own_instances=#restrict_to_own_instances#
/>
Часов в периоде: <b>#hours#</b> Строк в отчете: <b>#report.qCharge.recordCount#</b><br/>
Актуальность данных:
VCD Computing: <b<cfif datediff("h",report.qComputingAge.dt_load, Now()) GT 24> class="err"</cfif>>
#dateFormat(report.qComputingAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qComputingAge.dt_load,'HH:MM:SS')#
</b>
VCD Storage: <b<cfif datediff("h",report.qStorageAge.dt_load, Now()) GT 24> class="err"</cfif>>
#dateFormat(report.qStorageAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qStorageAge.dt_load,'HH:MM:SS')#</b>
S3 хранение: <b<cfif datediff("h",report.qS3VolAge.dt_load, Now()) GT 24> class="err"</cfif>>
#dateFormat(report.qS3VolAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qS3VolAge.dt_load,'HH:MM:SS')#
</b>
S3 операции и трафик: <b<cfif datediff("h",report.qS3OpsTrfAge.dt_load, Now()) GT 24> class="err"</cfif>>
#dateFormat(report.qS3OpsTrfAge.dt_load,'YYYY-MM-DD')# #timeFormat(report.qS3OpsTrfAge.dt_load,'HH:MM:SS')#
</b>
<br/>
Цены по данным договора из CRM (через elma-cache). Учитываются метрики по S3, vCloud (кроме GPU). Внимание! количество за период *** округляется вверх, но делается это после всех агрегаций.
<br/>
<a href="#request.thisPage#?output_xls" title="экспорт в Excel" style="margin-left:.5em; height:100%;" target="_blank"><img src="img/xls.gif" style="vertical-align:text-bottom;"/></a>
<a href="#request.thisPage#?output_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>
<th width="1%"></th>
<th width="7%" colspan="2">Артикул</th>
<th width="3%">С</td>
<th width="3%">По</td>
<th width="3%">Кол-во (метрика)</td>
<th width="3%">Free Tier</td>
<th width="7%">Оплачиваемый объем</th>
<th width="3%">Ед.изм.</th>
<th width="3%">Цена &#8381; с НДС</th>
<th width="3%">Стоимость &#8381; с НДС</th>
<th width="10%">Услуга</th>
<th width="10%">Компонент</th>
<!--- <td width="10%">Клиентс. назв.</td> --->
<!--- <td width="3%">GPL с НДС</td>
<td width="3%">Скидка%</td> --->
<!--- <td width="6%">Дата НОУ</td>
<td width="6%">Дата оконч.</td>
<td width="3%">Ключ строки</td> --->
<!--- <td width="3%">Сырая метрика</td> --->
<!--- <td width="5%">Договор</td>
<td width="10%">Допник</td>
<td width="10%">Сделка</td> --->
</thead>
<!--- *** Обнаружена засада: возможно, группировка (сортировка) отображаемого вывода нужна по размерности детализации, а не по коду
Но: агрегация скорее нужна по коду
Видимо, придется на фронте перетасовывать?
--->
<!--- Еще засада: если я делегирую агрегацию на фронт, мне нужно и вычет free_tier делегировать на фронт (помня еще, что он на месяц, а не на произвольный период - наверно, можно его вычитать всегда, а больше месяца не агрегировать... или умножать на число полных месяцев) --->
<!--- видимо, Free Tier и единицу измерения нужно брать и для строк, которые не найдены в договоре
(еше можно было бы туда для полноты добавлять GPL цену, но для этого нужен каталог)
Добавить реквизиты договора? --->
<cfoutput query="report.qCharge" group="year">
<cfoutput group="month">
<tr><td colspan="99"><cfif month GT 0><b>#year#-#numberFormat(month,"00")#</b></cfif></td></tr>
<cfoutput group="svc">
<cfset var accum_cost_svc = 0/>
<cfset var accum_metric_svc = 0/>
<tr>
<td bgcolor="##ccc"></td>
<td bgcolor="##ccc" colspan="12">
#svc# <b>#service_code#</b>
<cfif len(serviceCustomName)>(#serviceCustomName#)</cfif>
<cfif serviceHash EQ "">(не найдено в договоре)</cfif>
</td>
</tr>
<cfoutput group="code">
<cfoutput group="serviceStart"> <!--- может быть, для ясности надо в каждой строке писать точный период --->
<cfset accum_metric_svc += isNumeric(metric) ? metric : 0/><!--- агрегация по метрике возможна только на уровне компонента --->
<tr>
<td></td>
<td colspan="10">#code# <cfif code EQ "">(#m_code#)</cfif></td>
<td>#svc#</td>
<td>#component# </td>
</tr>
<cfset accum_metric_dimension_1 = 0/>
<cfoutput group="dimension_1">
<cfset accum_metric_dimension_1 += isNumeric(metric) ? metric : 0/>
<tr class="i">
<td>#currentRow#</td>
<td>#dimension_1_name#</td>
<td>#dimension_1#</td>
<td class="c">#dateFormat(dt_billed_from,"YYYY-MM-DD")#</td>
<td class="c">#dateFormat(dt_billed_to,"YYYY-MM-DD")#</td>
<td class="r"><cfif metric NEQ 0 AND isNumeric(metric)>#round(metric,3)#</cfif></td>
<td><!--- free tier ---></td>
<td class="r" style="font-size:120%; padding:0 1em;"></td>
<td class="c">#unit#</td>
<td class="r">#gaussianRound(discounted_price,2)#</td>
<td class="r"><!--- здесь не вычисляется стоимость, потому что Free Tier вычитается после агрегации (скидочные политики реализовывать будет непросто) ---></td>
<td></td>
<td></td>
</tr>
</cfoutput>
<cfset cost_dimension_1 = precisionEvaluate("gaussianRound(max(accum_metric_dimension_1 - free_tier, 0)*gaussianRound(discounted_price, 2), 2)")/>
<tr class="b">
<td></td>
<td colspan="4">Итого #code# <cfif code EQ "">(#m_code#)</cfif>:</td>
<td class="r"><cfif accum_metric_dimension_1 NEQ 0 AND isNumeric(accum_metric_dimension_1)>#round(accum_metric_dimension_1,3)#</cfif></td>
<td class="r">#free_tier#</td>
<td class="r" style="font-size:120%; padding:0 1em;"><cfif accum_metric_dimension_1 NEQ 0 AND isNumeric(accum_metric_dimension_1)>#max(ceiling(accum_metric_dimension_1) - free_tier, 0)#</cfif></td>
<td class="c">#unit#</td>
<td class="r">#gaussianRound(discounted_price,2)#</td><!--- если цена менялась в периоде, неправильно показыввать последнюю - но мы не тестировали с изменением в периоде --->
<!--- непонятно, как вычитать Free Tier для неполных месяцев. Если с 15 по 15 - вычитать 2 раза, вычитать (каким-то сложным образом) 1 раз или ни разу --->
<!--- видимо, нужно сформировать тест кейсы на неполные месяцы. Где взять биллинговые данные для этого ВПР --->
<td class="r">#cost_dimension_1#</td>
<cfset accum_cost_svc = precisionEvaluate("accum_cost_svc + cost_dimension_1")/><!--- простое сложение дает ошибку округления и лишние знаки, но и precisionEvaluate не выглядит слишком надежным --->
<td>#svc# <!--- #service_code# ---></td>
<td>#component# </td>
</tr>
</cfoutput><!--- serviceStart --->
</cfoutput><!--- code --->
<tr>
<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> &nbsp;&nbsp;&nbsp;
<cfif len(service_code)>
#svc# <b>#service_code#</b>
<cfelse>
(#m_code#)
</cfif>
<cfif month GT 0>за #year#-#numberFormat(month,"00")#</cfif>:
</td>
<td bgcolor="##eee" class="r b" style="border-bottom:2px solid gray;">#accum_cost_svc#</td>
<td bgcolor="##eee" colspan="99" style="border-bottom:2px solid gray;"></td>
</tr >
</cfoutput>
<tr><td colspan="99">&nbsp;</td></tr>
</cfoutput>
</cfoutput>
</table>
<!--- <cfdump var=#report.qSpecification#/> --->
<h3>Спецификация</h3>
<table class="worktable">
<tr>
<th width="5%">Договор</th>
<th width="15%">Доп. соглашение</th>
<th width="3%">Тип оплаты</th>
<th width="20%">Пользовательское наименование</th>
<th width="3%">Кол-во по строке</th>
<th width="3%">Скидка по строке</th>
<th width="5%">НОУ</th>
<th width="5%">КОУ</th>
<th width="10%">Компонент</th>
<th width="5%">Артикул</th>
<th width="5%">Кол-во</th>
<th width="5%">Ед.изм.</th>
<th width="5%">Цена с НДС</th>
<th width="5%">Скидка %</th>
<th width="5%">Цена со скидкой с НДС</th>
</tr>
<cfoutput query="report.qSpecification">
<tr>
<td>#contractOutsideId#</td>
<td>#additionalAgreementName#</td>
<td class="c">#serviceType#</td>
<td>#serviceCustomName#</td>
<td class="c">#serviceQuantity#</td>
<td class="r">#serviceDiscount#</td>
<td class="c">#dateFormat(serviceStart, "YYYY-MM-DD")#</td>
<td class="c">#dateFormat(serviceEnd, "YYYY-MM-DD")#</td>
<td>#component#</td>
<td>#componentCode#</td>
<td class="r">#quantity#</td>
<td class="c">#measureShort#</td>
<td class="r">#gaussianRound(priceWVat,2)#</td>
<td class="r">#discount#</td>
<td class="r">#gaussianRound((100-serviceDiscount)/100*(100-discount)/100*priceWVat,4)#</td>
</tr>
</cfoutput>
</table>
<!---<cfdump var=#report.qCharge#/> --->
<!--- <cfdump var=#this.datasources[request.ds].connectionString#/> --->
<layout:page section="footer"/>
<!--- select * from s3billing.billing_per_user_fix_pl('2025-05-01 00:00:00','2025-06-01 00:00:00','1395'); --->
<cfscript>
function gaussianRound(numeric number, numeric precision = 0) {
var factor = 10 ^ precision;
var scaledNumber = number * factor;
var absScaled = abs(scaledNumber);
var sign = sgn(scaledNumber);
var floored = int(absScaled);
// If it's not ending in exactly .5, use standard rounding or precision scaling
if (absScaled - floored != 0.5) {
return round(number * factor) / factor;
}
// If it is exactly .5, round towards the nearest even number
if (floored % 2 != 0) {
// Nearest even is UP
return (floored + 1) * sign / factor;
} else {
// Nearest even is DOWN
return floored * sign / factor;
}
}
</cfscript>