// TODO check logic here, it's just been copied from BookOrderItems. Possibly could remove duplication here.
        protected override async ValueTask ProposeOrderItems(List <OrderItemContext <SessionOpportunity> > orderItemContexts, StoreBookingFlowContext flowContext, OrderStateContext stateContext, OrderTransaction databaseTransaction)
        {
            // Check that there are no conflicts between the supplied opportunities
            // Also take into account spaces requested across OrderItems against total spaces in each opportunity

            foreach (var ctxGroup in orderItemContexts.GroupBy(x => x.RequestBookableOpportunityOfferId))
            {
                // Check that the Opportunity ID and type are as expected for the store
                if (ctxGroup.Key.OpportunityType != OpportunityType.ScheduledSession || !ctxGroup.Key.ScheduledSessionId.HasValue)
                {
                    throw new OpenBookingException(new UnableToProcessOrderItemError(), "Opportunity ID and type are as not expected for the SessionStore, during proposal");
                }

                // Attempt to book for those with the same IDs, which is atomic
                var(result, bookedOrderItemInfos) = await FakeDatabase.BookOrderItemsForClassOccurrence(
                    databaseTransaction.FakeDatabaseTransaction,
                    flowContext.OrderId.ClientId,
                    flowContext.SellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */,
                    flowContext.OrderId.uuid,
                    ctxGroup.Key.ScheduledSessionId.Value,
                    RenderOpportunityId(ctxGroup.Key),
                    RenderOfferId(ctxGroup.Key),
                    ctxGroup.Count(),
                    true
                    );

                switch (result)
                {
                case ReserveOrderItemsResult.Success:
                    // Set OrderItemId for each orderItemContext
                    foreach (var(ctx, bookedOrderItemInfo) in ctxGroup.Zip(bookedOrderItemInfos, (ctx, bookedOrderItemInfo) => (ctx, bookedOrderItemInfo)))
                    {
                        ctx.SetOrderItemId(flowContext, bookedOrderItemInfo.OrderItemId);
                    }
                    break;

                case ReserveOrderItemsResult.SellerIdMismatch:
                    throw new OpenBookingException(new SellerMismatchError(), "An OrderItem SellerID did not match");

                case ReserveOrderItemsResult.OpportunityNotFound:
                    throw new OpenBookingException(new UnableToProcessOrderItemError(), "Opportunity not found");

                case ReserveOrderItemsResult.NotEnoughCapacity:
                    throw new OpenBookingException(new OpportunityHasInsufficientCapacityError());

                case ReserveOrderItemsResult.OpportunityOfferPairNotBookable:
                    throw new OpenBookingException(new UnableToProcessOrderItemError(), "Opportunity and offer pair were not bookable");

                default:
                    throw new OpenBookingException(new OrderCreationFailedError(), "Booking failed for an unexpected reason");
                }
            }
        }
        //TODO: This should reuse code of LeaseOrderItem
        protected override async ValueTask BookOrderItems(List <OrderItemContext <SessionOpportunity> > orderItemContexts, StoreBookingFlowContext flowContext, OrderStateContext stateContext, OrderTransaction databaseTransaction)
        {
            // Check that there are no conflicts between the supplied opportunities
            // Also take into account spaces requested across OrderItems against total spaces in each opportunity

            // TODO: ENSURE THAT THIS IS CALLED EVERY TIME BY THE STOREBOOKINGENGINE, EVEN WITH ZERO ITEMS
            // This will ensure that items can be removed from the Order before the booking is confirmed if all items of that type have been removed from the lease

            // Step 1: Call lease to ensure items are already leased

            // Step 2: Set OrderItems to booked

            // Step 3: Write attendee and orderItemIntakeFormResponse to the OrderItem records, for inclusion in P later

            foreach (var ctxGroup in orderItemContexts.GroupBy(x => x.RequestBookableOpportunityOfferId))
            {
                // Check that the Opportunity ID and type are as expected for the store
                if (ctxGroup.Key.OpportunityType != OpportunityType.ScheduledSession || !ctxGroup.Key.ScheduledSessionId.HasValue)
                {
                    throw new OpenBookingException(new UnableToProcessOrderItemError(), "Opportunity ID and type are as not expected for the SessionStore, during booking");
                }

                // Attempt to book for those with the same IDs, which is atomic
                var(result, bookedOrderItemInfos) = await FakeDatabase.BookOrderItemsForClassOccurrence(
                    databaseTransaction.FakeDatabaseTransaction,
                    flowContext.OrderId.ClientId,
                    flowContext.SellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */,
                    flowContext.OrderId.uuid,
                    ctxGroup.Key.ScheduledSessionId.Value,
                    RenderOpportunityId(ctxGroup.Key),
                    RenderOfferId(ctxGroup.Key),
                    ctxGroup.Count(),
                    false
                    );

                switch (result)
                {
                case ReserveOrderItemsResult.Success:
                    foreach (var(ctx, bookedOrderItemInfo) in ctxGroup.Zip(bookedOrderItemInfos, (ctx, bookedOrderItemInfo) => (ctx, bookedOrderItemInfo)))
                    {
                        // Set OrderItemId and access properties for each orderItemContext
                        ctx.SetOrderItemId(flowContext, bookedOrderItemInfo.OrderItemId);
                        BookedOrderItemHelper.AddPropertiesToBookedOrderItem(ctx, bookedOrderItemInfo);
                    }
                    break;

                case ReserveOrderItemsResult.SellerIdMismatch:
                    throw new OpenBookingException(new SellerMismatchError(), "An OrderItem SellerID did not match");

                case ReserveOrderItemsResult.OpportunityNotFound:
                    throw new OpenBookingException(new UnableToProcessOrderItemError(), "Opportunity not found");

                case ReserveOrderItemsResult.NotEnoughCapacity:
                    throw new OpenBookingException(new OpportunityHasInsufficientCapacityError());

                case ReserveOrderItemsResult.OpportunityOfferPairNotBookable:
                    throw new OpenBookingException(new UnableToProcessOrderItemError(), "Opportunity and offer pair were not bookable");

                default:
                    throw new OpenBookingException(new OrderCreationFailedError(), "Booking failed for an unexpected reason");
                }
            }
        }