BritishYouthBandAssociation / OrgAdmin

Admin site for organisations
1 stars 0 forks source link

Entry Fee #137

Closed matt-fidd closed 1 year ago

matt-fidd commented 2 years ago

Another day, another thought 🤔

Let's add EntryFee to EventType. When a record is inserted into Event, it will pull across this value - however, this will remain editable up until the first band booking on.

Let's also create a new table - EventTypeDiscount. It will link to the EventType and allow us to set discounts based on the number of shows entered.

E.g.

EventTypeID: 1
DiscountAfter: 2
DiscountMultiplier: 0.75

Will indicate that entering more than 2 events of type 1 yields a 25% discount.

When a band registers for an event, we will grab the price from the Event and then check to see if any discounts apply. This value will be inserted into a new Fee record and tied to the event registration

matt-fidd commented 2 years ago

This is a subset of #42

matt-fidd commented 2 years ago
matt-fidd commented 2 years ago

@rugulous Number 6 in the list above is the consideration that makes this a lot harder than it should be, what do you reckon?

rugulous commented 2 years ago

So number 6 is that I've registered for 4 shows, getting 2 at full price and 2 discounted - then I withdraw from 1 of the full price shows, correct?

That would kinda be (typed on my phone so please forgive the lack of checking)

/*
assuming the following vars:
- season: current season
- typeID: EventTypeId from the withdrawn event
- org: id of the withdrawing organisation
*/

const [type, events, discount] = await Promise.all([
    req.db.findByPk(typeID),
    req.db.Event.findAll({
        where: {
            SeasonId: season,
            EventTypeId: typeID
        },
        include: [{
            model: req.db.EventRegistration,
            where: {
                OrganisationId: org
            },
            include: [
                req.db.Fee
            ]
        }
        ],
        order: [['Start', 'ASC']]
    }),
    req.db.EventDiscount.findAll({
        where: {
            EventTypeId: typeID
        },
        order: [['DiscountAfter', 'ASC']]
    })]);

const eligibleEvents = 0;

for (let i = 0; i < events.length; i++) {
    const reg = events[i].EventRegistration;

    if (!reg.IsWithdrawn) {
        eligibleEvents++;
    }

    const currFee = reg.Fee.Total;

    const targetMultiplier = getMultiplier(eligibleEvents, discount);
    const targetFee = type.EntryCost * targetMultiplier;

    if (currFee != targetFee) {
        reg.Fee.Total = targetFee;
        reg.Fee.save();

        //TODO: handle already paid!
    }
}

function getMultiplier(num, thresholds) {
    let multiplier = 1;

    for (let i = 0; i < thresholds.length; i++) {
        //thresholds is pre-sorted so
        if (thresholds[i].DiscountAfter > num) {
            return multiplier;
        }

        multiplier = thresholds[i].DiscountMultiplier;
    }

    return multiplier;
}

Initially I was thinking we could have a scheduled task that runs maybe once an hour, and recalculates all the fees in the background to make sure things are right, but I'm not entirely sure how I feel about that as that then means we don't need to do any calculation up front...

Another consideration is that we might have to explore the possibility of "part payment" - if I enter 4 shows and pay for them all then withdraw from my paid one, I've now only partially paid the balance owed on that show that went from discounted to full price 🤯

rugulous commented 2 years ago

Almost looks like we're going to have to have an actually decent financial side of things instead of just keeping it simple...