Skip to content

Commit

Permalink
Merge pull request #4263 from magfest/mff-fixes
Browse files Browse the repository at this point in the history
Dealer and payment fixes
  • Loading branch information
kitsuta authored Oct 9, 2023
2 parents 9af40b4 + 67a1636 commit 7296894
Show file tree
Hide file tree
Showing 38 changed files with 535 additions and 319 deletions.
8 changes: 4 additions & 4 deletions uber/automated_emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from uber.models import AdminAccount, Attendee, AttendeeAccount, ArtShowApplication, AutomatedEmail, Department, Group, \
GuestGroup, IndieGame, IndieJudge, IndieStudio, MarketplaceApplication, MITSTeam, MITSApplicant, PanelApplication, \
PanelApplicant, PromoCodeGroup, Room, RoomAssignment, Shift
from uber.utils import after, before, days_after, days_before, localized_now, DeptChecklistConf
from uber.utils import after, before, days_after, days_before, days_between, localized_now, DeptChecklistConf


class AutomatedEmailFixture:
Expand Down Expand Up @@ -169,7 +169,7 @@ def body(self):
AttendeeAccount,
'{EVENT_NAME} account creation confirmed',
'reg_workflow/account_confirmation.html',
lambda a: not a.imported and a.hashed and not a.password_reset,
lambda a: not a.imported and a.hashed and not a.password_reset and not a.is_sso_account,
needs_approval=False,
allow_at_the_con=True,
ident='attendee_account_confirmed')
Expand Down Expand Up @@ -313,7 +313,7 @@ def __init__(self, subject, template, filter, ident, **kwargs):
'Reminder to pay for your {EVENT_NAME} Art Show application',
'art_show/payment_reminder.txt',
lambda a: a.status == c.APPROVED and a.is_unpaid,
when=days_before(14, c.ART_SHOW_PAYMENT_DUE),
when=days_between((14, c.ART_SHOW_PAYMENT_DUE), (1, c.EPOCH)),
ident='art_show_payment_reminder')

ArtShowAppEmailFixture(
Expand All @@ -334,7 +334,7 @@ def __init__(self, subject, template, filter, ident, **kwargs):
'{EVENT_NAME} Art Show MAIL IN Instructions',
'art_show/mailing_in.html',
lambda a: a.status == c.APPROVED and not a.is_unpaid and a.delivery_method == c.BY_MAIL,
when=days_before(40, c.ART_SHOW_DEADLINE),
when=days_between((c.ART_SHOW_REG_START, 13), (16, c.ART_SHOW_WAITLIST)),
ident='art_show_mail_in')


Expand Down
1 change: 1 addition & 0 deletions uber/configspec.ini
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ marketplace_app_email = string(default="")

# Emails for Art Show applications are sent from this address.
art_show_email = string(default="")
art_show_notifications_email = string(default='%(art_show_email)s')

# These signatures are used at the bottom of many of our automated emails.
regdesk_email_signature = string(default='')
Expand Down
6 changes: 3 additions & 3 deletions uber/forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ def load_forms(params, model, form_list, prefix_dict={}, get_optional=True, trun
conflicting field names on the same page, e.g., passing {'GroupInfo': 'group_'} will add group_ to all GroupInfo fields.
`get_optional` is a flag that controls whether or not the forms' get_optional_fields() function is called. Set this to false
when loading forms for validation, as the validate_model function in utils.py handles optional fields.
`truncate_prefix` allows you to remove a single word from the form, so e.g. a truncate_prefix of "admin" will make
"AdminTableInfo" saved as "table_info." This allows loading admin and prereg versions of forms while using
the same form template.
`truncate_prefix` allows you to remove a single word from the form, so e.g. a truncate_prefix of "admin" will save
"AdminTableInfo" as "table_info." This allows loading admin and prereg versions of forms while using the
same form template.
Returns a dictionary of form objects with the snake-case version of the form as the ID, e.g.,
the PersonalInfo class will be returned as form_dict['personal_info'].
Expand Down
21 changes: 13 additions & 8 deletions uber/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ def access_query_matrix(self):
)
)

