# # Copyright (c) Daniel Sheffield 2023 # All rights reserved # # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY from bottle import request, template, FormsDict from pandas import DataFrame from psycopg import Cursor from psycopg.sql import SQL, Literal from ..data.filter import get_filter from ..data.util import get_where_include_exclude from ..data.QueryManager import get_data from .form import get_form from . import BOOLEAN, PARAMS def get_product_rollup_statement(filters) -> SQL: _map = { k: k[0] for k in ('product', 'category', 'group') } where = [ get_where_include_exclude( _map[k], "name", list(include), list(exclude) ) for k, (include, exclude) in filters.items() ] return SQL(""" SELECT count(DISTINCT p.id) AS "Products", count(DISTINCT c.id) AS "Categories", count(DISTINCT g.id) AS "Groups", p.name AS "product", c.name AS "category", g.name AS "group" FROM products p JOIN categories c ON p.category_id = c.id JOIN groups g ON c.group_id = g.id WHERE {where} GROUP BY ROLLUP (g.name, c.name, p.name) """).format(where=SQL("\nAND").join(where)) def get_inner_query(query: FormsDict) -> SQL: filters = get_filter(query, allow=('group', 'category', 'product')) inner = get_product_rollup_statement(filters) return inner def render_form(cur: Cursor, inner: str, query: FormsDict): _filter = get_filter(query, allow=PARAMS) data = DataFrame(get_data(cur, inner)).dropna() action = request.path.split('/')[-1] organic = BOOLEAN.get(query.organic, None) return get_form(action, 'post', _filter, organic, data) def get_xml(cur: Cursor, sql: str): return cur.execute(SQL( "SELECT query_to_xml_and_xmlschema({q}, false, false, ''::text)" ).format(q=Literal(sql))).fetchone()[0] def get_products(cur: Cursor, query: FormsDict): inner = get_inner_query(query) form = render_form(cur, inner, query) sql = SQL(""" SELECT --"Transactions", COALESCE("product", "Products"||'') "Product", COALESCE("category", "Categories"||'') "Category", COALESCE("group", "Groups"||'') "Group" FROM ({inner}) q WHERE q.product IS NOT NULL OR q.group IS NULL """).format(inner=inner).as_string(cur) xml = get_xml(cur, sql) return template("query-to-xml", title="Products", xml=xml, form=form) def get_categories(cur: Cursor, query: FormsDict): inner = get_inner_query(query) form = render_form(cur, inner, query) sql = SQL(""" SELECT "Products" "Product", COALESCE("category", "Categories"||'') "Category", COALESCE("group", "Groups"||'') "Group" FROM ({inner}) q WHERE q.product IS NULL AND (q.category IS NOT NULL OR q.group IS NULL) """).format(inner=inner).as_string(cur) xml = get_xml(cur, sql) return template("query-to-xml", title="Categories", xml=xml, form=form) def get_groups(cur: Cursor, query: FormsDict): inner = get_inner_query(query) form = render_form(cur, inner, query) sql = SQL(""" SELECT "Products", "Categories", COALESCE("group", "Groups"||'') "Group" FROM ({inner}) q WHERE q.category IS NULL """).format(inner=inner).as_string(cur) xml = get_xml(cur, sql) return template("query-to-xml", title="Groups", xml=xml, form=form) def get_tags_statement(filters) -> SQL: _map = { k: k[0] for k in ('product', 'category', 'group') } _map.update({ 'tag': 'tg' }) where = [ get_where_include_exclude( _map[k], "name", list(include), list(exclude) ) for k, (include, exclude) in filters.items() ] return SQL(""" SELECT * FROM (SELECT count(DISTINCT txn.id) AS "Uses", tg.name AS "Name" FROM tags tg JOIN tags_map tm ON tg.id = tm.tag_id JOIN transactions txn ON txn.id = tm.transaction_id WHERE {where} GROUP BY tg.name ORDER BY 1 DESC, 2) q UNION ALL SELECT count(DISTINCT txn.id) AS "Uses", count(DISTINCT tg.name)||'' AS "Name" FROM tags tg JOIN tags_map tm ON tg.id = tm.tag_id JOIN transactions txn ON txn.id = tm.transaction_id WHERE {where} """).format(where=SQL("\nAND").join(where)) def get_inner_tags_query(query: FormsDict) -> SQL: filters = get_filter(query, allow=('tag',)) inner = get_tags_statement(filters) return inner def get_tags(cur: Cursor, query: FormsDict): inner = get_inner_tags_query(query) form = render_form(cur, inner, query) sql = inner.as_string(cur) xml = get_xml(cur, sql) return template("query-to-xml", title="Tags", xml=xml, form=form)