spec/price_ls.cfm
2025-06-02 16:16:51 +03:00

367 lines
23 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:silent silent="No">
<m:prepare_ls entity="service_price" pageInfoOut="pageInfo" trackOut="tr"/>
<m:filter_settings target="#pageInfo.entity#_ls">
<m:filterparam filter=#filter# param="quickfilter" ftype="string" prefix="%" suffix="%" expression="((p.param_class_type like ?) OR (p.division like ?) )" default=""/>
</m:filter_settings>
<cfset pageInfo.settings.filter=#filter#/>
<!---<cftry>--->
<cfquery name="qRead" datasource="#request.DS#">
select
<d:field_set titleMapOut="qReadTitleMap" lengthOut="qReadFieldCount">
<d:field title="service_price_id" cfSqlType="CF_SQL_VARCHAR">prc.service_price_id</d:field>
<d:field title="service_param_price_id" cfSqlType="CF_SQL_VARCHAR">prc.service_param_price_id</d:field>
<d:field title="Модель цены">prm.pricing_model</d:field>
<d:field title="Модель цены">prm.pricing_model_short</d:field>
<d:field title="Модель цены">prm.pricing_model_code</d:field>
<d:field title="Период цены">prc.pricing_period</d:field>
<d:field title="Период опроса">prc.rating_period</d:field>
<d:field title="НДС%">s.vat_perc</d:field>
<d:field title="НДС не обл.">s.vat_free</d:field>
<d:field>case when s.vat_free then 0. else vat_perc/100. end as vat_rate</d:field>
<d:field title=#i18("GPL без НДС, ₽","GPL w/VAT, ₽")# cfSqlType="CF_SQL_DECIMAL">prc.price</d:field>
<d:field title=#i18("Цена со ск. без НДС, ₽","Price disc. w/VAT, ₽")# cfSqlType="CF_SQL_DECIMAL">case when price*.95 > min_price then price*.95 else min_price end as discount_price</d:field>/***/
<d:field title="Дно" cfSqlType="CF_SQL_DECIMAL">min_price</d:field>
<d:field title="Статус цены" cfSqlType="CF_SQL_DECIMAL">prc.status as price_status</d:field>
<d:field title="Начальная дата действия цены" cfSqlType="CF_SQL_TIMESTAMP">prc.dt_from</d:field>
<d:field title="Конечная дата действия цены" cfSqlType="CF_SQL_TIMESTAMP">prc.dt_to</d:field>
<d:field title=#i18("Макс. скидка, %","Max discount %")# cfSqlType="CF_SQL_DECIMAL">case when prc.price >0 then (prc.price-prc.min_price)/prc.price else NULL end as max_discount</d:field>
<d:field title="abstract_service_id" cfSqlType="CF_SQL_INTEGER">a.abstract_service_id</d:field>
<d:field title="service_id" cfSqlType="CF_SQL_INTEGER">s.service_id</d:field>
<d:field title=#i18("Код группы","Group Code")#>g.area_code</d:field>
<d:field title="Группа">g.area</d:field>
<d:field title="Группа">g.analytic_code</d:field>
<d:field title="Catalog Group">g.area_en</d:field>
<d:field title="Статус">u.status</d:field>
<d:field title="Status">u.status_en</d:field>
<d:field>g.area_code||'.'||a.code||'.'||m.code||'.'||p.code as code</d:field>
<d:field>a.code as abstract_service_code</d:field>
<d:field>m.code as modifier_code</d:field>
<d:field>p.code as param_code</d:field>
<d:field title="ID статуса абстрактной услуги">a.status_id</d:field>
<d:field title="Абстрактная услуга">a.abstract_service</d:field>
<d:field title="Abstract Service">a.abstract_service_en</d:field>
<d:field title="modifier_id" cfSqlType="CF_SQL_INTEGER">m.modifier_id</d:field>
<d:field title="Класс модификатора">mc.modifier_class</d:field>
<d:field title="Модификатор">m.modifier</d:field>
<d:field title="Modifier class">mc.modifier_class_en</d:field>
<d:field title="Modifier">m.modifier_en</d:field>
<d:field title="service_param_id" cfSqlType="CF_SQL_INTEGER">sp.service_param_id</d:field>
<d:field title="param_class_id" cfSqlType="CF_SQL_INTEGER">pc.param_class_id</d:field>
<d:field title="param_class">pc.param_class</d:field>
<d:field title="Сортировка компонента">ac.sort as param_class_sort</d:field><!--- порядок сортировки в списке компонентов, например CPU-RAM-HDD-GPU --->
<d:field title="Сортировка варианта компонента">p.sort as param_sort</d:field><!--- порядок сортировки в списке вариантов компонентов, например SATA-SAS-SSD --->
<d:field title="Компонент">p.param</d:field>
<d:field title="Resource">p.param_en</d:field>
<d:field title="param_id" cfSqlType="CF_SQL_INTEGER">p.param_id</d:field>
<d:field title="Ед.изм.">case when p.param_id IS NULL then se.measure_short else e.measure_short end as measure_short</d:field>
<d:field title="Measure">case when p.param_id IS NULL then se.measure_short_en else e.measure_short_en end as measure_short_en</d:field>
</d:field_set>
from (
select
t.service_param_price_id, t.price, t.min_price, t.service_param_id, t.pricing_model_id
, null as service_price_id, sp.service_id, t.pricing_period, t.rating_period
,t.status, t.dt_from, t.dt_to
from service_param_price t join service_param sp on (t.service_param_id=sp.service_param_id)
union all
select
null, st.price, st.min_price, null, st.pricing_model_id, st.service_price_id, st.service_id, st.pricing_period, st.rating_period
,st.status, st.dt_from, st.dt_to
from service_price st
) prc
left outer join service s on (prc.service_id=s.service_id)
left outer join service_param sp on (prc.service_param_id=sp.service_param_id)
left outer join abstract_service a on (s.abstract_service_id=a.abstract_service_id)
left outer join modifier m on (s.modifier_id=m.modifier_id)
left outer join modifier_class mc on (m.modifier_class_id=mc.modifier_class_id)
left outer join abstract_service_param_class ac on (sp.abstract_service_param_class_id=ac.abstract_service_param_class_id)
left outer join param_class pc on (ac.param_class_id=pc.param_class_id)
left outer join param p on (sp.param_id=p.param_id)
left outer join measure e on (p.measure_id=e.measure_id)
left outer join measure se on (s.measure_id=se.measure_id)
left outer join area g on (a.area_id=g.area_id)
left outer join status u on (a.status_id=u.status_id)
left outer join pricing_model prm on (prc.pricing_model_id=prm.pricing_model_id)
where 1=1 <m:filter_build filter=#pageInfo.settings.filter#/>
order by <m:order_build sortArray=#pageInfo.settings.sort.sortArray# fieldCount=#qReadFieldCount#/>
</cfquery>
<!---
<cfcatch type="database">
<m:ls_catch catch=#cfcatch# status=#pageInfo.status#/>
</cfcatch>
</cftry>--->
<!--- <cfdump var=#qRead#/> --->
<cfquery name="qCountTotal" datasource="#request.DS#">
select (select count(*) from service_param_price) + (select count(*) from service_price) as cnt;
</cfquery>
</m:silent><!---
-----------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------
---><cfif isDefined("output_xls")>
<layout:xml qRead=#qRead# titleMap=#qReadTitleMap# filename="#pageInfo.entity#.xml"/>
<cfabort/><!--- *** nonstandard name for titleMap --->
</cfif><cfif isDefined("output_json")><!--- *** incorrect dt_from, dt_to format --->
<layout:json qRead=#qRead# titleMap=#qReadTitleMap# filename="#pageInfo.entity#.json"/>
<cfabort/><!--- *** nonstandard name for titleMap --->
</cfif><!---
---><layout:page section="header" pageInfo=#pageInfo#>
<layout:attribute name="title">
<cfoutput><b>#i18("Прайс-лист базовый","General Price List")#</b></cfoutput>
</layout:attribute>
</layout:page>
<cfif pageInfo.readPermitted() AND !pageInfo.status.errorState>
<layout:grid_summary
recordCount=#qRead.recordCount#
totalCount=#qCountTotal.cnt#
footerOut="gridFooter"
excelLink="YES"<!--- #pageInfo.writePermitted()# --->
jsonLink="YES"<!--- #pageInfo.writePermitted()# --->
/>
<!---<cfdump var=#qRead#/>--->
<cffunction name="formatPrice" output="true">
<cfargument name="price"/>
<cfif isNumeric(price)><cfreturn numberFormat(price,'.00')/><cfelse><cfreturn "(по запросу)"></cfif>
</cffunction>
<cfset request.formatPrice=formatPrice/>
<!--- add formatted fields (запись форматированного значения в поле на место исходного выглядит неопрятно) --->
<!--- можно заметить, что если многие поля оформлены ссылками, то форматировать много, и выглядит это громоздко --->
<cfset queryAddColumn(qRead,'f_link_view_edit')/>
<!--- <cfset qReadTitleMap.f_link_view_edit={ordinal=#StructCount(qReadTitleMap)+1#}/> --->
<cfset qReadTitleMap.f_link_view_edit={ordinal=0}/><!--- сортировки по этой колонке нет, ее порядок в исходном резалтсете не нужен, как и заголовок --->
<cfset queryAddColumn(qRead,'f_link_del')/>
<cfset qReadTitleMap.f_link_del={ordinal=0}/><!--- сортировки по этой колонке нет, ее порядок в исходном резалтсете не нужен, как и заголовок --->
<cfset queryAddColumn(qRead,'f_service')/>
<!--- <cfset qReadTitleMap.f_service={ordinal=#qReadTitleMap.abstract_service.ordinal#}/> --->
<cfset qReadTitleMap.f_service=qReadTitleMap.abstract_service/>
<cfset qReadTitleMap.f_service.title="Вариант услуги"/>
<!--- "кодовый" стиль, плохо читается. А все ради лаконичного формата c:table (который скорее тривиален) --->
<cfset queryAddColumn(qRead,'f_area_code')/>
<cfset qReadTitleMap.f_area_code=qReadTitleMap.analytic_code/><!--- просто копируем метаданные колонки аналитического кода --->
<cfset queryAddColumn(qRead,'f_component')/>
<cfset qReadTitleMap.f_component=qReadTitleMap.param_class/>
<cfset qReadTitleMap.f_component.title="Компонент"/>
<cfset queryAddColumn(qRead,'f_pricing_model')/>
<cfset qReadTitleMap.f_pricing_model={ordinal=qReadTitleMap.pricing_model.ordinal,title="Модель цены"}/>
<!--- *** Неудобно высчитывать ordinal, надо привязаться к существующей колонке
*** Хочется иметь возможность сортировки по нескольким колонкам, раз уж нереально по синтетической - поскольку мы вынесли форматирование из БД --->
<cfset queryAddColumn(qRead,'f_code')/>
<cfset qReadTitleMap.f_code={ordinal=qReadTitleMap.area_code.ordinal,title="Код"}/>
<cfset queryAddColumn(qRead,'f_price')/>
<cfset qReadTitleMap.f_price=qReadTitleMap.price/>
<cfset queryAddColumn(qRead,'f_min_price')/>
<cfset qReadTitleMap.f_min_price=qReadTitleMap.min_price/>
<cfset queryAddColumn(qRead,'f_discount_price')/>
<cfset qReadTitleMap.f_discount_price=qReadTitleMap.discount_price/>
<cfset queryAddColumn(qRead,'f_max_discount')/>
<cfset qReadTitleMap.f_max_discount=qReadTitleMap.max_discount/>
<cfset queryAddColumn(qRead,'f_area')/>
<cfset qReadTitleMap.f_area=qReadTitleMap.analytic_code/>
<cfset queryAddColumn(qRead,'f_param_class_sort')/>
<cfset qReadTitleMap.f_param_class_sort=qReadTitleMap.param_class_sort/>
<cfset queryAddColumn(qRead,'f_param_sort')/>
<cfset qReadTitleMap.f_param_sort=qReadTitleMap.param_sort/>
<cfset queryAddColumn(qRead,'f_vat_free')/>
<cfset qReadTitleMap.f_vat_free=qReadTitleMap.vat_free/>
<!--- preformat resultset fields --->
<cfoutput query=#qRead# startRow=#pageInfo.nStart# maxRows=#pageInfo.recordsPerPage#>
<cfsavecontent variable="qRead.f_link_view_edit">
<cfif len(service_param_price_id)>
<a href="service_param_price.cfm?service_param_price_id=#service_param_price_id#&#tr.fwx#" name="#service_param_price_id#"
<cfif pageInfo.writePermitted()>title="редактировать"
<cfelse>title="просмотр"</cfif>><img src="img/piece.png" width="13" height="13"/></a>
<cfelse>
<a href="service_price.cfm?service_price_id=#service_price_id#&#tr.fwx#" name="#service_price_id#"
<cfif pageInfo.writePermitted()>title="редактировать"
<cfelse>title="просмотр"</cfif>><img src="img/svc.jpg" width="16" height="16"/></a>
</cfif>
</cfsavecontent>
<cfsavecontent variable="qRead.f_link_del">
<cfif len(service_param_price_id)>
<a href="service_param_price_del.cfm?service_param_price_id=#service_param_price_id#&#tr.fwx#" class="del"></a>
<cfelse>
<a href="service_price_del.cfm?service_price_id=#service_price_id#&#tr.fwx#" class="del"></a>
</cfif>
</cfsavecontent>
<cfsavecontent variable="qRead.f_area_code">
#analytic_code# #area_code#
</cfsavecontent>
<cfsavecontent variable="qRead.f_service">
<a href="service.cfm?service_id=#service_id#&#tr.fwx#">
#abstract_service#<!--- <cfif modifier_id GT 0>:</cfif> ---> #modifier#
</a>
</cfsavecontent>
<cfsavecontent variable="qRead.f_component">
<a href="service_param.cfm?service_param_id=#service_param_id#&#tr.fwx#">
#param_class#
#param# <!--- *** может быть param_id для тривиального варианта - а как правильно? Разница с вариантом услуги - в механизме образования варианта, для услуги тривиальный вариант образуется null-модификатором, а для параметра - пустым названием (оба способа не слишком изящны, а наличие двух еще усложняет игру) --->
</a>
</cfsavecontent>
<cfsavecontent variable="qRead.f_pricing_model">
#pricing_model#
</cfsavecontent>
<cfsavecontent variable="qRead.f_code">
#request.skuCode(area_code,abstract_service_code,modifier_code,param_code,pricing_model_code)#
</cfsavecontent>
<cfsavecontent variable="qRead.f_area">
#analytic_code# #area#
</cfsavecontent>
<cfsavecontent variable="qRead.f_price">
<cfif isNumeric(price)>
<cfif len(service_price_id)>
<a href="service_price.cfm?service_price_id=#service_price_id#&#tr.fwx#">
#numberFormat(price,'.00')#
</a>
<cfelseif len(service_param_price_id)>
<a href="service_param_price.cfm?service_param_price_id=#service_param_price_id#&#tr.fwx#">
#numberFormat(price,'.00')#
</a>
</cfif>
<cfelse>
<i>по запросу</i>
</cfif>
</cfsavecontent>
<cfsavecontent variable="qRead.f_discount_price">
<cfif isNumeric(discount_price)>
#numberFormat(discount_price,'.00')#
<cfelse>
<i>по запросу</i>
</cfif>
</cfsavecontent>
<cfsavecontent variable="qRead.f_min_price">
<cfif isNumeric(min_price)>
#numberFormat(min_price,'.00')#
<cfelse>
<i>по запросу</i>
</cfif>
</cfsavecontent>
<cfsavecontent variable="qRead.f_max_discount">
#numberFormat(f_max_discount,'.0')#
</cfsavecontent>
<cfsavecontent variable="qRead.f_param_class_sort">
#f_param_class_sort#
</cfsavecontent>
<cfsavecontent variable="qRead.f_param_sort">
#f_param_sort#
</cfsavecontent>
<cfsavecontent variable="qRead.f_vat_free">
<cfif qRead.vat_free GT 0>Не облагается</cfif>
</cfsavecontent>
</cfoutput>
<!---build table markup --->
<c:table query=#qRead# recordsPerPage=#pageInfo.recordsPerPage# nStart=#pageInfo.nStart# titleMap=#qReadTitleMap# sortArray=#pageInfo.settings.sort.sortArray# class="worktable wide">
<c:column width="1%" sortable=false>
<c:th><c:link_add canWrite=#pageInfo.writePermitted()# entity=#pageInfo.entity# key="service_price_id" fwx=#tr.fwx#/></c:th>
<c:td field="f_link_view_edit" class="c"/>
</c:column>
<c:column width="7%" field="f_area"/>
<c:column width="7%" field="f_code"/>
<c:column width="3%" field="status"><c:td class="c"/><!--- иллюстрация передачи атрибута ---></c:column>
<c:column width="20%" field="f_service"/>
<c:column width="15%" field="f_component"/><!---columnOrder="3"--->
<c:column width="5%" field="f_pricing_model"><c:td class="l"/></c:column>
<c:column width="3%" field="pricing_period"><c:td class="c"/></c:column>
<c:column width="3%" field="rating_period"><c:td class="c"/></c:column>
<c:column width="1%" field="measure_short"/>
<c:column width="3%" field="f_price"><c:td class="r"/></c:column>
<!--- <c:column width="3%" field="f_max_discount"><c:td class="r"/></c:column> --->
<c:column width="3%" field="f_discount_price"><c:td class="r"/></c:column>
<c:column width="3%" field="f_min_price"><c:td class="r"/></c:column>
<c:column width="2%" field="f_vat_free"><c:td class="c"/></c:column>
<c:column width="2%" field="vat_perc"><c:td class="r"/></c:column>
<c:column width="1%" sortable=false>
<c:th><!--- &nbsp; ---><!---*** пришлось что-нибудь написать, иначе c:th ищет field.title и, не найдя, бросает исключение ---></c:th>
<c:td field="f_link_del" class="c"/>
</c:column>
</c:table>
<cfoutput>#gridFooter#</cfoutput>
</cfif>
<layout:page section="footer"/>
<!---*** критика синтаксиса: CALLER в данном контексте выглядит ужасно. Когда применять #service_id#, когда ##service_id##
Может быть, стоит отказаться от пользовательского тега и обратиться к более ровному способу форматирования.
Например (явно плохая идея, но для примера) печатать таблицу, а потом ее парсить и переформатировать... некоторые это могли сделать на клиенте.
На самом деле все, что нужно нам из скоупа CALLER, уже определено в момент вызова тега, и мы можем заменить CALLER на <cfoutput>. Это тоже неуклюже. То есть тащить весь CALLER внутрь как бы избыточно (не говоря о том, что можно словить каких-то сложносочиненных глюков)
Сортировка должна происходить на сервере БД, иначе паджинатор бесполезен. Вытаскивать резалтсет на сервер приложений и там его сортировать выглядит очень плохим решением (еще глупее сортировать на клиенте). Для корректной сортировки нужно делать композицию колонок на сервере, что не очень хорошо, например, с нуллами надо обращаться осторожно (вот аргумент в пользу NOT NULL), и синтаксис становится зависим от диалекта SQL. Возможный вариант, если у нас композиция из 2 колонок, сортировать по обеим. Либо отказаться от сортировки по композитной колонке, что кажется проще всего.
Контекст query выглядит естественно, но эта простота вводит в заблуждение - на вид обычный синтаксис, а функции недоступны (паковать в реквест еще то удовольствие), контекст страницы недоступен, ищи его через CALLER - догадаться невозможно.
Можно передавать внутрь тега все, что может понадобиться - функции, переменные?
Можно придумать собственный синтаксис, заменяющий cfif
--->
<!--- тонкости
в примере ниже
Атрибуты тега вычисляются до вызова, в контексте вызывающей страницы, а содержимое обрабатывается самим тегом в его контексте (собственно, мы сказали ему так делать). Но при чтении исходника это совсем не очевидно и может порвать мозг.
Можно, конечно, обрабатывать исключение и подставлять контекст вызывающей страницы? Как заставить тег видеть контекст вызывающей страницы? Попробовал взять весь тег в cfoutput. В результате перестал видеть поля query. Откатился.
Печаль в том, что мы в одном месте (чего и хотели - чтобы рядом) получаем контекст вызывающей страницы и контекст query, поля которого хотим называть неквалифицированными именами.
Вариант: действуем в 2 прохода, сначала формируем структуру, а потом рендерим ее. В принципе, с учетом пагинации, может потреблять памяти меньше, чем сам query
<c:th><c:link_add canWrite=#pageInfo.writePermitted()# entity=#pageInfo.entity# fwx=#tr.fwx#/><cfoutput>#pageInfo.entity#</cfoutput>#pageInfo.entity#</c:th> --->
<!--- Хотелось бы:
чтобы контекст страницы был явно доступен во всех вложенных тегах. >> The custom tag's Caller scope is a reference to the calling page's Variables scope (добавить все из CALLER в Variables? А не зациклимся?
чтобы итератор работал прозрачно, как в cfoutput query
Как обойтись без динамического исполнения? Для всех форматтеров снаружи объявлять closure? нечитабельно.
Хотя читабельность простыней ниже - так себе
В общем, получается пока плохо
? полиморфизм i18(measure_short) - как бы избежать отдельной далекой декларации длинного списка похожих функций. Можно полю придать признак языка? Соглашение с суффиксами? Это уже какая-то морфология получается!
--->