diff --git a/uber/site_sections/budget.py b/uber/site_sections/budget.py index 0fc811d73..91bac105d 100644 --- a/uber/site_sections/budget.py +++ b/uber/site_sections/budget.py @@ -5,17 +5,28 @@ from uber.config import c from uber.decorators import all_renderable, log_pageview -from uber.models import ArbitraryCharge, Attendee, Group, MPointsForCash, ReceiptItem, Sale, PromoCode, PromoCodeGroup +from uber.models import ArbitraryCharge, Attendee, Group, MPointsForCash, ReceiptItem, Sale, PromoCode, PromoCodeGroup, ModelReceipt from uber.server import redirect_site_section from uber.utils import localized_now -def get_grouped_costs(session, filters, joins=[], selector=Attendee.badge_cost): +def _build_item_subquery(session): + return session.query(ModelReceipt.owner_id, ModelReceipt.item_total_sql.label('item_total') + ).join(ModelReceipt.receipt_items).group_by(ModelReceipt.owner_id).subquery() + +def _build_txn_subquery(session): + return session.query(ModelReceipt.owner_id, ModelReceipt.payment_total_sql.label('payment_total'), + ModelReceipt.refund_total_sql.label('refund_total') + ).join(ModelReceipt.receipt_txns).group_by(ModelReceipt.owner_id).subquery() + +def get_grouped_costs(session, filters=[], joins=[], selector=Attendee.badge_cost): # Returns a defaultdict with the {int(cost): count} of badges query = session.query(selector, func.count(selector)) for join in joins: - query.outerjoin(join) - return defaultdict(int, query.filter(*filters).group_by(selector).order_by(selector).all()) + query = query.join(join) + if filters: + query = query.filter(*filters) + return defaultdict(int, query.group_by(selector).order_by(selector).all()) def get_dict_sum(dict_to_sum): @@ -40,26 +51,42 @@ def index(self, session): def badge_cost_summary(self, session): attendees = session.query(Attendee) + item_subquery = _build_item_subquery(session) + txn_subquery = _build_txn_subquery(session) base_filter = [Attendee.has_or_will_have_badge] group_filter = base_filter + [Attendee.group_id != None, Attendee.paid == c.PAID_BY_GROUP] # noqa: E711 - badge_cost_matters_filter = group_filter + [Group.cost > 0, Group.auto_recalc == True] # noqa: E712 + badge_cost_matters_filter = [Group.cost > 0, Group.auto_recalc == True] # noqa: E712 group_counts = {} - group_counts['free_groups'] = session.query(Attendee).outerjoin(Attendee.group, aliased=True).filter( - *group_filter).filter(Group.cost <= 0).count() - group_counts['custom_price'] = session.query(Attendee).outerjoin(Attendee.group, aliased=True).filter( - *group_filter).filter(Group.cost > 0, Group.auto_recalc == False).count() # noqa: E712 - - paid_group_badges = get_grouped_costs(session, - badge_cost_matters_filter + [Group.is_paid == True], # noqa: E712 - [Attendee.group]) - unpaid_group_badges = get_grouped_costs(session, - badge_cost_matters_filter + [Group.cost > 0, - Group.is_paid == False], # noqa: E712 - [Attendee.group]) + group_counts['free_groups'] = session.query(Attendee).filter( + *group_filter).join(Attendee.group).filter(Group.cost <= 0).count() + group_counts['custom_price'] = session.query(Attendee).filter( + *group_filter).join(Attendee.group).filter(Group.cost > 0, Group.auto_recalc == False).count() # noqa: E712 + + group_subquery_base = session.query(Attendee.id).filter(*group_filter).outerjoin( + Attendee.group).filter(*badge_cost_matters_filter).outerjoin( + item_subquery, Group.id == item_subquery.c.owner_id + ).outerjoin(txn_subquery, Group.id == txn_subquery.c.owner_id).group_by(Attendee.id).group_by( + item_subquery.c.item_total).group_by(txn_subquery.c.payment_total).group_by( + txn_subquery.c.refund_total) + + paid_group_subquery = group_subquery_base.having( + (txn_subquery.c.payment_total - txn_subquery.c.refund_total) >= item_subquery.c.item_total).subquery() + unpaid_group_subquery = group_subquery_base.having( + (txn_subquery.c.payment_total - txn_subquery.c.refund_total) < item_subquery.c.item_total).subquery() + paid_group_badges = get_grouped_costs( + session, joins=[(paid_group_subquery, Attendee.id == paid_group_subquery.c.id)]) + unpaid_group_badges = get_grouped_costs( + session, joins=[(unpaid_group_subquery, Attendee.id == unpaid_group_subquery.c.id)]) + no_receipt_group_badges = get_grouped_costs(session, + filters=group_filter + badge_cost_matters_filter + [ + Attendee.default_cost > 0, ~Attendee.active_receipt.has()], + joins=[Attendee.group]) + for key, val in no_receipt_group_badges.items(): + unpaid_group_badges[key] += val group_total = session.query(Attendee).filter(*group_filter).count() @@ -71,7 +98,7 @@ def badge_cost_summary(self, session): pc_group_total = session.query(Attendee).filter(*pc_group_filter).count() pc_group_leaders = session.query(Attendee).filter(Attendee.promo_code_groups != None).count() # noqa: E711 - pc_group_badges = get_grouped_costs(session, paid_pc_group_filter, [Attendee.promo_code, PromoCode.group]) + pc_group_badges = get_grouped_costs(session, paid_pc_group_filter) for group in session.query(PromoCodeGroup).filter(PromoCodeGroup.total_cost <= 0): pc_comped_badges += len(group.used_promo_codes) @@ -84,11 +111,25 @@ def badge_cost_summary(self, session): individual_filter = base_filter + [not_(Attendee.paid.in_([c.PAID_BY_GROUP, c.NEED_NOT_PAY])), Attendee.promo_code_group_name == None, # noqa: E711 Attendee.badge_cost > 0] - paid_ind_filter = individual_filter + [Attendee.is_paid == True] # noqa: E712 - unpaid_ind_filter = individual_filter + [Attendee.default_cost > 0, Attendee.is_paid == False] # noqa: E712 - - individual_badges = get_grouped_costs(session, paid_ind_filter) - unpaid_badges = get_grouped_costs(session, unpaid_ind_filter) + ind_subquery_base = session.query(Attendee.id).filter(*individual_filter).outerjoin( + item_subquery, Attendee.id == item_subquery.c.owner_id + ).outerjoin(txn_subquery, Attendee.id == txn_subquery.c.owner_id).group_by(Attendee.id).group_by( + item_subquery.c.item_total).group_by(txn_subquery.c.payment_total).group_by( + txn_subquery.c.refund_total) + + paid_ind_subquery = ind_subquery_base.having( + (txn_subquery.c.payment_total - txn_subquery.c.refund_total) >= item_subquery.c.item_total).subquery() + unpaid_ind_subquery = ind_subquery_base.having( + (txn_subquery.c.payment_total - txn_subquery.c.refund_total) < item_subquery.c.item_total).subquery() + + individual_badges = get_grouped_costs(session, joins=[(paid_ind_subquery, Attendee.id == paid_ind_subquery.c.id)]) + unpaid_badges = get_grouped_costs(session, filters=[Attendee.default_cost > 0], + joins=[(unpaid_ind_subquery, Attendee.id == unpaid_ind_subquery.c.id)]) + no_receipt_badges = get_grouped_costs(session, + filters=individual_filter + [Attendee.default_cost > 0, + ~Attendee.active_receipt.has()]) + for key, val in no_receipt_badges.items(): + unpaid_badges[key] += val comped_badges = session.query(Attendee).filter(*base_filter, Attendee.promo_code_group_name == None, # noqa: E711 @@ -96,7 +137,7 @@ def badge_cost_summary(self, session): individual_total = session.query(Attendee).filter(*individual_filter).count() + comped_badges return { - 'total_badges': attendees.count(), + 'total_badges': attendees.filter(*base_filter).count(), 'group_total': group_total, 'group_counts': group_counts, 'group_badges': paid_group_badges, @@ -190,22 +231,49 @@ def attendee_addon_summary(self, session): preordered_merch_filter = base_filter + [Attendee.amount_extra > 0] extra_donation_filter = base_filter + [Attendee.extra_donation > 0] + item_subquery = _build_item_subquery(session) + txn_subquery = _build_txn_subquery(session) + + addons_subquery_base = session.query(Attendee.id).outerjoin( + item_subquery, Attendee.id == item_subquery.c.owner_id + ).outerjoin(txn_subquery, Attendee.id == txn_subquery.c.owner_id).group_by(Attendee.id).group_by( + item_subquery.c.item_total).group_by(txn_subquery.c.payment_total).group_by( + txn_subquery.c.refund_total) + + paid_addons_subquery = addons_subquery_base.having( + (txn_subquery.c.payment_total - txn_subquery.c.refund_total) >= item_subquery.c.item_total).subquery() + unpaid_addons_subquery = addons_subquery_base.having( + (txn_subquery.c.payment_total - txn_subquery.c.refund_total) < item_subquery.c.item_total).subquery() + paid_preordered_merch = get_grouped_costs(session, - preordered_merch_filter + [Attendee.is_paid == True], # noqa: E712 + filters=preordered_merch_filter, + joins=[(paid_addons_subquery, Attendee.id == paid_addons_subquery.c.id)], selector=Attendee.amount_extra) unpaid_preordered_merch = get_grouped_costs(session, - preordered_merch_filter + [ - Attendee.default_cost > 0, - Attendee.is_paid == False], # noqa: E712 + filters=preordered_merch_filter + [Attendee.default_cost > 0], + joins=[(unpaid_addons_subquery, Attendee.id == unpaid_addons_subquery.c.id)], selector=Attendee.amount_extra) + no_receipt_preordered_merch = get_grouped_costs(session, + filters=preordered_merch_filter + [Attendee.default_cost > 0, + ~Attendee.active_receipt.has()], + selector=Attendee.extra_donation) + for key, val in no_receipt_preordered_merch.items(): + unpaid_preordered_merch[key] += val paid_extra_donations = get_grouped_costs(session, - extra_donation_filter + [Attendee.is_paid == True], # noqa: E712 + filters=extra_donation_filter, + joins=[(paid_addons_subquery, Attendee.id == paid_addons_subquery.c.id)], selector=Attendee.extra_donation) + no_receipt_extra_donations = get_grouped_costs(session, + filters=extra_donation_filter + [Attendee.default_cost > 0, + ~Attendee.active_receipt.has()], + selector=Attendee.extra_donation) unpaid_extra_donations = get_grouped_costs(session, - extra_donation_filter + [Attendee.default_cost > 0, - Attendee.is_paid == False], # noqa: E712 + filters=extra_donation_filter + [Attendee.default_cost > 0], + joins=[(unpaid_addons_subquery, Attendee.id == unpaid_addons_subquery.c.id)], selector=Attendee.extra_donation) + for key, val in no_receipt_extra_donations.items(): + unpaid_extra_donations[key] += val return { 'total_addons': session.query(Attendee).filter(*base_filter).filter(or_(Attendee.amount_extra > 0, diff --git a/uber/templates/budget/badge_cost_summary.html b/uber/templates/budget/badge_cost_summary.html index b30bca531..ead2ff1d7 100644 --- a/uber/templates/budget/badge_cost_summary.html +++ b/uber/templates/budget/badge_cost_summary.html @@ -5,7 +5,7 @@
Please note that {% if c.HAS_REG_REPORTS_ACCESS %} - receipt discrepancies (warning: long load time!) + receipt discrepancies {% else %}receipt discrepancies{% endif %} may contribute to a different total intake than the amounts listed below.