return_dict['panels_admin'] = self.query(Attendee).filter(
return_dict['panels_admin'] = self.query(Attendee).outerjoin(PanelApplicant).filter(
or_(Attendee.ribbon.contains(c.PANELIST_RIBBON),
Attendee.panel_applications != None,
Attendee.assigned_panelists != None,
Expand All @@ -828,6 +828,17 @@ def access_query_matrix(self):
.join(GuestGroup, Group.id == GuestGroup.group_id).filter(
and_(Group.id == Attendee.group_id, GuestGroup.group_id == Group.id, GuestGroup.group_type == c.MIVS)
))
return_dict['art_show_admin'] = self.query(Attendee
).outerjoin(
ArtShowApplication,
or_(ArtShowApplication.attendee_id == Attendee.id,
ArtShowApplication.agent_id == Attendee.id)
).outerjoin(ArtShowBidder).filter(
or_(Attendee.art_show_bidder != None,
Attendee.art_show_purchases != None,
Attendee.art_show_applications != None,
Attendee.art_agent_applications != None)
)
return return_dict

def viewable_attendees(self):
Expand Down Expand Up @@ -1053,10 +1064,7 @@ def create_attendee_account(self, email=None, password=None):
def add_attendee_to_account(self, attendee, account):
from uber.utils import normalize_email

unclaimed_account = account.hashed != ''
if c.SSO_EMAIL_DOMAINS:
local, domain = normalize_email(account.email, split_address=True)
unclaimed_account = unclaimed_account and domain not in c.SSO_EMAIL_DOMAINS
unclaimed_account = account.hashed != '' and not account.is_sso_account

if c.ONE_MANAGER_PER_BADGE and attendee.managers and not unclaimed_account:
attendee.managers.clear()
Expand Down Expand Up @@ -1113,9 +1121,6 @@ def attendee_from_marketplace_app(self, **params):

return attendee, message

def art_show_apps(self):
return self.query(ArtShowApplication).options(joinedload('attendee')).all()

def attendee_from_art_show_app(self, **params):
attendee, message = self.create_or_find_attendee_by_id(**params)
if message:
Expand Down
13 changes: 7 additions & 6 deletions uber/models/art_show.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ def incomplete_reason(self):
return "Mailing address required"
if self.attendee.placeholder and self.attendee.badge_status != c.NOT_ATTENDING:
return "Missing registration info"

@hybrid_property
def is_valid(self):
return self.status != c.DECLINED

@property
def total_cost(self):
Expand All @@ -140,14 +144,11 @@ def total_cost(self):
else:
if self.active_receipt:
return self.active_receipt.item_total / 100
return self.potential_cost
return self.default_cost

@property
def potential_cost(self):
if self.overridden_price is not None:
return self.overridden_price
else:
return self.default_cost or 0
return self.default_cost or 0

def calc_app_price_change(self, **kwargs):
preview_app = ArtShowApplication(**self.to_dict())
Expand Down Expand Up @@ -179,7 +180,7 @@ def is_unpaid(self):

@property
def amount_unpaid(self):
return max(0, self.total_cost - (self.amount_paid / 100))
return max(0, ((self.total_cost * 100) - self.amount_paid) / 100)

@property
def amount_pending(self):
Expand Down
31 changes: 19 additions & 12 deletions uber/models/attendee.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,11 +516,12 @@ def _status_adjustments(self):
elif self.group.is_dealer and self.group.status != c.APPROVED:
self.badge_status = c.UNAPPROVED_DEALER_STATUS

if self.badge_status == c.INVALID_GROUP_STATUS and (not self.group or self.group.is_valid):
if self.badge_status == c.INVALID_GROUP_STATUS and (not self.group or self.group.is_valid or self.paid != c.PAID_BY_GROUP):
self.badge_status = c.NEW_STATUS

if self.badge_status == c.UNAPPROVED_DEALER_STATUS and (not self.group or
not self.group.is_dealer or
self.paid != c.PAID_BY_GROUP or
self.group.status == c.APPROVED):
self.badge_status = c.NEW_STATUS

Expand All @@ -542,7 +543,7 @@ def _status_adjustments(self):
elif self.badge_status == c.NEW_STATUS and not self.placeholder and self.first_name and (
self.paid in [c.HAS_PAID, c.NEED_NOT_PAY, c.REFUNDED]
or self.paid == c.PAID_BY_GROUP
and self.group_id
and self.group
and not self.group.is_unpaid):
self.badge_status = c.COMPLETED_STATUS

Expand Down Expand Up @@ -727,7 +728,7 @@ def access_sections(self):
section_list.append('mits_admin')
if self.group and self.group.guest and self.group.guest.group_type == c.MIVS:
section_list.append('mivs_admin')
if self.art_show_applications or self.art_show_bidder or self.art_show_purchases:
if self.art_show_applications or self.art_show_bidder or self.art_show_purchases or self.art_agent_applications:
section_list.append('art_show_admin')
if self.marketplace_applications:
section_list.append('marketplace_admin')
Expand Down Expand Up @@ -988,8 +989,6 @@ def calc_badge_cost_change(self, **kwargs):
new_cost = preview_attendee.calculate_badge_prices_cost(self.badge_type) * 100
if 'ribbon' in kwargs:
add_opt(preview_attendee.ribbon_ints, int(kwargs['ribbon']))
if 'paid' in kwargs:
preview_attendee.paid = int(kwargs['paid'])

current_cost = self.calculate_badge_cost() * 100
if not new_cost:
Expand Down Expand Up @@ -1035,13 +1034,13 @@ def calc_promo_discount_change(self, promo_code):
def calc_badge_comp_change(self, paid):
preview_attendee = Attendee(**self.to_dict())
paid = int(paid)
comped_or_refunded = [c.NEED_NOT_PAY, c.REFUNDED]
free_badge_statuses = [c.NEED_NOT_PAY, c.REFUNDED, c.PAID_BY_GROUP]
preview_attendee.paid = paid
if paid != c.NEED_NOT_PAY and self.paid != c.NEED_NOT_PAY:
if paid not in free_badge_statuses and self.paid not in free_badge_statuses:
return 0, 0
elif self.paid in comped_or_refunded and paid in comped_or_refunded:
elif self.paid in free_badge_statuses and paid in free_badge_statuses:
return 0, 0
elif paid == c.NEED_NOT_PAY:
elif paid in free_badge_statuses:
return 0, self.badge_cost * -1 * 100
else:
badge_cost = preview_attendee.calculate_badge_cost() * 100
Expand Down Expand Up @@ -1156,7 +1155,9 @@ def is_not_ready_to_checkin(self):
def can_abandon_badge(self):
return not self.amount_paid and (
not self.paid == c.NEED_NOT_PAY or self.in_promo_code_group
) and not self.is_group_leader and not self.checked_in
) and (not self.is_group_leader or not self.group.is_valid) and not self.checked_in and (
not self.art_show_applications or not self.art_show_applications[0].is_valid
) and (not self.art_agent_applications or not any(app.is_valid for app in self.art_agent_applications))

@property
def can_self_service_refund_badge(self):
Expand Down Expand Up @@ -2180,6 +2181,12 @@ def normalized_email(self):
def normalized_email(cls):
return func.replace(func.lower(func.trim(cls.email)), '.', '')

@property
def is_sso_account(self):
if c.SSO_EMAIL_DOMAINS:
local, domain = normalize_email(self.email, split_address=True)
return domain in c.SSO_EMAIL_DOMAINS

@property
def has_only_one_badge(self):
return len(self.attendees) == 1
Expand All @@ -2194,11 +2201,11 @@ def valid_attendees(self):

@property
def valid_single_badges(self):
return [attendee for attendee in self.valid_attendees if not attendee.group]
return [attendee for attendee in self.valid_attendees if not attendee.group or not attendee.group.is_valid]

@property
def valid_group_badges(self):
return [attendee for attendee in self.valid_attendees if attendee.group]
return [attendee for attendee in self.valid_attendees if attendee.group and attendee.group.is_valid]

@property
def imported_attendees(self):
Expand Down
4 changes: 3 additions & 1 deletion uber/models/commerce.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,11 @@ def get_last_incomplete_txn(self):
txn.cancelled = datetime.now() # TODO: Add logs to txns/items and log the automatic cancellation reason?

if txn.amount != self.current_amount_owed and self.current_amount_owed:
txn.amount = self.current_amount_owed
if not c.AUTHORIZENET_LOGIN_ID:
txn.amount = self.current_amount_owed
stripe.PaymentIntent.modify(txn.intent_id, amount = txn.amount)
else:
txn.cancelled = datetime.now()

if self.session:
self.session.add(txn)
Expand Down
6 changes: 3 additions & 3 deletions uber/models/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def _cost_and_leader(self):
self.cost = 0
if self.status == c.APPROVED and not self.approved:
self.approved = datetime.now(UTC)
if self.leader and self.is_dealer:
if self.leader and self.is_dealer and self.leader.paid == c.PAID_BY_GROUP:
self.leader.ribbon = add_opt(self.leader.ribbon_ints, c.DEALER_RIBBON)
if not self.is_unpaid or self.orig_value_of('status') != self.status:
for a in self.attendees:
Expand Down Expand Up @@ -265,7 +265,8 @@ def badges_purchased(self):
@badges_purchased.expression
def badges_purchased(cls):
from uber.models import Attendee
return exists().where(and_(Attendee.group_id == cls.id, Attendee.paid == c.PAID_BY_GROUP))
return select([func.count(Attendee.id)]
).where(and_(Attendee.group_id == cls.id, Attendee.paid == c.PAID_BY_GROUP)).label('badges_purchased')

@property
def badges(self):
Expand Down Expand Up @@ -293,7 +294,6 @@ def current_badge_cost(self):

return total_badge_cost


@property
def new_badge_cost(self):
return c.DEALER_BADGE_PRICE if self.is_dealer else c.get_group_price()
Expand Down
39 changes: 15 additions & 24 deletions uber/payments.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,18 @@
import importlib
import math
import os
import random
import re
import string
import traceback
import json
import pytz
from typing import Iterable
import urllib
from collections import defaultdict, OrderedDict
from datetime import date, datetime, timedelta
from collections import OrderedDict
from datetime import datetime, timedelta
from dateutil.parser import parse
from glob import glob
from os.path import basename
from random import randrange
from rpctools.jsonrpc import ServerProxy
from urllib.parse import urlparse, urljoin
from uuid import uuid4

import cherrypy
import phonenumbers
import stripe
from authorizenet import apicontractsv1, apicontrollers
from phonenumbers import PhoneNumberFormat
from pockets import cached_property, classproperty, floor_datetime, is_listy, listify
from pockets import cached_property, classproperty, is_listy, listify
from pockets.autolog import log
from sideboard.lib import threadlocal

import uber
from uber.config import c, _config, signnow_sdk
from uber.config import c
from uber.custom_tags import format_currency, email_only
from uber.errors import CSRFException, HTTPRedirect
from uber.utils import report_critical_exception
Expand Down Expand Up @@ -821,7 +804,6 @@ def create_new_receipt(cls, model, create_model=False, items=None):
for calculation in i[model.__class__.__name__].values():
item = calculation(model)
if item:
log.debug(item)
try:
desc, cost, col_name, count = item
except ValueError:
Expand Down Expand Up @@ -1039,6 +1021,12 @@ def auto_update_receipt(self, model, receipt, params):
if coerced_val != getattr(model, key, None):
changed_params[key] = coerced_val

if isinstance(model, Group):
# "badges" is a property and not a column, so we have to include it explicitly
maybe_badges_update = params.get('badges', None)
if maybe_badges_update != None and maybe_badges_update != model.badges:
changed_params['badges'] = maybe_badges_update

cost_changes = getattr(model.__class__, 'cost_changes', [])
credit_changes = getattr(model.__class__, 'credit_changes', [])
for param in changed_params:
Expand Down Expand Up @@ -1071,7 +1059,7 @@ def mark_paid_from_stripe_intent(payment_intent):
log.error(f"Tried to mark payments with intent ID {payment_intent.id} as paid but the charge on this intent wasn't successful!")
return []

ReceiptManager.mark_paid_from_ids(payment_intent.id, payment_intent.charges.data[0].id)
return ReceiptManager.mark_paid_from_ids(payment_intent.id, payment_intent.charges.data[0].id)

@staticmethod
def mark_paid_from_ids(intent_id, charge_id):
Expand All @@ -1095,6 +1083,9 @@ def mark_paid_from_ids(intent_id, charge_id):
session.add(txn)
txn_receipt = txn.receipt

if txn.cancelled != None:
txn.cancelled == None

for item in txn.receipt_items:
item.closed = datetime.now()
session.add(item)
Expand Down Expand Up @@ -1127,7 +1118,7 @@ def mark_paid_from_ids(intent_id, charge_id):
try:
send_email.delay(
c.ART_SHOW_EMAIL,
c.ART_SHOW_EMAIL,
c.ART_SHOW_NOTIFICATIONS_EMAIL,
'Art Show Payment Received',
render('emails/art_show/payment_notification.txt',
{'app': model}, encoding=None),
Expand Down
5 changes: 3 additions & 2 deletions uber/receipt_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def app_cost(app):

@cost_calculation.ArtShowApplication
def overridden_app_cost(app):
if app.status == c.APPROVED and app.overridden_price != None:
if app.overridden_price != None:
return ("Art Show Application (Custom Price)", app.overridden_price * 100, 'overridden_price')

@cost_calculation.ArtShowApplication
Expand Down Expand Up @@ -66,12 +66,13 @@ def mailing_fee_cost(app):
Attendee.cost_changes = {
'overridden_price': ('Custom Badge Price', "calc_badge_cost_change"),
'badge_type': ('Badge ({})', "calc_badge_cost_change", c.BADGES),
'ribbon': ('Ribbon ({})', "calc_badge_cost_change", c.RIBBONS),
'amount_extra': ('Preordered Merch ({})', None, c.DONATION_TIERS),
'extra_donation': ('Extra Donation', None),
}

Attendee.credit_changes = {
'paid': ('Badge Comp', "calc_badge_comp_change"),
'paid': ('Badge Comped or Paid By Group', "calc_badge_comp_change"),
'birthdate': ('Age Discount', "calc_age_discount_change"),
'promo_code': ('Promo Code', "calc_promo_discount_change"),
}
Expand Down
Loading

0 comments on commit 7296894

Please sign in to comment.