From 22bd5349e833a6e38316b17bedb12167bdbe6f7e Mon Sep 17 00:00:00 2001 From: nickevansuk <2616208+nickevansuk@users.noreply.github.com> Date: Tue, 31 Aug 2021 10:01:49 +0100 Subject: [PATCH 01/33] temp: Debug race conditions --- .../CustomBookingEngine.cs | 8 +- .../Async/AsyncDuplicateLock.cs | 22 +- .../IdTransforms/IdTemplate.cs | 2 +- .../StoreBookingEngine/StoreBookingEngine.cs | 424 +++++++++--------- 4 files changed, 234 insertions(+), 222 deletions(-) diff --git a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs index 80f6f211..8378ef8e 100644 --- a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs +++ b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs @@ -129,7 +129,7 @@ public CustomBookingEngine(BookingEngineSettings settings, Uri openBookingAPIBas private Uri openDataFeedBaseUrl; private Dictionary> idConfigurationLookup; private Dictionary feedAssignedTemplates; - private readonly AsyncDuplicateLock asyncDuplicateLock = new AsyncDuplicateLock(); + private static readonly AsyncDuplicateLock asyncDuplicateLock = new AsyncDuplicateLock(); protected Dictionary OpportunityTemplateLookup { get; } @@ -403,6 +403,7 @@ private async Task ProcessCheckpoint(string clientId, Uri selle using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { var orderResponse = await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, flowStage, orderQuote), orderQuote); + // Return a 409 status code if any OrderItem level errors exist return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(orderResponse), orderResponse.OrderedItem.Exists(x => x.Error?.Count > 0) ? HttpStatusCode.Conflict : HttpStatusCode.OK); @@ -424,7 +425,10 @@ public async Task ProcessOrderCreationB(string clientId, Uri se var response = order.OrderProposalVersion != null ? await ProcessOrderCreationFromOrderProposal(orderId, settings.OrderIdTemplate, seller, sellerIdComponents, order) : await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, FlowStage.B, order), order); - return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(response), HttpStatusCode.Created); + + // Return a 409 status code if any OrderItem level errors exist + return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(response), + response.OrderedItem.Exists(x => x.Error?.Count > 0) ? HttpStatusCode.Conflict : HttpStatusCode.Created); } } diff --git a/OpenActive.Server.NET/OpenBookingHelper/Async/AsyncDuplicateLock.cs b/OpenActive.Server.NET/OpenBookingHelper/Async/AsyncDuplicateLock.cs index d4153638..37575f7a 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Async/AsyncDuplicateLock.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Async/AsyncDuplicateLock.cs @@ -5,7 +5,7 @@ namespace OpenActive.Server.NET.OpenBookingHelper { - public sealed class AsyncDuplicateLock + public sealed class AsyncDuplicateLock { private sealed class RefCounted { @@ -16,13 +16,13 @@ public RefCounted(T value) } public int RefCount { get; set; } - public T Value { get; } + public T Value { get; private set; } } - private static readonly Dictionary> SemaphoreSlims - = new Dictionary>(); + private static readonly Dictionary> SemaphoreSlims + = new Dictionary>(); - private SemaphoreSlim GetOrCreate(TKey key) + private SemaphoreSlim GetOrCreate(object key) { RefCounted item; lock (SemaphoreSlims) @@ -40,7 +40,13 @@ private SemaphoreSlim GetOrCreate(TKey key) return item.Value; } - public async Task LockAsync(TKey key) + public IDisposable Lock(object key) + { + GetOrCreate(key).Wait(); + return new Releaser { Key = key }; + } + + public async Task LockAsync(object key) { await GetOrCreate(key).WaitAsync().ConfigureAwait(false); return new Releaser { Key = key }; @@ -48,7 +54,7 @@ public async Task LockAsync(TKey key) private sealed class Releaser : IDisposable { - public TKey Key { get; set; } + public object Key { get; set; } public void Dispose() { @@ -64,4 +70,4 @@ public void Dispose() } } } -} \ No newline at end of file +} diff --git a/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs b/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs index 8ef8bdf5..dc7f3753 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs @@ -123,7 +123,7 @@ public override IBookableIdComponents GetOpportunityReference(Uri opportunityId, { // Require both opportunityId and offerId to not be null if (opportunityId == null) throw new ArgumentNullException(nameof(opportunityId)); - if (offerId == null) throw new ArgumentNullException(nameof(offerId)); + // if (offerId == null) throw new ArgumentNullException(nameof(offerId)); // As inheritance is in use, the Offer must be resolved against either: Opportunity with Offer; or Opportunity and _parent_ Offer // Note in OpenActive Modelling Specification 2.0 this behaviour is only applicable to SessionSeries and ScheduledSession diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index af9d060d..7b37174f 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -180,11 +180,11 @@ public void SetResponseOrderItem(OrderItem item, SellerIdComponents sellerId, St var requestAcceptedOfferId = RequestOrderItem?.AcceptedOffer.IdReference; if (requestAcceptedOfferId == null) { - throw new OpenBookingException(new InternalLibraryError(), "Request must include an acceptedOffer for the OrderItem"); + //throw new OpenBookingException(new InternalLibraryError(), "Request must include an acceptedOffer for the OrderItem"); } if (item?.AcceptedOffer.Object?.Id != requestAcceptedOfferId) { - throw new OpenBookingException(new InternalLibraryError(), "The Offer ID within the response OrderItem must match the request OrderItem"); + //throw new OpenBookingException(new InternalLibraryError(), "The Offer ID within the response OrderItem must match the request OrderItem"); } if (sellerId != flowContext.SellerId) @@ -260,6 +260,8 @@ public List ValidateDetails(FlowStage flowStage) /// public class StoreBookingEngine : CustomBookingEngine { + private class SilentRollbackException : Exception {} + /// /// Simple constructor /// @@ -383,25 +385,11 @@ protected override async Task ProcessOrderQuoteDeletion(OrderIdComponents orderI private static void CheckOrderIntegrity(Order requestOrder, Order responseOrder) { - // If any capacity errors were returned from GetOrderItems, the booking must fail - // https://www.openactive.io/open-booking-api/EditorsDraft/#order-creation-b - if (responseOrder.OrderedItem.Any(i => i.Error != null && i.Error.Any(e => e != null && e.GetType() == typeof(OpportunityHasInsufficientCapacityError)))) - { - throw new OpenBookingException(new OpportunityHasInsufficientCapacityError()); - } - - // If any lease capacity errors were returned from GetOrderItems, the booking must fail - // https://www.openactive.io/open-booking-api/EditorsDraft/#order-creation-b - if (responseOrder.OrderedItem.Any(i => i.Error != null && i.Error.Any(e => e != null && e.GetType() == typeof(OpportunityCapacityIsReservedByLeaseError)))) - { - throw new OpenBookingException(new OpportunityCapacityIsReservedByLeaseError()); - } - // If any other errors were returned from GetOrderItems, the booking must fail // https://www.openactive.io/open-booking-api/EditorsDraft/#order-creation-b if (responseOrder.OrderedItem.Any(x => x.Error != null && x.Error.Count > 0)) { - throw new OpenBookingException(new UnableToProcessOrderItemError(), string.Join(", ", responseOrder.OrderedItem.Where(x => x.Error != null).SelectMany(x => x.Error).Select(x => (x.Name ?? "") + (x.Description != null ? (": " + x.Description) : "")).ToList())); + throw new SilentRollbackException(); } // Throw error on payment due mismatch @@ -491,11 +479,14 @@ private List GetOrderItemContexts(List sourceOrder new IncompleteOrderItemError(), "orderedItem @id was not provided"); } + /* + TODO: Check if Customer Account auth and throw if not if (acceptedOfferId == null) { return new UnknownOrderItemContext(index, orderItem, new IncompleteOrderItemError(), "acceptedOffer @id was not provided"); } + */ var idComponents = base.ResolveOpportunityID(orderedItemId, acceptedOfferId); @@ -660,6 +651,8 @@ public override async Task ProcessFlowRequest(BookingFlowContext // StateContext is useful for transferring state between stages of the flow, and initialising disposable resources for use throughout the flow using (var stateContext = await storeBookingEngineSettings.OrderStore.CreateOrderStateContext(flowContext)) { + Console.WriteLine($"## {flowContext.OrderId.uuid} | ENTERING CRITICAL SECTION {flowContext.Stage.ToString()} for {flowContext?.Customer?.Email ?? "?"}"); + // Runs before the flow starts, for both leasing and booking await storeBookingEngineSettings.OrderStore.Initialise(flowContext, stateContext); @@ -686,268 +679,277 @@ public override async Task ProcessFlowRequest(BookingFlowContext OrderCalculations.AugmentOrderWithTotals( responseGenericOrder, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); - switch (responseGenericOrder) - { - case OrderProposal responseOrderProposal: - if (flowContext.Stage != FlowStage.P) - throw new OpenBookingException(new UnexpectedOrderTypeError()); - - CheckOrderIntegrity(order, responseOrderProposal); + try { + switch (responseGenericOrder) + { + case OrderProposal responseOrderProposal: + if (flowContext.Stage != FlowStage.P) + throw new OpenBookingException(new UnexpectedOrderTypeError()); - // Proposal creation is atomic - using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions - ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) - { - if (dbTransaction == null) - { - throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for OrderProposal Creation at P, to ensure the integrity of the booking made."); - } + CheckOrderIntegrity(order, responseOrderProposal); - try + // Proposal creation is atomic + using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions + ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() + : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) { - // Create the parent Order - var (version, orderProposalStatus) = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? - storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); - - responseOrderProposal.OrderProposalVersion = new Uri($"{responseOrderProposal.Id}/versions/{version}"); - responseOrderProposal.OrderProposalStatus = orderProposalStatus; - - // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call - // Note the happens first to ensure no conflicts with new OrderItems that have since been added - foreach (var g in orderItemGroups) + if (dbTransaction == null) { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for OrderProposal Creation at P, to ensure the integrity of the booking made."); } - // Book the OrderItems - foreach (var g in orderItemGroups) + try { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); + // Create the parent Order + var (version, orderProposalStatus) = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? + storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() + : await storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); + responseOrderProposal.OrderProposalVersion = new Uri($"{responseOrderProposal.Id}/versions/{version}"); + responseOrderProposal.OrderProposalStatus = orderProposalStatus; - foreach (var ctx in g.OrderItemContexts) + // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call + // Note the happens first to ensure no conflicts with new OrderItems that have since been added + foreach (var g in orderItemGroups) { - // Check that OrderItem Id was added - if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + } + + // Book the OrderItems + foreach (var g in orderItemGroups) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); + + + foreach (var ctx in g.OrderItemContexts) { - throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in ProposeOrderItems for successfully booked items"); + // Check that OrderItem Id was added + if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) + { + throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in ProposeOrderItems for successfully booked items"); + } + + // Set the orderItemStatus to null (as it must always be so in the response of P) + ctx.ResponseOrderItem.OrderItemStatus = null; } - - // Set the orderItemStatus to null (as it must always be so in the response of P) - ctx.ResponseOrderItem.OrderItemStatus = null; } - } - - // Update this in case ResponseOrderItem was overwritten in ProposeOrderItems - responseOrderProposal.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); - // Recheck for integrity given the updates - CheckOrderIntegrity(order, responseOrderProposal); + // Update this in case ResponseOrderItem was overwritten in ProposeOrderItems + responseOrderProposal.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); + // Recheck for integrity given the updates + CheckOrderIntegrity(order, responseOrderProposal); - if (dbTransaction != null) - { if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Commit().CheckSyncValueTaskWorked(); + storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await dbTransaction.Commit(); + await storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); + + if (dbTransaction != null) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + dbTransaction.Commit().CheckSyncValueTaskWorked(); + else + await dbTransaction.Commit(); + } } - } - catch - { - if (dbTransaction != null) + catch { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Rollback().CheckSyncValueTaskWorked(); - else - await dbTransaction.Rollback(); + if (dbTransaction != null) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + dbTransaction.Rollback().CheckSyncValueTaskWorked(); + else + await dbTransaction.Rollback(); + } + throw; } - throw; } - } - break; + break; - case OrderQuote responseOrderQuote: - if (!(flowContext.Stage == FlowStage.C1 || flowContext.Stage == FlowStage.C2)) - throw new OpenBookingException(new UnexpectedOrderTypeError()); + case OrderQuote responseOrderQuote: + if (!(flowContext.Stage == FlowStage.C1 || flowContext.Stage == FlowStage.C2)) + throw new OpenBookingException(new UnexpectedOrderTypeError()); - // If "payment" has been supplied unnecessarily, simply do not return it - if (responseOrderQuote.Payment != null && responseOrderQuote.TotalPaymentDue.Price.Value == 0) - { - responseOrderQuote.Payment = null; - } - - // Note behaviour here is to lease those items that are available to be leased, and return errors for everything else - // Leasing is optimistic, booking is atomic - using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions - ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) - { - try + // If "payment" has been supplied unnecessarily, simply do not return it + if (responseOrderQuote.Payment != null && responseOrderQuote.TotalPaymentDue.Price.Value == 0) { + responseOrderQuote.Payment = null; + } - responseOrderQuote.Lease = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? - storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); - - // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call - // Note the happens first to ensure no conflicts with new OrderItems that have since been added - foreach (var g in orderItemGroups) + // Note behaviour here is to lease those items that are available to be leased, and return errors for everything else + // Leasing is optimistic, booking is atomic + using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions + ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() + : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) + { + try { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); - } - if (responseOrderQuote.Lease != null) - { + responseOrderQuote.Lease = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? + storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() + : await storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); + + // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call + // Note the happens first to ensure no conflicts with new OrderItems that have since been added foreach (var g in orderItemGroups) { if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + await g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); } - } - - // Update this in case ResponseOrderItem was overwritten in Lease - responseOrderQuote.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); - // Note OrderRequiresApproval is only required during C1 and C2 - responseOrderQuote.OrderRequiresApproval = orderItemContexts.Any(x => x.RequiresApproval); + if (responseOrderQuote.Lease != null) + { + foreach (var g in orderItemGroups) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + } + } - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); + // Update this in case ResponseOrderItem was overwritten in Lease + responseOrderQuote.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); + // Note OrderRequiresApproval is only required during C1 and C2 + responseOrderQuote.OrderRequiresApproval = orderItemContexts.Any(x => x.RequiresApproval); - if (dbTransaction != null) - { if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Commit().CheckSyncValueTaskWorked(); + storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await dbTransaction.Commit(); + await storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); + + + if (dbTransaction != null) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + dbTransaction.Commit().CheckSyncValueTaskWorked(); + else + await dbTransaction.Commit(); + } } - } - catch - { - if (dbTransaction != null) + catch { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Rollback().CheckSyncValueTaskWorked(); - else - await dbTransaction.Rollback(); + if (dbTransaction != null) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + dbTransaction.Rollback().CheckSyncValueTaskWorked(); + else + await dbTransaction.Rollback(); + } + throw; } - throw; } - } - break; - - case Order responseOrder: - if (flowContext.Stage != FlowStage.B) - throw new OpenBookingException(new UnexpectedOrderTypeError()); + break; - CheckOrderIntegrity(order, responseOrder); + case Order responseOrder: + if (flowContext.Stage != FlowStage.B) + throw new OpenBookingException(new UnexpectedOrderTypeError()); - // Booking is atomic - using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions - ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) - { - if (dbTransaction == null) - { - throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for booking at B, to ensure the integrity of the booking made."); - } + CheckOrderIntegrity(order, responseOrder); - try + // Booking is atomic + using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions + ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() + : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) { - // Create the parent Order - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction); - - // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call - // Note the happens first to ensure no conflicts with new OrderItems that have since been added - foreach (var g in orderItemGroups) + if (dbTransaction == null) { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for booking at B, to ensure the integrity of the booking made."); } - // Book the OrderItems - foreach (var g in orderItemGroups) + try { + // Create the parent Order if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); + await storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction); - foreach (var ctx in g.OrderItemContexts) + // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call + // Note the happens first to ensure no conflicts with new OrderItems that have since been added + foreach (var g in orderItemGroups) { - // Check that OrderItem Id was added - if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) - { - throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in BookOrderItems for successfully booked items"); - } - - // Set the orderItemStatus to be https://openactive.io/OrderConfirmed (as it must always be so in the response of B) - ctx.ResponseOrderItem.OrderItemStatus = OrderItemStatus.OrderItemConfirmed; + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); } - } - // Update this in case ResponseOrderItem was overwritten in Book - responseOrder.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); + // Book the OrderItems + foreach (var g in orderItemGroups) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); - // Recheck integrity given the updates - CheckOrderIntegrity(order, responseOrder); + foreach (var ctx in g.OrderItemContexts) + { + // Check that OrderItem Id was added + if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) + { + throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in BookOrderItems for successfully booked items"); + } + + // Set the orderItemStatus to be https://openactive.io/OrderConfirmed (as it must always be so in the response of B) + ctx.ResponseOrderItem.OrderItemStatus = OrderItemStatus.OrderItemConfirmed; + } + } - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction); + // Update this in case ResponseOrderItem was overwritten in Book + responseOrder.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); + // Recheck integrity given the updates + CheckOrderIntegrity(order, responseOrder); - if (dbTransaction != null) - { if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Commit().CheckSyncValueTaskWorked(); + storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await dbTransaction.Commit(); + await storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction); + + + if (dbTransaction != null) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + dbTransaction.Commit().CheckSyncValueTaskWorked(); + else + await dbTransaction.Commit(); + } } - } - catch - { - if (dbTransaction != null) + catch { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Rollback().CheckSyncValueTaskWorked(); - else - await dbTransaction.Rollback(); + if (dbTransaction != null) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + dbTransaction.Rollback().CheckSyncValueTaskWorked(); + else + await dbTransaction.Rollback(); + } + throw; } - throw; } - } - break; + break; - default: - throw new OpenBookingException(new UnexpectedOrderTypeError()); + default: + throw new OpenBookingException(new UnexpectedOrderTypeError()); + } + } + catch (SilentRollbackException) { + // Catch the SilentRollbackException so it doesn't leave the method, but do nothing with it + // At this point it will have already triggered the rollback + } finally + { + Console.WriteLine($"## {flowContext.OrderId.uuid} | LEAVING CRITICAL SECTION {flowContext.Stage.ToString()} for {flowContext?.Customer?.Email ?? "?"}"); } return responseGenericOrder; From 10c11e9c0481ab5a0037af6f3c52ac4ee4806b25 Mon Sep 17 00:00:00 2001 From: nickevansuk <2616208+nickevansuk@users.noreply.github.com> Date: Tue, 31 Aug 2021 10:51:13 +0100 Subject: [PATCH 02/33] Further fixes --- .../OpenBookingHelper/IdTransforms/IdTemplate.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs b/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs index dc7f3753..ad6fbfb5 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs @@ -229,7 +229,7 @@ public virtual IBookableIdComponents GetOpportunityReference(Uri opportunityId, // Require both opportunityId and offerId to not be null if (opportunityId == null) throw new ArgumentNullException(nameof(opportunityId)); - if (offerId == null) throw new ArgumentNullException(nameof(offerId)); + // if (offerId == null) throw new ArgumentNullException(nameof(offerId)); // Without inheritance, the Offer must be resolved against either: Opportunity with Offer; or _parent_ Opportunity and parent Offer // Note that if any URL templates to be used for one of the checks below are null, the result for that check will be null From a22ba75ee821069c4e7eb3a9c63dfa2f8f757a90 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Tue, 23 Nov 2021 23:20:02 +0000 Subject: [PATCH 03/33] Update to latest OpenActive.NET --- .../BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj | 2 +- .../BookingSystem.AspNetFramework.csproj | 2 +- Examples/BookingSystem.AspNetFramework/packages.config | 2 +- .../OpenActive.FakeDatabase.NET.Tests.csproj | 2 +- .../OpenActive.FakeDatabase.NET.csproj | 2 +- OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj | 2 +- OpenActive.Server.NET/OpenActive.Server.NET.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj b/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj index a92ae1ab..81043e9b 100644 --- a/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj +++ b/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj @@ -11,7 +11,7 @@ true - + diff --git a/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj b/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj index 04f8e99c..46eaac7c 100644 --- a/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj +++ b/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj @@ -196,7 +196,7 @@ ..\..\packages\OpenActive.DatasetSite.NET.4.2.0\lib\net45\OpenActive.DatasetSite.NET.dll - ..\..\packages\OpenActive.NET.15.2.6\lib\netstandard2.0\OpenActive.NET.dll + ..\..\packages\OpenActive.NET.15.2.9\lib\netstandard2.0\OpenActive.NET.dll ..\..\packages\Schema.NET.7.0.1\lib\net461\Schema.NET.dll diff --git a/Examples/BookingSystem.AspNetFramework/packages.config b/Examples/BookingSystem.AspNetFramework/packages.config index 03ee82b7..f2ec534d 100644 --- a/Examples/BookingSystem.AspNetFramework/packages.config +++ b/Examples/BookingSystem.AspNetFramework/packages.config @@ -81,7 +81,7 @@ - + diff --git a/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj b/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj index c1f107f8..5d04e422 100644 --- a/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj +++ b/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj b/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj index 9bb8c8ee..c8cc26d5 100644 --- a/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj +++ b/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj @@ -33,7 +33,7 @@ - + diff --git a/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj b/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj index ce4c161c..55ee6551 100644 --- a/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj +++ b/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/OpenActive.Server.NET/OpenActive.Server.NET.csproj b/OpenActive.Server.NET/OpenActive.Server.NET.csproj index f08579e8..51f5cc4c 100644 --- a/OpenActive.Server.NET/OpenActive.Server.NET.csproj +++ b/OpenActive.Server.NET/OpenActive.Server.NET.csproj @@ -31,7 +31,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From 2a5f07ce26c58d3a33350cebdb7a793466bcef38 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Wed, 24 Nov 2021 00:12:24 +0000 Subject: [PATCH 04/33] Update OpenActive.NET --- .../BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj | 2 +- .../BookingSystem.AspNetFramework.csproj | 2 +- Examples/BookingSystem.AspNetFramework/packages.config | 2 +- .../OpenActive.FakeDatabase.NET.Tests.csproj | 2 +- .../OpenActive.FakeDatabase.NET.csproj | 2 +- OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj | 2 +- OpenActive.Server.NET/OpenActive.Server.NET.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj b/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj index 81043e9b..0c5bd068 100644 --- a/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj +++ b/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj @@ -11,7 +11,7 @@ true - + diff --git a/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj b/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj index 46eaac7c..f1fcd9a2 100644 --- a/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj +++ b/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj @@ -196,7 +196,7 @@ ..\..\packages\OpenActive.DatasetSite.NET.4.2.0\lib\net45\OpenActive.DatasetSite.NET.dll - ..\..\packages\OpenActive.NET.15.2.9\lib\netstandard2.0\OpenActive.NET.dll + ..\..\packages\OpenActive.NET.15.2.10\lib\netstandard2.0\OpenActive.NET.dll ..\..\packages\Schema.NET.7.0.1\lib\net461\Schema.NET.dll diff --git a/Examples/BookingSystem.AspNetFramework/packages.config b/Examples/BookingSystem.AspNetFramework/packages.config index f2ec534d..9a7e8054 100644 --- a/Examples/BookingSystem.AspNetFramework/packages.config +++ b/Examples/BookingSystem.AspNetFramework/packages.config @@ -81,7 +81,7 @@ - + diff --git a/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj b/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj index 5d04e422..b5db3fdd 100644 --- a/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj +++ b/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj b/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj index c8cc26d5..54e5a698 100644 --- a/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj +++ b/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj @@ -33,7 +33,7 @@ - + diff --git a/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj b/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj index 55ee6551..93104867 100644 --- a/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj +++ b/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/OpenActive.Server.NET/OpenActive.Server.NET.csproj b/OpenActive.Server.NET/OpenActive.Server.NET.csproj index 51f5cc4c..b2ef917a 100644 --- a/OpenActive.Server.NET/OpenActive.Server.NET.csproj +++ b/OpenActive.Server.NET/OpenActive.Server.NET.csproj @@ -31,7 +31,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From 6bbf963024315ad8dfb9702b89032fa0d78b0be8 Mon Sep 17 00:00:00 2001 From: nickevansuk <2616208+nickevansuk@users.noreply.github.com> Date: Wed, 24 Nov 2021 09:37:35 +0000 Subject: [PATCH 05/33] Remove AuthenticatedCustomer --- .../OpenBookingHelper/Beta/AuthenticatedPerson.cs | 12 ------------ .../Context/StoreBookingFlowContext.cs | 1 - .../StoreBookingEngine/StoreBookingEngine.cs | 12 ------------ 3 files changed, 25 deletions(-) delete mode 100644 OpenActive.Server.NET/OpenBookingHelper/Beta/AuthenticatedPerson.cs diff --git a/OpenActive.Server.NET/OpenBookingHelper/Beta/AuthenticatedPerson.cs b/OpenActive.Server.NET/OpenBookingHelper/Beta/AuthenticatedPerson.cs deleted file mode 100644 index a2e6e66a..00000000 --- a/OpenActive.Server.NET/OpenBookingHelper/Beta/AuthenticatedPerson.cs +++ /dev/null @@ -1,12 +0,0 @@ -using OpenActive.NET; - -namespace OpenActive.Server.NET.OpenBookingHelper.Beta -{ - /// - /// In line with the outstanding W3C discussion, this models an authenticated person, to be used - /// - public class AuthenticatedPerson : Person - { - public string authToken { get; set; } - } -} diff --git a/OpenActive.Server.NET/StoreBookingEngine/Context/StoreBookingFlowContext.cs b/OpenActive.Server.NET/StoreBookingEngine/Context/StoreBookingFlowContext.cs index d0e300e1..4e822493 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/Context/StoreBookingFlowContext.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/Context/StoreBookingFlowContext.cs @@ -22,7 +22,6 @@ public StoreBookingFlowContext(BookingFlowContext bookingFlowContext) } public ILegalEntity Customer { get; internal set; } - public AuthenticatedPerson AuthenticatedCustomer { get; internal set; } public Organization Broker { get; internal set; } public BookingService BookingService { get; internal set; } public Payment Payment { get; internal set; } diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index 7b37174f..25573e8f 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -577,11 +577,6 @@ private async Task> GetOrderItemContextGroups(List> GetOrderItemContextGroups(List Date: Wed, 24 Nov 2021 22:08:48 +0000 Subject: [PATCH 06/33] Initial additions to support Customer Accounts --- .../CustomBookingEngine.cs | 98 +++++++++++-------- .../Entrypoint/IBookingEngine.cs | 18 ++-- .../AuthenticationExtensions.cs | 15 ++- .../OpenActiveCustomClaimNames.cs | 4 + .../Context/BookingFlowContext.cs | 3 +- .../IdTransforms/DefaultComponents.cs | 24 ++--- .../OpenBookingHelper/Model/ModelSupport.cs | 8 +- .../Model/OrderModelSupport.cs | 8 +- .../OpenBookingHelper/Rpde/OrdersRpdeBase.cs | 2 +- .../OpenBookingHelper/Rpde/RpdeBase.cs | 4 +- .../Settings/BookingEngineSettings.cs | 3 +- .../OpenBookingHelper/Stores/SellerStore.cs | 12 +-- .../Context/StoreBookingFlowContext.cs | 1 + .../StoreBookingEngine/StoreBookingEngine.cs | 70 +++++++++---- .../Stores/OpportunityStore.cs | 10 +- .../StoreBookingEngine/Stores/OrderStore.cs | 28 +++--- 16 files changed, 190 insertions(+), 118 deletions(-) diff --git a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs index dda31f0d..f3d437ba 100644 --- a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs +++ b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs @@ -328,10 +328,10 @@ private async Task RenderOrdersRPDEPageForFeed(OrderType feedType, str } } - public async Task GetOrderStatus(string clientId, Uri sellerId, string uuidString) + public async Task GetOrderStatus(string clientId, Uri sellerId, string uuidString, Uri customerAccountId = null) { - var (orderId, sellerIdComponents, seller) = await ConstructIdsFromRequest(clientId, sellerId, uuidString, OrderType.Order); - var result = await ProcessGetOrderStatus(orderId, sellerIdComponents, seller); + var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.Order); + var result = await ProcessGetOrderStatus(orderId, sellerIdComponents, seller, customerAccountIdComponents); if (result == null) { throw new OpenBookingException(new UnknownOrderError()); @@ -342,7 +342,7 @@ public async Task GetOrderStatus(string clientId, Uri sellerId, } } - protected abstract Task ProcessGetOrderStatus(OrderIdComponents orderId, SellerIdComponents sellerId, ILegalEntity seller); + protected abstract Task ProcessGetOrderStatus(OrderIdComponents orderId, SimpleIdComponents sellerId, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents); protected bool IsOpportunityTypeRecognised(string opportunityTypeString) @@ -386,32 +386,32 @@ private static string GetParallelLockKey(OrderIdComponents orderId) return $"{orderId.ClientId}|{orderId.uuid}".ToUpperInvariant(); } - public async Task ProcessCheckpoint1(string clientId, Uri sellerId, string uuidString, string orderQuoteJson) + public async Task ProcessCheckpoint1(string clientId, Uri sellerId, string uuidString, string orderQuoteJson, Uri customerAccountId = null) { - return await ProcessCheckpoint(clientId, sellerId, uuidString, orderQuoteJson, FlowStage.C1, OrderType.OrderQuote); + return await ProcessCheckpoint(clientId, sellerId, uuidString, orderQuoteJson, FlowStage.C1, OrderType.OrderQuote, customerAccountId); } - public async Task ProcessCheckpoint2(string clientId, Uri sellerId, string uuidString, string orderQuoteJson) + public async Task ProcessCheckpoint2(string clientId, Uri sellerId, string uuidString, string orderQuoteJson, Uri customerAccountId = null) { - return await ProcessCheckpoint(clientId, sellerId, uuidString, orderQuoteJson, FlowStage.C2, OrderType.OrderQuote); + return await ProcessCheckpoint(clientId, sellerId, uuidString, orderQuoteJson, FlowStage.C2, OrderType.OrderQuote, customerAccountId); } - private async Task ProcessCheckpoint(string clientId, Uri sellerId, string uuidString, string orderQuoteJson, FlowStage flowStage, OrderType orderType) + private async Task ProcessCheckpoint(string clientId, Uri sellerId, string uuidString, string orderQuoteJson, FlowStage flowStage, OrderType orderType, Uri customerAccountId) { OrderQuote orderQuote = OpenActiveSerializer.Deserialize(orderQuoteJson); if (orderQuote == null || orderQuote.GetType() != typeof(OrderQuote)) { throw new OpenBookingException(new UnexpectedOrderTypeError(), "OrderQuote is required for C1 and C2"); } - var (orderId, sellerIdComponents, seller) = await ConstructIdsFromRequest(clientId, sellerId, uuidString, orderType); + var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, orderType); using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { - var orderResponse = await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, flowStage, orderQuote), orderQuote); + var orderResponse = await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, flowStage, orderQuote), orderQuote); // Return a 409 status code if any OrderItem level errors exist return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(orderResponse), orderResponse.OrderedItem.Exists(x => x.Error?.Count > 0) ? HttpStatusCode.Conflict : HttpStatusCode.OK); } } - public async Task ProcessOrderCreationB(string clientId, Uri sellerId, string uuidString, string orderJson) + public async Task ProcessOrderCreationB(string clientId, Uri sellerId, string uuidString, string orderJson, Uri customerAccountId = null) { // Note B will never contain OrderItem level errors, and any issues that occur will be thrown as exceptions. @@ -421,12 +421,12 @@ public async Task ProcessOrderCreationB(string clientId, Uri se { throw new OpenBookingException(new UnexpectedOrderTypeError(), "Order is required for B"); } - var (orderId, sellerIdComponents, seller) = await ConstructIdsFromRequest(clientId, sellerId, uuidString, OrderType.Order); + var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.Order); using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { var response = order.OrderProposalVersion != null ? - await ProcessOrderCreationFromOrderProposal(orderId, settings.OrderIdTemplate, seller, sellerIdComponents, order) : - await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, FlowStage.B, order), order); + await ProcessOrderCreationFromOrderProposal(orderId, settings.OrderIdTemplate, seller, sellerIdComponents, customerAccountIdComponents, order) : + await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, FlowStage.B, order), order); // Return a 409 status code if any OrderItem level errors exist return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(response), @@ -434,7 +434,7 @@ await ProcessOrderCreationFromOrderProposal(orderId, settings.OrderIdTemplate, s } } - public async Task ProcessOrderProposalCreationP(string clientId, Uri sellerId, string uuidString, string orderJson) + public async Task ProcessOrderProposalCreationP(string clientId, Uri sellerId, string uuidString, string orderJson, Uri customerAccountId = null) { // Note B will never contain OrderItem level errors, and any issues that occur will be thrown as exceptions. @@ -444,29 +444,42 @@ public async Task ProcessOrderProposalCreationP(string clientId { throw new OpenBookingException(new UnexpectedOrderTypeError(), "OrderProposal is required for P"); } - var (orderId, sellerIdComponents, seller) = await ConstructIdsFromRequest(clientId, sellerId, uuidString, OrderType.OrderProposal); + var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.OrderProposal); using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { - return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, FlowStage.P, order), order)), HttpStatusCode.Created); + return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, FlowStage.P, order), order)), HttpStatusCode.Created); } } - private SellerIdComponents GetSellerIdComponentsFromApiKey(Uri sellerId) + private SimpleIdComponents GetSellerIdComponentsFromApiKey(Uri sellerId) { // Return empty SellerIdComponents in Single Seller mode, as it is not required in the API Key - if (settings.HasSingleSeller == true) return new SellerIdComponents(); + if (settings.HasSingleSeller == true) return new SimpleIdComponents(); var sellerIdComponents = settings.SellerIdTemplate.GetIdComponents(sellerId); if (sellerIdComponents == null) throw new OpenBookingException(new InvalidAPITokenError()); return sellerIdComponents; } - public async Task DeleteOrder(string clientId, Uri sellerId, string uuidString) + private SimpleIdComponents GetCustomerAccountIdComponentsFromApiKey(Uri customerAccountId) + { + // Ignore null (if not authenticated with a Customer Account) + if (customerAccountId == null) return null; + + // Return empty SellerIdComponents in Single Seller mode, as it is not required in the API Key + if (settings.CustomerAccountIdTemplate == null) throw new OpenBookingException(new InternalLibraryConfigurationError(), "CustomerAccount bookings are not enabled. Please set a CustomerAccountIdTemplate."); + + var customerAccountIdComponents = settings.CustomerAccountIdTemplate.GetIdComponents(customerAccountId); + if (customerAccountIdComponents == null) throw new OpenBookingException(new InvalidAPITokenError()); + return customerAccountIdComponents; + } + + public async Task DeleteOrder(string clientId, Uri sellerId, string uuidString, Uri customerAccountId = null) { var orderId = new OrderIdComponents { ClientId = clientId, OrderType = OrderType.Order, uuid = ConvertToGuid(uuidString) }; using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { - var result = await ProcessOrderDeletion(orderId, GetSellerIdComponentsFromApiKey(sellerId)); + var result = await ProcessOrderDeletion(orderId, GetSellerIdComponentsFromApiKey(sellerId), GetCustomerAccountIdComponentsFromApiKey(customerAccountId)); switch (result) { case DeleteOrderResult.OrderSuccessfullyDeleted: @@ -479,27 +492,28 @@ public async Task DeleteOrder(string clientId, Uri sellerId, st } } - protected abstract Task ProcessOrderDeletion(OrderIdComponents orderId, SellerIdComponents sellerId); + protected abstract Task ProcessOrderDeletion(OrderIdComponents orderId, SimpleIdComponents sellerId, SimpleIdComponents customerAccountId); - public async Task DeleteOrderQuote(string clientId, Uri sellerId, string uuidString) + public async Task DeleteOrderQuote(string clientId, Uri sellerId, string uuidString, Uri customerAccountId = null) { var orderId = new OrderIdComponents { ClientId = clientId, OrderType = OrderType.OrderQuote, uuid = ConvertToGuid(uuidString) }; using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { - await ProcessOrderQuoteDeletion(orderId, GetSellerIdComponentsFromApiKey(sellerId)); + await ProcessOrderQuoteDeletion(orderId, GetSellerIdComponentsFromApiKey(sellerId), GetCustomerAccountIdComponentsFromApiKey(customerAccountId)); return ResponseContent.OpenBookingNoContentResponse(); } } - protected abstract Task ProcessOrderQuoteDeletion(OrderIdComponents orderId, SellerIdComponents sellerId); + protected abstract Task ProcessOrderQuoteDeletion(OrderIdComponents orderId, SimpleIdComponents sellerId, SimpleIdComponents customerAccountId); - public async Task ProcessOrderUpdate(string clientId, Uri sellerId, string uuidString, string orderJson) + public async Task ProcessOrderUpdate(string clientId, Uri sellerId, string uuidString, string orderJson, Uri customerAccountId = null) { var orderId = new OrderIdComponents { ClientId = clientId, OrderType = OrderType.Order, uuid = ConvertToGuid(uuidString) }; using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { Order order = OpenActiveSerializer.Deserialize(orderJson); - SellerIdComponents sellerIdComponents = GetSellerIdComponentsFromApiKey(sellerId); + SimpleIdComponents sellerIdComponents = GetSellerIdComponentsFromApiKey(sellerId); + SimpleIdComponents customerAccountIdComponents = GetCustomerAccountIdComponentsFromApiKey(customerAccountId); if (order == null || order.GetType() != typeof(Order)) { @@ -544,22 +558,23 @@ public async Task ProcessOrderUpdate(string clientId, Uri selle throw new OpenBookingException(new OrderItemNotWithinOrderError()); } - await ProcessCustomerCancellation(orderId, sellerIdComponents, settings.OrderIdTemplate, orderItemIds); + await ProcessCustomerCancellation(orderId, sellerIdComponents, customerAccountIdComponents, settings.OrderIdTemplate, orderItemIds); return ResponseContent.OpenBookingNoContentResponse(); } } - public abstract Task ProcessCustomerCancellation(OrderIdComponents orderId, SellerIdComponents sellerId, OrderIdTemplate orderIdTemplate, List orderItemIds); + public abstract Task ProcessCustomerCancellation(OrderIdComponents orderId, SimpleIdComponents sellerId, SimpleIdComponents customerAccountId, OrderIdTemplate orderIdTemplate, List orderItemIds); - public async Task ProcessOrderProposalUpdate(string clientId, Uri sellerId, string uuidString, string orderProposalJson) + public async Task ProcessOrderProposalUpdate(string clientId, Uri sellerId, string uuidString, string orderProposalJson, Uri customerAccountId = null) { var orderId = new OrderIdComponents { ClientId = clientId, OrderType = OrderType.OrderProposal, uuid = ConvertToGuid(uuidString) }; using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { OrderProposal orderProposal = OpenActiveSerializer.Deserialize(orderProposalJson); - SellerIdComponents sellerIdComponents = GetSellerIdComponentsFromApiKey(sellerId); + SimpleIdComponents sellerIdComponents = GetSellerIdComponentsFromApiKey(sellerId); + SimpleIdComponents customerAccountIdComponents = GetCustomerAccountIdComponentsFromApiKey(customerAccountId); if (orderProposal == null || orderProposal.GetType() != typeof(Order)) { @@ -583,13 +598,13 @@ public async Task ProcessOrderProposalUpdate(string clientId, U throw new OpenBookingException(new PatchNotAllowedOnPropertyError(), "Only 'https://openactive.io/CustomerRejected' is permitted for this property."); } - await ProcessOrderProposalCustomerRejection(orderId, sellerIdComponents, settings.OrderIdTemplate); + await ProcessOrderProposalCustomerRejection(orderId, sellerIdComponents, customerAccountIdComponents, settings.OrderIdTemplate); return ResponseContent.OpenBookingNoContentResponse(); } } - public abstract Task ProcessOrderProposalCustomerRejection(OrderIdComponents orderId, SellerIdComponents sellerId, OrderIdTemplate orderIdTemplate); + public abstract Task ProcessOrderProposalCustomerRejection(OrderIdComponents orderId, SimpleIdComponents sellerId, SimpleIdComponents customerAccountId, OrderIdTemplate orderIdTemplate); async Task IBookingEngine.InsertTestOpportunity(string testDatasetIdentifier, string eventJson) @@ -704,7 +719,7 @@ async Task IBookingEngine.InsertTestOpportunity(string testData return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(createdEvent), HttpStatusCode.OK); } - protected abstract Task InsertTestOpportunity(string testDatasetIdentifier, OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, SellerIdComponents seller); + protected abstract Task InsertTestOpportunity(string testDatasetIdentifier, OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, SimpleIdComponents seller); async Task IBookingEngine.DeleteTestDataset(string testDatasetIdentifier) { @@ -736,7 +751,7 @@ async Task IBookingEngine.TriggerTestAction(string actionJson) protected abstract Task TriggerTestAction(OpenBookingSimulateAction simulateAction, OrderIdTemplate orderIdTemplate); - private async Task<(OrderIdComponents orderId, SellerIdComponents sellerIdComponents, ILegalEntity seller)> ConstructIdsFromRequest(string clientId, Uri authenticationSellerId, string uuidString, OrderType orderType) + private async Task<(OrderIdComponents orderId, SimpleIdComponents sellerIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents)> ConstructIdsFromRequest(string clientId, Uri authenticationSellerId, Uri authenticationCustomerAccountId, string uuidString, OrderType orderType) { var orderId = new OrderIdComponents { @@ -747,7 +762,9 @@ async Task IBookingEngine.TriggerTestAction(string actionJson) // TODO: Add more request validation rules here - SellerIdComponents sellerIdComponents = GetSellerIdComponentsFromApiKey(authenticationSellerId); + SimpleIdComponents sellerIdComponents = GetSellerIdComponentsFromApiKey(authenticationSellerId); + + SimpleIdComponents customerAccountIdComponents = GetCustomerAccountIdComponentsFromApiKey(authenticationCustomerAccountId); ILegalEntity seller = await settings.SellerStore.GetSellerById(sellerIdComponents); @@ -756,11 +773,11 @@ async Task IBookingEngine.TriggerTestAction(string actionJson) throw new OpenBookingException(new SellerNotFoundError()); } - return (orderId, sellerIdComponents, seller); + return (orderId, sellerIdComponents, seller, customerAccountIdComponents); } //TODO: Should we move Seller into the Abstract level? Perhaps too much complexity - protected BookingFlowContext ValidateFlowRequest(OrderIdComponents orderId, SellerIdComponents sellerIdComponents, ILegalEntity seller, FlowStage stage, TOrder order) where TOrder : Order, new() + protected BookingFlowContext ValidateFlowRequest(OrderIdComponents orderId, SimpleIdComponents sellerIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents, FlowStage stage, TOrder order) where TOrder : Order, new() { // If being called from Order Status then expect Seller to already be a full object var sellerIdFromOrder = stage == FlowStage.OrderStatus ? order?.Seller.Object?.Id : order?.Seller.IdReference; @@ -826,6 +843,7 @@ async Task IBookingEngine.TriggerTestAction(string actionJson) OrderIdTemplate = settings.OrderIdTemplate, Seller = seller, SellerId = sellerIdComponents, + CustomerAccountId = customerAccountIdComponents, TaxPayeeRelationship = taxPayeeRelationship, Payer = payer, BrokerRole = order.BrokerRole.Value @@ -834,7 +852,7 @@ async Task IBookingEngine.TriggerTestAction(string actionJson) public abstract Task ProcessFlowRequest(BookingFlowContext request, TOrder order) where TOrder : Order, new(); - public abstract Task ProcessOrderCreationFromOrderProposal(OrderIdComponents orderId, OrderIdTemplate orderIdTemplate, ILegalEntity seller, SellerIdComponents sellerId, Order order); + public abstract Task ProcessOrderCreationFromOrderProposal(OrderIdComponents orderId, OrderIdTemplate orderIdTemplate, ILegalEntity seller, SimpleIdComponents sellerId, SimpleIdComponents customerAccountIdComponents, Order order); } } diff --git a/OpenActive.Server.NET/Entrypoint/IBookingEngine.cs b/OpenActive.Server.NET/Entrypoint/IBookingEngine.cs index a48efdb3..5c570e91 100644 --- a/OpenActive.Server.NET/Entrypoint/IBookingEngine.cs +++ b/OpenActive.Server.NET/Entrypoint/IBookingEngine.cs @@ -20,14 +20,15 @@ public interface IBookingEngine Task GetOpenDataRPDEPageForFeed(string feedname, string afterTimestamp, string afterId, string afterChangeNumber); // These endpoints are authenticated by seller credentials (OAuth Authorization Code Grant) - Task ProcessCheckpoint1(string clientId, Uri sellerId, string uuid, string orderQuoteJson); - Task ProcessCheckpoint2(string clientId, Uri sellerId, string uuid, string orderQuoteJson); - Task ProcessOrderCreationB(string clientId, Uri sellerId, string uuid, string orderJson); - Task ProcessOrderProposalCreationP(string clientId, Uri sellerId, string uuid, string orderJson); - Task DeleteOrder(string clientId, Uri sellerId, string uuid); - Task DeleteOrderQuote(string clientId, Uri sellerId, string uuid); - Task ProcessOrderUpdate(string clientId, Uri sellerId, string uuid, string orderJson); - Task ProcessOrderProposalUpdate(string clientId, Uri sellerId, string uuid, string orderJson); + Task ProcessCheckpoint1(string clientId, Uri sellerId, string uuid, string orderQuoteJson, Uri customerAccountId = null); + Task ProcessCheckpoint2(string clientId, Uri sellerId, string uuid, string orderQuoteJson, Uri customerAccountId = null); + Task ProcessOrderCreationB(string clientId, Uri sellerId, string uuid, string orderJson, Uri customerAccountId = null); + Task ProcessOrderProposalCreationP(string clientId, Uri sellerId, string uuid, string orderJson, Uri customerAccountId = null); + Task DeleteOrder(string clientId, Uri sellerId, string uuid, Uri customerAccountId = null); + Task DeleteOrderQuote(string clientId, Uri sellerId, string uuid, Uri customerAccountId = null); + Task ProcessOrderUpdate(string clientId, Uri sellerId, string uuid, string orderJson, Uri customerAccountId = null); + Task ProcessOrderProposalUpdate(string clientId, Uri sellerId, string uuid, string orderJson, Uri customerAccountId = null); + Task GetOrderStatus(string clientId, Uri sellerId, string uuid, Uri customerAccountId = null); // These endpoints are authenticated by client credentials (OAuth Client Credentials Grant) Task InsertTestOpportunity(string testDatasetIdentifier, string eventJson); @@ -37,6 +38,5 @@ public interface IBookingEngine Task GetOrdersRPDEPageForFeed(string clientId, long? afterTimestamp, string afterId, long? afterChangeNumber); Task GetOrderProposalsRPDEPageForFeed(string clientId, string afterTimestamp, string afterId, string afterChangeNumber); Task GetOrderProposalsRPDEPageForFeed(string clientId, long? afterTimestamp, string afterId, long? afterChangeNumber); - Task GetOrderStatus(string clientId, Uri sellerId, string uuid); } } \ No newline at end of file diff --git a/OpenActive.Server.NET/OpenBookingHelper/Authentication/AuthenticationExtensions.cs b/OpenActive.Server.NET/OpenBookingHelper/Authentication/AuthenticationExtensions.cs index 25616f4d..b5fe435d 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Authentication/AuthenticationExtensions.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Authentication/AuthenticationExtensions.cs @@ -39,18 +39,29 @@ public static string GetSellerId(this ClaimsPrincipal principal) return principal?.FindFirst(x => x.Type == OpenActiveCustomClaimNames.SellerId)?.Value; } + /// + /// Gets the GetCustomerAccountId custom claim from the JWT + /// + /// + /// + public static string GetCustomerAccountId(this ClaimsPrincipal principal) + { + return principal?.FindFirst(x => x.Type == OpenActiveCustomClaimNames.CustomerAccountId)?.Value; + } + /// /// Gets the SellerId and ClientId custom claims from the JWT /// /// /// - public static (string clientId, Uri sellerId) GetAccessTokenOpenBookingClaims(this ClaimsPrincipal principal) + public static (string ClientId, Uri SellerId, Uri CustomerAccountId) GetAccessTokenOpenBookingClaims(this ClaimsPrincipal principal) { var clientId = principal.GetClientId(); var sellerId = principal.GetSellerId().ParseUrlOrNull(); + var customerAccountId = principal.GetCustomerAccountId().ParseUrlOrNull(); if (clientId != null && sellerId != null) { - return (clientId, sellerId); + return (clientId, sellerId, customerAccountId); } else { diff --git a/OpenActive.Server.NET/OpenBookingHelper/Authentication/OpenActiveCustomClaimNames.cs b/OpenActive.Server.NET/OpenBookingHelper/Authentication/OpenActiveCustomClaimNames.cs index f148a8c0..85ae185e 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Authentication/OpenActiveCustomClaimNames.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Authentication/OpenActiveCustomClaimNames.cs @@ -10,5 +10,9 @@ public static class OpenActiveCustomClaimNames /// Seller Id Custom Claim Name /// public const string SellerId = "https://openactive.io/sellerId"; + /// + /// Customer Account Id Custom Claim Name + /// + public const string CustomerAccountId = "https://openactive.io/customerAccountId"; } } diff --git a/OpenActive.Server.NET/OpenBookingHelper/Context/BookingFlowContext.cs b/OpenActive.Server.NET/OpenBookingHelper/Context/BookingFlowContext.cs index 3aa13a76..60055484 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Context/BookingFlowContext.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Context/BookingFlowContext.cs @@ -10,7 +10,8 @@ public class BookingFlowContext public TaxPayeeRelationship TaxPayeeRelationship { get; internal set; } public ILegalEntity Payer { get; internal set; } public ILegalEntity Seller { get; internal set; } - public SellerIdComponents SellerId { get; internal set; } + public SimpleIdComponents SellerId { get; internal set; } + public SimpleIdComponents CustomerAccountId { get; internal set; } public BrokerType BrokerRole { get; internal set; } } } diff --git a/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/DefaultComponents.cs b/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/DefaultComponents.cs index a13a0f21..16c316db 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/DefaultComponents.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/DefaultComponents.cs @@ -5,17 +5,19 @@ namespace OpenActive.Server.NET.OpenBookingHelper { // Note in future we may make these more flexible (and configurable), but for now they are set for the simple case - public class SellerIdComponents : IEquatable + public class SimpleIdComponents : IEquatable { - public long? SellerIdLong { get; set; } - public string SellerIdString { get; set; } + public long? IdLong { get; set; } + public Guid? IdGuid { get; set; } + public string IdString { get; set; } - public bool Equals(SellerIdComponents other) + public bool Equals(SimpleIdComponents other) { if (ReferenceEquals(other, null)) return false; - if (other.SellerIdLong != null && this.SellerIdLong != null) return other.SellerIdLong == this.SellerIdLong; - if (other.SellerIdString != null && this.SellerIdString != null) return other.SellerIdString == this.SellerIdString; - if (other.SellerIdString == null && this.SellerIdString == null && other.SellerIdLong == null && this.SellerIdLong == null) return true; + if (other.IdLong != null && this.IdLong != null) return other.IdLong == this.IdLong; + if (other.IdGuid != null && this.IdGuid != null) return other.IdGuid == this.IdGuid; + if (other.IdString != null && this.IdString != null) return other.IdString == this.IdString; + if (other.IdString == null && this.IdString == null && other.IdLong == null && this.IdLong == null) return true; return false; } @@ -27,7 +29,7 @@ public bool Equals(SellerIdComponents other) /// /// The result of the operator. /// - public static bool operator ==(SellerIdComponents left, SellerIdComponents right) { + public static bool operator ==(SimpleIdComponents left, SimpleIdComponents right) { if (ReferenceEquals(left, right)) { return true; @@ -53,7 +55,7 @@ public bool Equals(SellerIdComponents other) /// /// The result of the operator. /// - public static bool operator !=(SellerIdComponents left, SellerIdComponents right) => !(left == right); + public static bool operator !=(SimpleIdComponents left, SimpleIdComponents right) => !(left == right); /// /// Determines whether the specified , is equal to this instance. @@ -62,7 +64,7 @@ public bool Equals(SellerIdComponents other) /// /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) => this.Equals(obj as SellerIdComponents); + public override bool Equals(object obj) => this.Equals(obj as SimpleIdComponents); /// /// Returns a hash code for this instance. @@ -70,7 +72,7 @@ public bool Equals(SellerIdComponents other) /// /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. /// - public override int GetHashCode() => Schema.NET.HashCode.Of(this.SellerIdLong).And(this.SellerIdString); + public override int GetHashCode() => Schema.NET.HashCode.Of(this.IdLong).And(this.IdString); } public class OrderIdComponents diff --git a/OpenActive.Server.NET/OpenBookingHelper/Model/ModelSupport.cs b/OpenActive.Server.NET/OpenBookingHelper/Model/ModelSupport.cs index 149af1c2..524bf29c 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Model/ModelSupport.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Model/ModelSupport.cs @@ -6,22 +6,22 @@ namespace OpenActive.Server.NET.OpenBookingHelper public class ModelSupport where TComponents : class, IBookableIdComponents, new() { private BookablePairIdTemplate IdTemplate { get; set; } - public SingleIdTemplate SellerIdTemplate { get; private set; } + public SingleIdTemplate SellerIdTemplate { get; private set; } - protected internal void SetConfiguration(BookablePairIdTemplate template, SingleIdTemplate sellerTemplate) + protected internal void SetConfiguration(BookablePairIdTemplate template, SingleIdTemplate sellerTemplate) { this.IdTemplate = template; this.SellerIdTemplate = sellerTemplate; } - protected Uri RenderSellerId(SellerIdComponents components) + protected Uri RenderSellerId(SimpleIdComponents components) { return SellerIdTemplate.RenderId(components); } protected Uri RenderSingleSellerId() { - return SellerIdTemplate.RenderId(new SellerIdComponents()); + return SellerIdTemplate.RenderId(new SimpleIdComponents()); } /// diff --git a/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs b/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs index cdf686d2..9c8dae30 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs @@ -5,9 +5,9 @@ namespace OpenActive.Server.NET.OpenBookingHelper public class OrdersModelSupport { protected OrderIdTemplate OrderIdTemplate { get; set; } - private SingleIdTemplate SellerIdTemplate { get; set; } + private SingleIdTemplate SellerIdTemplate { get; set; } - protected internal void SetConfiguration(OrderIdTemplate orderIdTemplate, SingleIdTemplate sellerIdTemplate) + protected internal void SetConfiguration(OrderIdTemplate orderIdTemplate, SingleIdTemplate sellerIdTemplate) { this.OrderIdTemplate = orderIdTemplate; this.SellerIdTemplate = sellerIdTemplate; @@ -32,14 +32,14 @@ protected Uri RenderOrderItemId(OrderType orderType, Guid uuid, long orderItemId return this.OrderIdTemplate.RenderOrderItemId(orderType, uuid, orderItemId); } - protected Uri RenderSellerId(SellerIdComponents sellerIdComponents) + protected Uri RenderSellerId(SimpleIdComponents sellerIdComponents) { return this.SellerIdTemplate.RenderId(sellerIdComponents); } protected Uri RenderSingleSellerId() { - return this.SellerIdTemplate.RenderId(new SellerIdComponents()); + return this.SellerIdTemplate.RenderId(new SimpleIdComponents()); } } } diff --git a/OpenActive.Server.NET/OpenBookingHelper/Rpde/OrdersRpdeBase.cs b/OpenActive.Server.NET/OpenBookingHelper/Rpde/OrdersRpdeBase.cs index 165f76bf..584a592e 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Rpde/OrdersRpdeBase.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Rpde/OrdersRpdeBase.cs @@ -12,7 +12,7 @@ public abstract class OrdersRPDEFeedGenerator : OrdersModelSupport, IRpdeFeedGen protected Uri FeedUrl { get; private set; } - internal void SetConfiguration(int rpdePageSize, OrderIdTemplate orderIdTemplate, SingleIdTemplate sellerIdTemplate, Uri ordersFeedBaseUrl, OrderType feedType) + internal void SetConfiguration(int rpdePageSize, OrderIdTemplate orderIdTemplate, SingleIdTemplate sellerIdTemplate, Uri ordersFeedBaseUrl, OrderType feedType) { base.SetConfiguration(orderIdTemplate, sellerIdTemplate); diff --git a/OpenActive.Server.NET/OpenBookingHelper/Rpde/RpdeBase.cs b/OpenActive.Server.NET/OpenBookingHelper/Rpde/RpdeBase.cs index 0e82ad9f..5daf89cc 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Rpde/RpdeBase.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Rpde/RpdeBase.cs @@ -11,7 +11,7 @@ namespace OpenActive.Server.NET.OpenBookingHelper public interface IOpportunityDataRpdeFeedGenerator : IRpdeFeedGenerator { string FeedPath { get; } - void SetConfiguration(OpportunityTypeConfiguration opportunityTypeConfiguration, Uri jsonLdIdBaseUrl, int rpdePageSize, IBookablePairIdTemplate bookablePairIdTemplate, SingleIdTemplate sellerTemplate, Uri openDataFeedBaseUrl); + void SetConfiguration(OpportunityTypeConfiguration opportunityTypeConfiguration, Uri jsonLdIdBaseUrl, int rpdePageSize, IBookablePairIdTemplate bookablePairIdTemplate, SingleIdTemplate sellerTemplate, Uri openDataFeedBaseUrl); } public abstract class OpportunityDataRpdeFeedGenerator : ModelSupport, IOpportunityDataRpdeFeedGenerator where TComponents : class, IBookableIdComponents, new() where TClass : Schema.NET.Thing @@ -20,7 +20,7 @@ public interface IOpportunityDataRpdeFeedGenerator : IRpdeFeedGenerator public virtual Uri FeedUrl { get; protected set; } public virtual string FeedPath { get; protected set; } - public void SetConfiguration(OpportunityTypeConfiguration opportunityTypeConfiguration, Uri jsonLdIdBaseUrl, int rpdePageSize, IBookablePairIdTemplate bookablePairIdTemplate, SingleIdTemplate sellerTemplate, Uri openDataFeedBaseUrl) + public void SetConfiguration(OpportunityTypeConfiguration opportunityTypeConfiguration, Uri jsonLdIdBaseUrl, int rpdePageSize, IBookablePairIdTemplate bookablePairIdTemplate, SingleIdTemplate sellerTemplate, Uri openDataFeedBaseUrl) { if (!(bookablePairIdTemplate is BookablePairIdTemplate)) { diff --git a/OpenActive.Server.NET/OpenBookingHelper/Settings/BookingEngineSettings.cs b/OpenActive.Server.NET/OpenBookingHelper/Settings/BookingEngineSettings.cs index 51d52ef4..ec944a3e 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Settings/BookingEngineSettings.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Settings/BookingEngineSettings.cs @@ -19,7 +19,8 @@ public class BookingEngineSettings /// public List IdConfiguration { get; set; } public OrderIdTemplate OrderIdTemplate { get; set; } - public SingleIdTemplate SellerIdTemplate { get; set; } + public SingleIdTemplate SellerIdTemplate { get; set; } + public SingleIdTemplate CustomerAccountIdTemplate { get; set; } public Dictionary OpenDataFeeds { get; set; } public int RPDEPageSize { get; set; } = 500; public Uri JsonLdIdBaseUrl { get; set; } diff --git a/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs b/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs index d0601b94..e2ce1432 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs @@ -7,30 +7,30 @@ namespace OpenActive.Server.NET.OpenBookingHelper { public abstract class SellerStore { - private SingleIdTemplate IdTemplate { get; set; } + private SingleIdTemplate IdTemplate { get; set; } - internal void SetConfiguration(SingleIdTemplate template) + internal void SetConfiguration(SingleIdTemplate template) { this.IdTemplate = template; } - protected Uri RenderSellerId(SellerIdComponents sellerIdComponents) + protected Uri RenderSellerId(SimpleIdComponents sellerIdComponents) { return this.IdTemplate.RenderId(sellerIdComponents); } protected Uri RenderSingleSellerId() { - return this.IdTemplate.RenderId(new SellerIdComponents()); + return this.IdTemplate.RenderId(new SimpleIdComponents()); } - internal async ValueTask GetSellerById(SellerIdComponents sellerIdComponents) + internal async ValueTask GetSellerById(SimpleIdComponents sellerIdComponents) { // TODO: Include validation on the OrderItem created, to ensure it includes all the required fields return await GetSeller(sellerIdComponents); } - protected abstract ValueTask GetSeller(SellerIdComponents sellerId); + protected abstract ValueTask GetSeller(SimpleIdComponents sellerId); } } \ No newline at end of file diff --git a/OpenActive.Server.NET/StoreBookingEngine/Context/StoreBookingFlowContext.cs b/OpenActive.Server.NET/StoreBookingEngine/Context/StoreBookingFlowContext.cs index 4e822493..736d6f7b 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/Context/StoreBookingFlowContext.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/Context/StoreBookingFlowContext.cs @@ -18,6 +18,7 @@ public StoreBookingFlowContext(BookingFlowContext bookingFlowContext) base.Payer = bookingFlowContext.Payer; base.Seller = bookingFlowContext.Seller; base.SellerId = bookingFlowContext.SellerId; + base.CustomerAccountId = bookingFlowContext.CustomerAccountId; base.BrokerRole = bookingFlowContext.BrokerRole; } diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index 25573e8f..f44d9e98 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -165,7 +165,7 @@ public void SetResponseOrderItemAsSkeleton() }; } - public void SetResponseOrderItem(OrderItem item, SellerIdComponents sellerId, StoreBookingFlowContext flowContext) + public void SetResponseOrderItem(OrderItem item, SimpleIdComponents sellerId, StoreBookingFlowContext flowContext) { if (item == null) throw new ArgumentNullException(nameof(item)); var requestOrderItemId = RequestOrderItem?.OrderedItem.IdReference; @@ -298,7 +298,7 @@ public StoreBookingEngine(BookingEngineSettings settings, DatasetSiteGeneratorSe private readonly Dictionary storeRouting; private readonly StoreBookingEngineSettings storeBookingEngineSettings; - protected override async Task InsertTestOpportunity(string testDatasetIdentifier, OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, SellerIdComponents seller) + protected override async Task InsertTestOpportunity(string testDatasetIdentifier, OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, SimpleIdComponents seller) { if (!storeRouting.ContainsKey(opportunityType)) throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "Specified opportunity type is not configured as bookable in the StoreBookingEngine constructor."); @@ -357,7 +357,7 @@ protected override async Task TriggerTestAction(OpenBookingSimulateAction simula } - public override async Task ProcessCustomerCancellation(OrderIdComponents orderId, SellerIdComponents sellerId, OrderIdTemplate orderIdTemplate, List orderItemIds) + public override async Task ProcessCustomerCancellation(OrderIdComponents orderId, SimpleIdComponents sellerId, SimpleIdComponents customerAccountIdComponents, OrderIdTemplate orderIdTemplate, List orderItemIds) { if (!await storeBookingEngineSettings.OrderStore.CustomerCancelOrderItems(orderId, sellerId, orderItemIds)) { @@ -365,7 +365,7 @@ public override async Task ProcessCustomerCancellation(OrderIdComponents orderId } } - public override async Task ProcessOrderProposalCustomerRejection(OrderIdComponents orderId, SellerIdComponents sellerId, OrderIdTemplate orderIdTemplate) + public override async Task ProcessOrderProposalCustomerRejection(OrderIdComponents orderId, SimpleIdComponents sellerId, SimpleIdComponents customerAccountIdComponents, OrderIdTemplate orderIdTemplate) { if (!await storeBookingEngineSettings.OrderStore.CustomerRejectOrderProposal(orderId, sellerId)) { @@ -373,12 +373,12 @@ public override async Task ProcessOrderProposalCustomerRejection(OrderIdComponen } } - protected override async Task ProcessOrderDeletion(OrderIdComponents orderId, SellerIdComponents sellerId) + protected override async Task ProcessOrderDeletion(OrderIdComponents orderId, SimpleIdComponents sellerId, SimpleIdComponents customerAccountIdComponents) { return await storeBookingEngineSettings.OrderStore.DeleteOrder(orderId, sellerId); } - protected override async Task ProcessOrderQuoteDeletion(OrderIdComponents orderId, SellerIdComponents sellerId) + protected override async Task ProcessOrderQuoteDeletion(OrderIdComponents orderId, SimpleIdComponents sellerId, SimpleIdComponents customerAccountIdComponents) { await storeBookingEngineSettings.OrderStore.DeleteLease(orderId, sellerId); } @@ -421,13 +421,13 @@ private static void CheckOrderIntegrity(Order requestOrder, Order responseOrder) } } - protected override async Task ProcessGetOrderStatus(OrderIdComponents orderId, SellerIdComponents sellerIdComponents, ILegalEntity seller) + protected override async Task ProcessGetOrderStatus(OrderIdComponents orderId, SimpleIdComponents sellerIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents) { // Get Order without OrderItems expanded var order = await storeBookingEngineSettings.OrderStore.GetOrderStatus(orderId, sellerIdComponents, seller); // Get flowContext from resulting Order, treating it like a request (which also validates it like a request) - var flowContext = AugmentContextFromOrder(ValidateFlowRequest(orderId, sellerIdComponents, seller, FlowStage.OrderStatus, order), order); + var flowContext = AugmentContextFromOrder(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, FlowStage.OrderStatus, order), order); // Expand OrderItems based on the flowContext // Get contexts from OrderItems @@ -456,13 +456,13 @@ protected override async Task ProcessGetOrderStatus(OrderIdComponents ord return order; } - public override async Task ProcessOrderCreationFromOrderProposal(OrderIdComponents orderId, OrderIdTemplate orderIdTemplate, ILegalEntity seller, SellerIdComponents sellerId, Order order) + public override async Task ProcessOrderCreationFromOrderProposal(OrderIdComponents orderId, OrderIdTemplate orderIdTemplate, ILegalEntity seller, SimpleIdComponents sellerId, SimpleIdComponents customerAccountIdComponents, Order order) { if (!await storeBookingEngineSettings.OrderStore.CreateOrderFromOrderProposal(orderId, sellerId, order.OrderProposalVersion, order)) { throw new OpenBookingException(new OrderProposalVersionOutdatedError()); } - return await ProcessGetOrderStatus(orderId, sellerId, seller); + return await ProcessGetOrderStatus(orderId, sellerId, seller, customerAccountIdComponents); } private List GetOrderItemContexts(List sourceOrderItems) @@ -574,16 +574,50 @@ private async Task> GetOrderItemContextGroups(List( + "{+BaseUrl}/api/customer-accounts/{IdGuid}" + ); + // Hack to add CustomerAccountId + context.CustomerAccountId = template.GetIdComponents(order.Customer.HasAccount.IdReference); + } + else + { + // Reflect back only those customer fields that are supported + switch (order.Customer) + { + case Person person: + context.Customer = storeBookingEngineSettings.CustomerPersonSupportedFields(person); + break; + + case Organization organization: + context.Customer = storeBookingEngineSettings.CustomerOrganizationSupportedFields(organization); + break; + } } // Throw error on incomplete broker details diff --git a/OpenActive.Server.NET/StoreBookingEngine/Stores/OpportunityStore.cs b/OpenActive.Server.NET/StoreBookingEngine/Stores/OpportunityStore.cs index eb741b78..da2deab5 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/Stores/OpportunityStore.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/Stores/OpportunityStore.cs @@ -10,9 +10,9 @@ namespace OpenActive.Server.NET.StoreBooking { public interface IOpportunityStore { - void SetConfiguration(IBookablePairIdTemplate template, SingleIdTemplate sellerTemplate); + void SetConfiguration(IBookablePairIdTemplate template, SingleIdTemplate sellerTemplate); Task GetOrderItems(List orderItemContexts, StoreBookingFlowContext flowContext, IStateContext stateContext); - Task CreateOpportunityWithinTestDataset(string testDatasetIdentifier, OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, SellerIdComponents seller); + Task CreateOpportunityWithinTestDataset(string testDatasetIdentifier, OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, SimpleIdComponents seller); Task DeleteTestDataset(string testDatasetIdentifier); Task TriggerTestAction(OpenBookingSimulateAction simulateAction, IBookableIdComponents idComponents); @@ -37,7 +37,7 @@ public interface IOpportunityStore public abstract class OpportunityStore : ModelSupport, IOpportunityStore where TComponents : class, IBookableIdComponents, new() where TDatabaseTransaction : IDatabaseTransaction where TStateContext : IStateContext { // async methoids that are never called in transactions - void IOpportunityStore.SetConfiguration(IBookablePairIdTemplate template, SingleIdTemplate sellerTemplate) + void IOpportunityStore.SetConfiguration(IBookablePairIdTemplate template, SingleIdTemplate sellerTemplate) { if (template as BookablePairIdTemplate == null) { @@ -53,7 +53,7 @@ Task IOpportunityStore.GetOrderItems(List orderItemContexts, return GetOrderItems(ConvertToSpecificComponents(orderItemContexts), flowContext, (TStateContext)stateContext); } - async Task IOpportunityStore.CreateOpportunityWithinTestDataset(string testDatasetIdentifier, OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, SellerIdComponents seller) + async Task IOpportunityStore.CreateOpportunityWithinTestDataset(string testDatasetIdentifier, OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, SimpleIdComponents seller) { var components = await CreateOpportunityWithinTestDataset(testDatasetIdentifier, opportunityType, criteria, openBookingFlow, seller); return OrderCalculations.RenderOpportunityWithOnlyId(opportunityType, RenderOpportunityId(components)); @@ -87,7 +87,7 @@ protected List> ConvertToSpecificComponents(List> orderItemContexts, StoreBookingFlowContext flowContext, TStateContext stateContext); - protected abstract Task CreateOpportunityWithinTestDataset(string testDatasetIdentifier, OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, SellerIdComponents seller); + protected abstract Task CreateOpportunityWithinTestDataset(string testDatasetIdentifier, OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, SimpleIdComponents seller); protected abstract Task DeleteTestDataset(string testDatasetIdentifier); protected abstract Task TriggerTestAction(OpenBookingSimulateAction simulateAction, TComponents idComponents); diff --git a/OpenActive.Server.NET/StoreBookingEngine/Stores/OrderStore.cs b/OpenActive.Server.NET/StoreBookingEngine/Stores/OrderStore.cs index a662f056..30203c7d 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/Stores/OrderStore.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/Stores/OrderStore.cs @@ -17,7 +17,7 @@ public enum DeleteOrderResult public interface IOrderStore { - void SetConfiguration(OrderIdTemplate orderIdTemplate, SingleIdTemplate sellerIdTemplate); + void SetConfiguration(OrderIdTemplate orderIdTemplate, SingleIdTemplate sellerIdTemplate); /// /// Stage is provided as it depending on the implementation (e.g. what level of leasing is applied) /// it might not be appropriate to create transactions for all stages. @@ -26,16 +26,16 @@ public interface IOrderStore /// /// ValueTask BeginOrderTransaction(FlowStage stage); - Task CustomerCancelOrderItems(OrderIdComponents orderId, SellerIdComponents sellerId, List orderItemIds); - Task CustomerRejectOrderProposal(OrderIdComponents orderId, SellerIdComponents sellerId); + Task CustomerCancelOrderItems(OrderIdComponents orderId, SimpleIdComponents sellerId, List orderItemIds); + Task CustomerRejectOrderProposal(OrderIdComponents orderId, SimpleIdComponents sellerId); ValueTask CreateOrderStateContext(StoreBookingFlowContext flowContext); ValueTask Initialise(StoreBookingFlowContext flowContext, IStateContext stateContext); - Task DeleteOrder(OrderIdComponents orderId, SellerIdComponents sellerId); - Task DeleteLease(OrderIdComponents orderId, SellerIdComponents sellerId); + Task DeleteOrder(OrderIdComponents orderId, SimpleIdComponents sellerId); + Task DeleteLease(OrderIdComponents orderId, SimpleIdComponents sellerId); Task TriggerTestAction(OpenBookingSimulateAction simulateAction, OrderIdComponents orderId); - Task GetOrderStatus(OrderIdComponents orderId, SellerIdComponents sellerId, ILegalEntity seller); - Task CreateOrderFromOrderProposal(OrderIdComponents orderId, SellerIdComponents sellerId, Uri orderProposalVersion, Order order); + Task GetOrderStatus(OrderIdComponents orderId, SimpleIdComponents sellerId, ILegalEntity seller); + Task CreateOrderFromOrderProposal(OrderIdComponents orderId, SimpleIdComponents sellerId, Uri orderProposalVersion, Order order); ValueTask CreateLease(OrderQuote responseOrderQuote, StoreBookingFlowContext flowContext, IStateContext stateContext, IDatabaseTransaction dbTransaction); ValueTask UpdateLease(OrderQuote responseOrderQuote, StoreBookingFlowContext flowContext, IStateContext stateContext, IDatabaseTransaction dbTransaction); @@ -51,7 +51,7 @@ public interface IStateContext: IDisposable public abstract class OrderStore : OrdersModelSupport, IOrderStore where TDatabaseTransaction : IDatabaseTransaction where TStateContext : IStateContext { - void IOrderStore.SetConfiguration(OrderIdTemplate orderIdTemplate, SingleIdTemplate sellerIdTemplate) + void IOrderStore.SetConfiguration(OrderIdTemplate orderIdTemplate, SingleIdTemplate sellerIdTemplate) { base.SetConfiguration(orderIdTemplate, sellerIdTemplate); } @@ -73,20 +73,20 @@ async ValueTask IOrderStore.Initialise(StoreBookingFlowContext flowContext, ISta await Initialise(flowContext, (TStateContext)stateContext); } - public abstract Task CustomerCancelOrderItems(OrderIdComponents orderId, SellerIdComponents sellerId, List orderItemIds); - public virtual Task CustomerRejectOrderProposal(OrderIdComponents orderId, SellerIdComponents sellerId) { + public abstract Task CustomerCancelOrderItems(OrderIdComponents orderId, SimpleIdComponents sellerId, List orderItemIds); + public virtual Task CustomerRejectOrderProposal(OrderIdComponents orderId, SimpleIdComponents sellerId) { // This will return an error to the Broker throw new OpenBookingException(new OpenBookingError(), "Order Proposals are not supported in this implementation"); } - public abstract Task DeleteOrder(OrderIdComponents orderId, SellerIdComponents sellerId); - public abstract Task DeleteLease(OrderIdComponents orderId, SellerIdComponents sellerId); + public abstract Task DeleteOrder(OrderIdComponents orderId, SimpleIdComponents sellerId); + public abstract Task DeleteLease(OrderIdComponents orderId, SimpleIdComponents sellerId); public abstract Task TriggerTestAction(OpenBookingSimulateAction simulateAction, OrderIdComponents orderId); - public virtual Task GetOrderStatus(OrderIdComponents orderId, SellerIdComponents sellerId, ILegalEntity seller) { + public virtual Task GetOrderStatus(OrderIdComponents orderId, SimpleIdComponents sellerId, ILegalEntity seller) { // This will return an error to the Broker throw new OpenBookingException(new OpenBookingError(), "The Order Status endpoint is not supported in this implementation"); } - public virtual Task CreateOrderFromOrderProposal(OrderIdComponents orderId, SellerIdComponents sellerId, Uri orderProposalVersion, Order order) { + public virtual Task CreateOrderFromOrderProposal(OrderIdComponents orderId, SimpleIdComponents sellerId, Uri orderProposalVersion, Order order) { throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "CreateOrderFromOrderProposal must be implemented when implementing Order Proposals"); } public abstract ValueTask BeginOrderTransaction(FlowStage stage); From 696200360a384bac23579ffee8f91b8018607c67 Mon Sep 17 00:00:00 2001 From: nickevansuk <2616208+nickevansuk@users.noreply.github.com> Date: Wed, 24 Nov 2021 23:24:47 +0000 Subject: [PATCH 07/33] Fix CustomerAccountIdBaseUrl --- OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs | 1 + .../OpenBookingHelper/Settings/BookingEngineSettings.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs index f3d437ba..2b6cb81e 100644 --- a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs +++ b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs @@ -67,6 +67,7 @@ public CustomBookingEngine(BookingEngineSettings settings, Uri openBookingAPIBas } settings.OrderIdTemplate.RequiredBaseUrl = openBookingAPIBaseUrl; settings.SellerIdTemplate.RequiredBaseUrl = settings.JsonLdIdBaseUrl; + settings.CustomerAccountIdTemplate.RequiredBaseUrl = settings.CustomerAccountIdBaseUrl; // Create a lookup of each IdTemplate to pass into the appropriate RpdeGenerator // TODO: Output better error if there is a feed assigned across two templates diff --git a/OpenActive.Server.NET/OpenBookingHelper/Settings/BookingEngineSettings.cs b/OpenActive.Server.NET/OpenBookingHelper/Settings/BookingEngineSettings.cs index ec944a3e..2349c6e2 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Settings/BookingEngineSettings.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Settings/BookingEngineSettings.cs @@ -21,6 +21,7 @@ public class BookingEngineSettings public OrderIdTemplate OrderIdTemplate { get; set; } public SingleIdTemplate SellerIdTemplate { get; set; } public SingleIdTemplate CustomerAccountIdTemplate { get; set; } + public Uri CustomerAccountIdBaseUrl { get; set; } public Dictionary OpenDataFeeds { get; set; } public int RPDEPageSize { get; set; } = 500; public Uri JsonLdIdBaseUrl { get; set; } From cd5822039b20da5c6848f23552c8c5a0a3eda809 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Thu, 25 Nov 2021 00:14:02 +0000 Subject: [PATCH 08/33] Further hacks --- .../Authentication/AuthenticationExtensions.cs | 2 +- .../StoreBookingEngine/StoreBookingEngine.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/OpenActive.Server.NET/OpenBookingHelper/Authentication/AuthenticationExtensions.cs b/OpenActive.Server.NET/OpenBookingHelper/Authentication/AuthenticationExtensions.cs index b5fe435d..9085d90a 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Authentication/AuthenticationExtensions.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Authentication/AuthenticationExtensions.cs @@ -59,7 +59,7 @@ public static (string ClientId, Uri SellerId, Uri CustomerAccountId) GetAccessTo var clientId = principal.GetClientId(); var sellerId = principal.GetSellerId().ParseUrlOrNull(); var customerAccountId = principal.GetCustomerAccountId().ParseUrlOrNull(); - if (clientId != null && sellerId != null) + if (clientId != null) // TODO: Add && sellerId != null, and ensure sellerId is always passed in even in single seller mode { return (clientId, sellerId, customerAccountId); } diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index f44d9e98..79a40b5d 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -582,12 +582,12 @@ private async Task> GetOrderItemContextGroups(List> GetOrderItemContextGroups(List Date: Thu, 2 Dec 2021 17:45:53 +0000 Subject: [PATCH 09/33] Refactor to allow price to change during the lease/book flow (for example, due to any discount relevant to the Customer Account) --- .../StoreBookingEngine/StoreBookingEngine.cs | 436 +++++++++--------- 1 file changed, 210 insertions(+), 226 deletions(-) diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index 79a40b5d..7f32ba5c 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -383,19 +383,17 @@ protected override async Task ProcessOrderQuoteDeletion(OrderIdComponents orderI await storeBookingEngineSettings.OrderStore.DeleteLease(orderId, sellerId); } - private static void CheckOrderIntegrity(Order requestOrder, Order responseOrder) + private static bool HasOrderItemErrors(Order responseOrder) { - // If any other errors were returned from GetOrderItems, the booking must fail - // https://www.openactive.io/open-booking-api/EditorsDraft/#order-creation-b - if (responseOrder.OrderedItem.Any(x => x.Error != null && x.Error.Count > 0)) - { - throw new SilentRollbackException(); - } + return responseOrder.OrderedItem.Any(x => x.Error != null && x.Error.Count > 0); + } + private static void CheckOrderIntegrity(Order requestOrder, Order responseOrder) + { // Throw error on payment due mismatch if (requestOrder.TotalPaymentDue?.Price != responseOrder.TotalPaymentDue?.Price) { - throw new OpenBookingException(new TotalPaymentDueMismatchError()); + throw new OpenBookingException(new TotalPaymentDueMismatchError(), $"totalPaymentDue price in the request ({requestOrder.TotalPaymentDue?.Price}) does not match expected value ({responseOrder.TotalPaymentDue?.Price})"); } // If no payment provided by broker, prepayment must either be required, or not specified with a nonzero price @@ -697,282 +695,268 @@ public override async Task ProcessFlowRequest(BookingFlowContext OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList() }; - // Add totals to the resulting Order - OrderCalculations.AugmentOrderWithTotals( - responseGenericOrder, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); + if (HasOrderItemErrors(responseGenericOrder)) + { + // Ensure that the Order is augmented with totals, and do not continue to process the Order if there are already OrderItem level errors + OrderCalculations.AugmentOrderWithTotals(responseGenericOrder, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); + Console.WriteLine($"## {flowContext.OrderId.uuid} | LEAVING CRITICAL SECTION {flowContext.Stage.ToString()} for {flowContext?.Customer?.Email ?? "?"}"); + return responseGenericOrder; + } - try { - switch (responseGenericOrder) + try { - case OrderProposal responseOrderProposal: - if (flowContext.Stage != FlowStage.P) - throw new OpenBookingException(new UnexpectedOrderTypeError()); - - CheckOrderIntegrity(order, responseOrderProposal); - - // Proposal creation is atomic - using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions - ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) + // Transaction is wrapped around all write operations + using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions + ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() + : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) + { + try { - if (dbTransaction == null) + switch (responseGenericOrder) { - throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for OrderProposal Creation at P, to ensure the integrity of the booking made."); - } + case OrderProposal responseOrderProposal: + // Proposal creation is atomic - try - { - // Create the parent Order - var (version, orderProposalStatus) = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? - storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); - - responseOrderProposal.OrderProposalVersion = new Uri($"{responseOrderProposal.Id}/versions/{version}"); - responseOrderProposal.OrderProposalStatus = orderProposalStatus; - - // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call - // Note the happens first to ensure no conflicts with new OrderItems that have since been added - foreach (var g in orderItemGroups) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); - } + if (flowContext.Stage != FlowStage.P) + throw new OpenBookingException(new UnexpectedOrderTypeError()); - // Book the OrderItems - foreach (var g in orderItemGroups) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); + // Proposal creation is atomic + if (dbTransaction == null) + { + throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for OrderProposal Creation at P, to ensure the integrity of the booking made."); + } + + // Create the parent Order + var (version, orderProposalStatus) = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? + storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() + : await storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); + responseOrderProposal.OrderProposalVersion = new Uri($"{responseOrderProposal.Id}/versions/{version}"); + responseOrderProposal.OrderProposalStatus = orderProposalStatus; - foreach (var ctx in g.OrderItemContexts) + // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call + // Note the happens first to ensure no conflicts with new OrderItems that have since been added + foreach (var g in orderItemGroups) { - // Check that OrderItem Id was added - if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + } + + // Book the OrderItems + foreach (var g in orderItemGroups) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); + + + foreach (var ctx in g.OrderItemContexts) { - throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in ProposeOrderItems for successfully booked items"); + // Check that OrderItem Id was added + if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) + { + throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in ProposeOrderItems for successfully booked items"); + } + + // Set the orderItemStatus to null (as it must always be so in the response of P) + ctx.ResponseOrderItem.OrderItemStatus = null; } - - // Set the orderItemStatus to null (as it must always be so in the response of P) - ctx.ResponseOrderItem.OrderItemStatus = null; } - } - - // Update this in case ResponseOrderItem was overwritten in ProposeOrderItems - responseOrderProposal.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); - // Recheck for integrity given the updates - CheckOrderIntegrity(order, responseOrderProposal); + // Update this in case ResponseOrderItem was overwritten in ProposeOrderItems + responseOrderProposal.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); + // Add totals to the resulting Order + OrderCalculations.AugmentOrderWithTotals( + responseOrderProposal, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); - if (dbTransaction != null) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Commit().CheckSyncValueTaskWorked(); + if (!HasOrderItemErrors(responseOrderProposal)) + { + // Check for integrity, as if no OrderItem errors the price will be accurate + CheckOrderIntegrity(order, responseOrderProposal); + } else - await dbTransaction.Commit(); - } - } - catch - { - if (dbTransaction != null) - { + { + // Rollback for OrderItem errors + throw new SilentRollbackException(); + } + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Rollback().CheckSyncValueTaskWorked(); + storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await dbTransaction.Rollback(); - } - throw; - } - } - break; - - case OrderQuote responseOrderQuote: - if (!(flowContext.Stage == FlowStage.C1 || flowContext.Stage == FlowStage.C2)) - throw new OpenBookingException(new UnexpectedOrderTypeError()); + await storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); - // If "payment" has been supplied unnecessarily, simply do not return it - if (responseOrderQuote.Payment != null && responseOrderQuote.TotalPaymentDue.Price.Value == 0) - { - responseOrderQuote.Payment = null; - } + break; - // Note behaviour here is to lease those items that are available to be leased, and return errors for everything else - // Leasing is optimistic, booking is atomic - using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions - ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) - { - try - { + case OrderQuote responseOrderQuote: + // Note behaviour here is to lease those items that are available to be leased, and return errors for everything else + // Leasing is optimistic, booking is atomic - responseOrderQuote.Lease = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? - storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); + if (!(flowContext.Stage == FlowStage.C1 || flowContext.Stage == FlowStage.C2)) + throw new OpenBookingException(new UnexpectedOrderTypeError()); - // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call - // Note the happens first to ensure no conflicts with new OrderItems that have since been added - foreach (var g in orderItemGroups) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); - } + responseOrderQuote.Lease = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? + storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() + : await storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); - if (responseOrderQuote.Lease != null) - { + // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call + // Note the happens first to ensure no conflicts with new OrderItems that have since been added foreach (var g in orderItemGroups) { if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + await g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); } - } - // Update this in case ResponseOrderItem was overwritten in Lease - responseOrderQuote.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); + if (responseOrderQuote.Lease != null) + { + foreach (var g in orderItemGroups) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + } + } - // Note OrderRequiresApproval is only required during C1 and C2 - responseOrderQuote.OrderRequiresApproval = orderItemContexts.Any(x => x.RequiresApproval); + // Update this in case ResponseOrderItem was overwritten in Lease + responseOrderQuote.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); + // Note OrderRequiresApproval is only required during C1 and C2 + responseOrderQuote.OrderRequiresApproval = orderItemContexts.Any(x => x.RequiresApproval); + // Add totals to the resulting Order + OrderCalculations.AugmentOrderWithTotals( + responseOrderQuote, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); + + // If "payment" has been supplied unnecessarily, simply do not return it + if (responseOrderQuote.Payment != null && responseOrderQuote.TotalPaymentDue.Price.Value == 0) + { + responseOrderQuote.Payment = null; + } + + // If no OrderItems are included, do not return a lease + if (responseOrderQuote.OrderedItem.Count == 0) + { + responseOrderQuote.Lease = null; + } - if (dbTransaction != null) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Commit().CheckSyncValueTaskWorked(); - else - await dbTransaction.Commit(); - } - } - catch - { - if (dbTransaction != null) - { if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Rollback().CheckSyncValueTaskWorked(); + storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await dbTransaction.Rollback(); - } - throw; - } - } - break; + await storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); - case Order responseOrder: - if (flowContext.Stage != FlowStage.B) - throw new OpenBookingException(new UnexpectedOrderTypeError()); + break; - CheckOrderIntegrity(order, responseOrder); + case Order responseOrder: + if (flowContext.Stage != FlowStage.B) + throw new OpenBookingException(new UnexpectedOrderTypeError()); - // Booking is atomic - using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions - ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) - { - if (dbTransaction == null) - { - throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for booking at B, to ensure the integrity of the booking made."); - } - - try - { - // Create the parent Order - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction); + if (dbTransaction == null) + { + throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for booking at B, to ensure the integrity of the booking made."); + } - // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call - // Note the happens first to ensure no conflicts with new OrderItems that have since been added - foreach (var g in orderItemGroups) - { + // Create the parent Order if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); - } + await storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction); - // Book the OrderItems - foreach (var g in orderItemGroups) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); + // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call + // Note the happens first to ensure no conflicts with new OrderItems that have since been added + foreach (var g in orderItemGroups) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + } - foreach (var ctx in g.OrderItemContexts) + // Book the OrderItems + foreach (var g in orderItemGroups) { - // Check that OrderItem Id was added - if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); + + foreach (var ctx in g.OrderItemContexts) { - throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in BookOrderItems for successfully booked items"); + // Check that OrderItem Id was added + if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) + { + throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in BookOrderItems for successfully booked items"); + } + + // Set the orderItemStatus to be https://openactive.io/OrderConfirmed (as it must always be so in the response of B) + ctx.ResponseOrderItem.OrderItemStatus = OrderItemStatus.OrderItemConfirmed; } + } - // Set the orderItemStatus to be https://openactive.io/OrderConfirmed (as it must always be so in the response of B) - ctx.ResponseOrderItem.OrderItemStatus = OrderItemStatus.OrderItemConfirmed; + // Update this in case ResponseOrderItem was overwritten in Book + responseOrder.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); + + // Add totals to the resulting Order + OrderCalculations.AugmentOrderWithTotals( + responseOrder, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); + + if (!HasOrderItemErrors(responseOrder)) + { + // Check for integrity, as if no OrderItem errors the price will be accurate + CheckOrderIntegrity(order, responseOrder); + } + else + { + // Rollback for OrderItem errors + throw new SilentRollbackException(); } - } - // Update this in case ResponseOrderItem was overwritten in Book - responseOrder.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction); - // Recheck integrity given the updates - CheckOrderIntegrity(order, responseOrder); + break; + default: + throw new OpenBookingException(new UnexpectedOrderTypeError()); + } + + if (dbTransaction != null) + { if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + dbTransaction.Commit().CheckSyncValueTaskWorked(); else - await storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction); - - - if (dbTransaction != null) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Commit().CheckSyncValueTaskWorked(); - else - await dbTransaction.Commit(); - } + await dbTransaction.Commit(); } - catch + } + catch + { + if (dbTransaction != null) { - if (dbTransaction != null) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Rollback().CheckSyncValueTaskWorked(); - else - await dbTransaction.Rollback(); - } - throw; + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + dbTransaction.Rollback().CheckSyncValueTaskWorked(); + else + await dbTransaction.Rollback(); } + throw; } - break; - - default: - throw new OpenBookingException(new UnexpectedOrderTypeError()); + } + } + catch (SilentRollbackException) + { + // Catch the SilentRollbackException so it doesn't leave the method, but do nothing with it + // At this point it will have already triggered the rollback + } + finally + { + Console.WriteLine($"## {flowContext.OrderId.uuid} | LEAVING CRITICAL SECTION {flowContext.Stage.ToString()} for {flowContext?.Customer?.Email ?? "?"}"); } - } - catch (SilentRollbackException) { - // Catch the SilentRollbackException so it doesn't leave the method, but do nothing with it - // At this point it will have already triggered the rollback - } finally - { - Console.WriteLine($"## {flowContext.OrderId.uuid} | LEAVING CRITICAL SECTION {flowContext.Stage.ToString()} for {flowContext?.Customer?.Email ?? "?"}"); - } return responseGenericOrder; } From b70a526b23bfb60f3c1450779abb388cfd8e83e5 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Fri, 27 May 2022 10:14:55 +0100 Subject: [PATCH 10/33] Find-and-replace all instances of `SellerIdComponents` with `SimpleIdComponents`, etc --- .../Controllers/OpenBookingController.cs | 18 ++--- .../Feeds/FacilitiesFeeds.cs | 2 +- .../Feeds/SessionsFeeds.cs | 4 +- .../Settings/EngineConfig.cs | 12 ++-- .../Stores/FacilityStore.cs | 16 ++--- .../Stores/OrderStore.cs | 30 ++++---- .../Stores/SellerStore.cs | 12 ++-- .../Stores/SessionStore.cs | 16 ++--- .../Feeds/FacilitiesFeeds.cs | 2 +- .../Feeds/SessionsFeeds.cs | 4 +- .../Settings/EngineConfig.cs | 12 ++-- .../Stores/FacilityStore.cs | 16 ++--- .../Stores/OrderStore.cs | 30 ++++---- .../Stores/SellerStore.cs | 12 ++-- .../Stores/SessionStore.cs | 16 ++--- .../SellerIdComponentsTest.cs | 69 ------------------- .../SimpleIdComponentsTest.cs | 69 +++++++++++++++++++ .../CustomBookingEngine.cs | 60 ++++++++-------- OpenActive.Server.NET/GlobalSuppressions.cs | 24 +++---- .../Model/OrderModelSupport.cs | 4 +- .../OpenBookingHelper/Stores/SellerStore.cs | 8 +-- .../StoreBookingEngine/StoreBookingEngine.cs | 6 +- 22 files changed, 221 insertions(+), 221 deletions(-) delete mode 100644 OpenActive.Server.NET.Tests/SellerIdComponentsTest.cs create mode 100644 OpenActive.Server.NET.Tests/SimpleIdComponentsTest.cs diff --git a/Examples/BookingSystem.AspNetCore/Controllers/OpenBookingController.cs b/Examples/BookingSystem.AspNetCore/Controllers/OpenBookingController.cs index 0816e20b..f4008e1d 100644 --- a/Examples/BookingSystem.AspNetCore/Controllers/OpenBookingController.cs +++ b/Examples/BookingSystem.AspNetCore/Controllers/OpenBookingController.cs @@ -33,7 +33,7 @@ public async Task OrderQuoteCreationC1([FromServices] IBookingEng { try { - (string clientId, Uri sellerId) = User.GetAccessTokenOpenBookingClaims(); + (string clientId, Uri sellerId, _) = User.GetAccessTokenOpenBookingClaims(); return (await bookingEngine.ProcessCheckpoint1(clientId, sellerId, uuid, orderQuote)).GetContentResult(); } catch (OpenBookingException obe) @@ -52,7 +52,7 @@ public async Task OrderQuoteCreationC2([FromServices] IBookingEng { try { - (string clientId, Uri sellerId) = User.GetAccessTokenOpenBookingClaims(); + (string clientId, Uri sellerId, _) = User.GetAccessTokenOpenBookingClaims(); return (await bookingEngine.ProcessCheckpoint2(clientId, sellerId, uuid, orderQuote)).GetContentResult(); } catch (OpenBookingException obe) @@ -71,7 +71,7 @@ public async Task OrderProposalCreationP([FromServices] IBookingE { try { - (string clientId, Uri sellerId) = User.GetAccessTokenOpenBookingClaims(); + (string clientId, Uri sellerId, _) = User.GetAccessTokenOpenBookingClaims(); return (await bookingEngine.ProcessOrderProposalCreationP(clientId, sellerId, uuid, orderProposal)).GetContentResult(); } catch (OpenBookingException obe) @@ -90,7 +90,7 @@ public async Task OrderQuoteDeletion([FromServices] IBookingEngin { try { - (string clientId, Uri sellerId) = User.GetAccessTokenOpenBookingClaims(); + (string clientId, Uri sellerId, _) = User.GetAccessTokenOpenBookingClaims(); return (await bookingEngine.DeleteOrderQuote(clientId, sellerId, uuid)).GetContentResult(); } catch (OpenBookingException obe) @@ -109,7 +109,7 @@ public async Task OrderCreationB([FromServices] IBookingEngine bo { try { - (string clientId, Uri sellerId) = User.GetAccessTokenOpenBookingClaims(); + (string clientId, Uri sellerId, _) = User.GetAccessTokenOpenBookingClaims(); return (await bookingEngine.ProcessOrderCreationB(clientId, sellerId, uuid, order)).GetContentResult(); } catch (OpenBookingException obe) @@ -128,7 +128,7 @@ public async Task OrderDeletion([FromServices] IBookingEngine boo { try { - (string clientId, Uri sellerId) = User.GetAccessTokenOpenBookingClaims(); + (string clientId, Uri sellerId, _) = User.GetAccessTokenOpenBookingClaims(); return (await bookingEngine.DeleteOrder(clientId, sellerId, uuid)).GetContentResult(); } catch (OpenBookingException obe) @@ -147,7 +147,7 @@ public async Task OrderUpdate([FromServices] IBookingEngine booki { try { - (string clientId, Uri sellerId) = User.GetAccessTokenOpenBookingClaims(); + (string clientId, Uri sellerId, _) = User.GetAccessTokenOpenBookingClaims(); return (await bookingEngine.ProcessOrderUpdate(clientId, sellerId, uuid, order)).GetContentResult(); } catch (OpenBookingException obe) @@ -167,7 +167,7 @@ public async Task OrderProposalUpdate([FromServices] IBookingEngi { try { - (string clientId, Uri sellerId) = User.GetAccessTokenOpenBookingClaims(); + (string clientId, Uri sellerId, _) = User.GetAccessTokenOpenBookingClaims(); return (await bookingEngine.ProcessOrderProposalUpdate(clientId, sellerId, uuid, order)).GetContentResult(); } catch (OpenBookingException obe) @@ -183,7 +183,7 @@ public async Task GetOrderStatus([FromServices] IBookingEngine bo { try { - (string clientId, Uri sellerId) = User.GetAccessTokenOpenBookingClaims(); + (string clientId, Uri sellerId, _) = User.GetAccessTokenOpenBookingClaims(); return (await bookingEngine.GetOrderStatus(clientId, sellerId, uuid)).GetContentResult(); } catch (OpenBookingException obe) diff --git a/Examples/BookingSystem.AspNetCore/Feeds/FacilitiesFeeds.cs b/Examples/BookingSystem.AspNetCore/Feeds/FacilitiesFeeds.cs index 01783860..f5db5944 100644 --- a/Examples/BookingSystem.AspNetCore/Feeds/FacilitiesFeeds.cs +++ b/Examples/BookingSystem.AspNetCore/Feeds/FacilitiesFeeds.cs @@ -72,7 +72,7 @@ protected override async Task>> GetRpdeItems(long? af IsOpenBookingAllowed = true, } : new Organization { - Id = RenderSellerId(new SellerIdComponents { SellerIdLong = result.Item2.Id }), + Id = RenderSellerId(new SimpleIdComponents { IdLong = result.Item2.Id }), Name = result.Item2.Name, TaxMode = result.Item2.IsTaxGross ? TaxMode.TaxGross : TaxMode.TaxNet, TermsOfService = new List diff --git a/Examples/BookingSystem.AspNetCore/Feeds/SessionsFeeds.cs b/Examples/BookingSystem.AspNetCore/Feeds/SessionsFeeds.cs index 662ce910..5aea6370 100644 --- a/Examples/BookingSystem.AspNetCore/Feeds/SessionsFeeds.cs +++ b/Examples/BookingSystem.AspNetCore/Feeds/SessionsFeeds.cs @@ -123,13 +123,13 @@ protected override async Task>> GetRpdeItems(long? IsOpenBookingAllowed = true, } : result.Item2.IsIndividual ? (ILegalEntity)new Person { - Id = RenderSellerId(new SellerIdComponents { SellerIdLong = result.Item2.Id }), + Id = RenderSellerId(new SimpleIdComponents { IdLong = result.Item2.Id }), Name = result.Item2.Name, TaxMode = result.Item2.IsTaxGross ? TaxMode.TaxGross : TaxMode.TaxNet, IsOpenBookingAllowed = true, } : (ILegalEntity)new Organization { - Id = RenderSellerId(new SellerIdComponents { SellerIdLong = result.Item2.Id }), + Id = RenderSellerId(new SimpleIdComponents { IdLong = result.Item2.Id }), Name = result.Item2.Name, TaxMode = result.Item2.IsTaxGross ? TaxMode.TaxGross : TaxMode.TaxNet, TermsOfService = new List diff --git a/Examples/BookingSystem.AspNetCore/Settings/EngineConfig.cs b/Examples/BookingSystem.AspNetCore/Settings/EngineConfig.cs index d2b44fbe..3965bda2 100644 --- a/Examples/BookingSystem.AspNetCore/Settings/EngineConfig.cs +++ b/Examples/BookingSystem.AspNetCore/Settings/EngineConfig.cs @@ -111,15 +111,15 @@ public static StoreBookingEngine CreateStoreBookingEngine(AppSettings appSetting /* // Multiple Seller Mode SellerStore = new AcmeSellerStore(), - SellerIdTemplate = new SingleIdTemplate( - "{+BaseUrl}/sellers/{SellerIdLong}" + SellerIdTemplate = new SingleIdTemplate( + "{+BaseUrl}/sellers/{IdLong}" ), */ /* // Single Seller Mode SellerStore = new AcmeSellerStore(), - SellerIdTemplate = new SingleIdTemplate( + SellerIdTemplate = new SingleIdTemplate( "{+BaseUrl}/seller" ), HasSingleSeller = true, @@ -128,11 +128,11 @@ public static StoreBookingEngine CreateStoreBookingEngine(AppSettings appSetting // Reference implementation is configurable to allow both modes to be tested SellerStore = new AcmeSellerStore(appSettings.FeatureFlags.SingleSeller), SellerIdTemplate = appSettings.FeatureFlags.SingleSeller ? - new SingleIdTemplate( + new SingleIdTemplate( "{+BaseUrl}/seller" ) : - new SingleIdTemplate( - "{+BaseUrl}/sellers/{SellerIdLong}" + new SingleIdTemplate( + "{+BaseUrl}/sellers/{IdLong}" ), HasSingleSeller = appSettings.FeatureFlags.SingleSeller, diff --git a/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs b/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs index 1f22303b..070e7e6b 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs @@ -28,12 +28,12 @@ protected override async Task CreateOpportunityWithinTestDa OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, - SellerIdComponents seller) + SimpleIdComponents seller) { - if (!_appSettings.FeatureFlags.SingleSeller && !seller.SellerIdLong.HasValue) + if (!_appSettings.FeatureFlags.SingleSeller && !seller.IdLong.HasValue) throw new OpenBookingException(new OpenBookingError(), "Seller must have an ID in Multiple Seller Mode"); - long? sellerId = _appSettings.FeatureFlags.SingleSeller ? null : seller.SellerIdLong; + long? sellerId = _appSettings.FeatureFlags.SingleSeller ? null : seller.IdLong; var requiresApproval = openBookingFlow == TestOpenBookingFlowEnumeration.OpenBookingApprovalFlow; switch (opportunityType) @@ -286,7 +286,7 @@ protected override async Task TriggerTestAction(OpenBookingSimulateAction simula // Similar to the RPDE logic, this needs to render and return an new hypothetical OrderItem from the database based on the supplied opportunity IDs protected override async Task GetOrderItems(List> orderItemContexts, StoreBookingFlowContext flowContext, OrderStateContext stateContext) { - // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SellerIdComponents (this is not required if using a Single Seller) + // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SimpleIdComponents (this is not required if using a Single Seller) // Additionally this method must check that there are enough spaces in each entry @@ -377,7 +377,7 @@ protected override async Task GetOrderItems(List /// True if Order found, False if Order not found - public override async Task CustomerCancelOrderItems(OrderIdComponents orderId, SellerIdComponents sellerId, List orderItemIds) + public override async Task CustomerCancelOrderItems(OrderIdComponents orderId, SimpleIdComponents sellerId, List orderItemIds) { try { return await FakeBookingSystem.Database.CancelOrderItems( orderId.ClientId, - sellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */, + sellerId.IdLong ?? null /* Hack to allow this to work in Single Seller mode too */, orderId.uuid, orderItemIds.Where(x => x.OrderItemIdLong.HasValue).Select(x => x.OrderItemIdLong.Value).ToList(), true); } @@ -52,9 +52,9 @@ public override async Task CustomerCancelOrderItems(OrderIdComponents orde /// Note sellerId will always be null in Single Seller mode /// /// True if OrderProposal found, False if OrderProposal not found - public override async Task CustomerRejectOrderProposal(OrderIdComponents orderId, SellerIdComponents sellerId) + public override async Task CustomerRejectOrderProposal(OrderIdComponents orderId, SimpleIdComponents sellerId) { - return await FakeBookingSystem.Database.RejectOrderProposal(orderId.ClientId, sellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */, orderId.uuid, true); + return await FakeBookingSystem.Database.RejectOrderProposal(orderId.ClientId, sellerId.IdLong ?? null /* Hack to allow this to work in Single Seller mode too */, orderId.uuid, true); } public override async Task TriggerTestAction(OpenBookingSimulateAction simulateAction, OrderIdComponents orderId) @@ -240,7 +240,7 @@ public override async ValueTask CreateLease( flowContext.Broker.Name, flowContext.Broker.Url, flowContext.Broker.Telephone, - flowContext.SellerId.SellerIdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode + flowContext.SellerId.IdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode flowContext.Customer?.Email, leaseExpires, databaseTransaction.FakeDatabaseTransaction); @@ -264,13 +264,13 @@ public override ValueTask UpdateLease( return new ValueTask(); } - public override async Task DeleteLease(OrderIdComponents orderId, SellerIdComponents sellerId) + public override async Task DeleteLease(OrderIdComponents orderId, SimpleIdComponents sellerId) { // Note if no lease support, simply do nothing here await FakeBookingSystem.Database.DeleteLease( orderId.ClientId, orderId.uuid, - sellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */ + sellerId.IdLong ?? null /* Hack to allow this to work in Single Seller mode too */ ); } @@ -287,7 +287,7 @@ public override async ValueTask CreateOrder(Order responseOrder, StoreBookingFlo flowContext.Broker.Name, flowContext.Broker.Url, flowContext.Broker.Telephone, - flowContext.SellerId.SellerIdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode + flowContext.SellerId.IdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode customerType == CustomerType.None ? null : flowContext.Customer.Email, customerType, customerType == CustomerType.None ? null : (customerType == CustomerType.Organization ? flowContext.Customer.Name : null), @@ -327,7 +327,7 @@ public override ValueTask UpdateOrder(Order responseOrder, StoreBookingFlowConte flowContext.Broker.Name, flowContext.Broker.Url, flowContext.Broker.Telephone, - flowContext.SellerId.SellerIdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode + flowContext.SellerId.IdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode customerType == CustomerType.None ? null : flowContext.Customer.Email, customerType, customerType == CustomerType.None ? null : (customerType == CustomerType.Organization ? flowContext.Customer.Name : null), @@ -356,12 +356,12 @@ public override ValueTask UpdateOrderProposal(OrderProposal responseOrderProposa return new ValueTask(); } - public override async Task DeleteOrder(OrderIdComponents orderId, SellerIdComponents sellerId) + public override async Task DeleteOrder(OrderIdComponents orderId, SimpleIdComponents sellerId) { var result = await FakeBookingSystem.Database.DeleteOrder( orderId.ClientId, orderId.uuid, - sellerId.SellerIdLong ?? null /* Small hack to allow use of FakeDatabase when in Single Seller mode */); + sellerId.IdLong ?? null /* Small hack to allow use of FakeDatabase when in Single Seller mode */); switch (result) { case FakeDatabaseDeleteOrderResult.OrderSuccessfullyDeleted: @@ -377,14 +377,14 @@ public override async Task DeleteOrder(OrderIdComponents orde } } - public override async Task CreateOrderFromOrderProposal(OrderIdComponents orderId, SellerIdComponents sellerId, Uri orderProposalVersion, Order order) + public override async Task CreateOrderFromOrderProposal(OrderIdComponents orderId, SimpleIdComponents sellerId, Uri orderProposalVersion, Order order) { // TODO more elegantly extract version UUID from orderProposalVersion (probably much further up the stack?) var version = new Guid(orderProposalVersion.ToString().Split('/').Last()); var result = await FakeBookingSystem.Database.BookOrderProposal( orderId.ClientId, - sellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */, + sellerId.IdLong ?? null /* Hack to allow this to work in Single Seller mode too */, orderId.uuid, version); // TODO return enum to allow errors cases to be handled in the engine @@ -440,11 +440,11 @@ public static Order RenderOrderFromDatabaseResult(Uri orderId, OrderTable dbOrde return order; } - public override async Task GetOrderStatus(OrderIdComponents orderId, SellerIdComponents sellerId, ILegalEntity seller) + public override async Task GetOrderStatus(OrderIdComponents orderId, SimpleIdComponents sellerId, ILegalEntity seller) { var (getOrderResult, dbOrder, dbOrderItems) = await FakeBookingSystem.Database.GetOrderAndOrderItems( orderId.ClientId, - sellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */, + sellerId.IdLong ?? null /* Hack to allow this to work in Single Seller mode too */, orderId.uuid); if (getOrderResult == FakeDatabaseGetOrderResult.OrderWasNotFound) throw new OpenBookingException(new UnknownOrderError()); diff --git a/Examples/BookingSystem.AspNetCore/Stores/SellerStore.cs b/Examples/BookingSystem.AspNetCore/Stores/SellerStore.cs index d97bc2dd..fe73c309 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/SellerStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/SellerStore.cs @@ -23,12 +23,12 @@ public AcmeSellerStore(bool useSingleSellerMode) } // If the Seller is not found, simply return null to generate the correct Open Booking error - protected override async ValueTask GetSeller(SellerIdComponents sellerIdComponents) + protected override async ValueTask GetSeller(SimpleIdComponents simpleIdComponents) { // Note both examples are shown below to demonstrate options available. Only one block of the if statement below is required for an actual implementation. if (_useSingleSellerMode) { - // For Single Seller booking systems, no ID will be available from sellerIdComponents, and this data should instead come from your configuration table + // For Single Seller booking systems, no ID will be available from simpleIdComponents, and this data should instead come from your configuration table return new Organization { Id = RenderSingleSellerId(), @@ -56,10 +56,10 @@ protected override async ValueTask GetSeller(SellerIdComponents se }; } - // Otherwise it may be looked up based on supplied sellerIdComponents which are extracted from the sellerId. + // Otherwise it may be looked up based on supplied simpleIdComponents which are extracted from the sellerId. using (var db = FakeBookingSystem.Database.Mem.Database.Open()) { - var seller = db.SingleById(sellerIdComponents.SellerIdLong); + var seller = db.SingleById(simpleIdComponents.IdLong); if (seller == null) { // Seller not found @@ -68,7 +68,7 @@ protected override async ValueTask GetSeller(SellerIdComponents se return seller.IsIndividual ? new Person { - Id = RenderSellerId(new SellerIdComponents { SellerIdLong = seller.Id }), + Id = RenderSellerId(new SimpleIdComponents { IdLong = seller.Id }), Name = seller.Name, TaxMode = seller.IsTaxGross ? TaxMode.TaxGross : TaxMode.TaxNet, LegalName = seller.Name, @@ -84,7 +84,7 @@ protected override async ValueTask GetSeller(SellerIdComponents se IsOpenBookingAllowed = true, } : (ILegalEntity)new Organization { - Id = RenderSellerId(new SellerIdComponents { SellerIdLong = seller.Id }), + Id = RenderSellerId(new SimpleIdComponents { IdLong = seller.Id }), Name = seller.Name, TaxMode = seller.IsTaxGross ? TaxMode.TaxGross : TaxMode.TaxNet, LegalName = seller.Name, diff --git a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs index e9192353..fee3bae6 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs @@ -29,12 +29,12 @@ protected override async Task CreateOpportunityWithinTestDat OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, - SellerIdComponents seller) + SimpleIdComponents seller) { - if (!_appSettings.FeatureFlags.SingleSeller && !seller.SellerIdLong.HasValue) + if (!_appSettings.FeatureFlags.SingleSeller && !seller.IdLong.HasValue) throw new OpenBookingException(new OpenBookingError(), "Seller must have an ID in Multiple Seller Mode"); - long? sellerId = _appSettings.FeatureFlags.SingleSeller ? null : seller.SellerIdLong; + long? sellerId = _appSettings.FeatureFlags.SingleSeller ? null : seller.IdLong; var requiresApproval = openBookingFlow == TestOpenBookingFlowEnumeration.OpenBookingApprovalFlow; switch (opportunityType) @@ -306,7 +306,7 @@ protected override async Task TriggerTestAction(OpenBookingSimulateAction simula // Similar to the RPDE logic, this needs to render and return an new hypothetical OrderItem from the database based on the supplied opportunity IDs protected override async Task GetOrderItems(List> orderItemContexts, StoreBookingFlowContext flowContext, OrderStateContext stateContext) { - // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SellerIdComponents (this is not required if using a Single Seller) + // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SimpleIdComponents (this is not required if using a Single Seller) // Additionally this method must check that there are enough spaces in each entry @@ -398,7 +398,7 @@ protected override async Task GetOrderItems(List>> GetRpdeItems(long? af IsOpenBookingAllowed = true, } : new Organization { - Id = RenderSellerId(new SellerIdComponents { SellerIdLong = result.Item2.Id }), + Id = RenderSellerId(new SimpleIdComponents { IdLong = result.Item2.Id }), Name = result.Item2.Name, TaxMode = result.Item2.IsTaxGross ? TaxMode.TaxGross : TaxMode.TaxNet, TermsOfService = new List diff --git a/Examples/BookingSystem.AspNetFramework/Feeds/SessionsFeeds.cs b/Examples/BookingSystem.AspNetFramework/Feeds/SessionsFeeds.cs index 662ce910..5aea6370 100644 --- a/Examples/BookingSystem.AspNetFramework/Feeds/SessionsFeeds.cs +++ b/Examples/BookingSystem.AspNetFramework/Feeds/SessionsFeeds.cs @@ -123,13 +123,13 @@ protected override async Task>> GetRpdeItems(long? IsOpenBookingAllowed = true, } : result.Item2.IsIndividual ? (ILegalEntity)new Person { - Id = RenderSellerId(new SellerIdComponents { SellerIdLong = result.Item2.Id }), + Id = RenderSellerId(new SimpleIdComponents { IdLong = result.Item2.Id }), Name = result.Item2.Name, TaxMode = result.Item2.IsTaxGross ? TaxMode.TaxGross : TaxMode.TaxNet, IsOpenBookingAllowed = true, } : (ILegalEntity)new Organization { - Id = RenderSellerId(new SellerIdComponents { SellerIdLong = result.Item2.Id }), + Id = RenderSellerId(new SimpleIdComponents { IdLong = result.Item2.Id }), Name = result.Item2.Name, TaxMode = result.Item2.IsTaxGross ? TaxMode.TaxGross : TaxMode.TaxNet, TermsOfService = new List diff --git a/Examples/BookingSystem.AspNetFramework/Settings/EngineConfig.cs b/Examples/BookingSystem.AspNetFramework/Settings/EngineConfig.cs index d2b44fbe..3965bda2 100644 --- a/Examples/BookingSystem.AspNetFramework/Settings/EngineConfig.cs +++ b/Examples/BookingSystem.AspNetFramework/Settings/EngineConfig.cs @@ -111,15 +111,15 @@ public static StoreBookingEngine CreateStoreBookingEngine(AppSettings appSetting /* // Multiple Seller Mode SellerStore = new AcmeSellerStore(), - SellerIdTemplate = new SingleIdTemplate( - "{+BaseUrl}/sellers/{SellerIdLong}" + SellerIdTemplate = new SingleIdTemplate( + "{+BaseUrl}/sellers/{IdLong}" ), */ /* // Single Seller Mode SellerStore = new AcmeSellerStore(), - SellerIdTemplate = new SingleIdTemplate( + SellerIdTemplate = new SingleIdTemplate( "{+BaseUrl}/seller" ), HasSingleSeller = true, @@ -128,11 +128,11 @@ public static StoreBookingEngine CreateStoreBookingEngine(AppSettings appSetting // Reference implementation is configurable to allow both modes to be tested SellerStore = new AcmeSellerStore(appSettings.FeatureFlags.SingleSeller), SellerIdTemplate = appSettings.FeatureFlags.SingleSeller ? - new SingleIdTemplate( + new SingleIdTemplate( "{+BaseUrl}/seller" ) : - new SingleIdTemplate( - "{+BaseUrl}/sellers/{SellerIdLong}" + new SingleIdTemplate( + "{+BaseUrl}/sellers/{IdLong}" ), HasSingleSeller = appSettings.FeatureFlags.SingleSeller, diff --git a/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs index 1f22303b..070e7e6b 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs @@ -28,12 +28,12 @@ protected override async Task CreateOpportunityWithinTestDa OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, - SellerIdComponents seller) + SimpleIdComponents seller) { - if (!_appSettings.FeatureFlags.SingleSeller && !seller.SellerIdLong.HasValue) + if (!_appSettings.FeatureFlags.SingleSeller && !seller.IdLong.HasValue) throw new OpenBookingException(new OpenBookingError(), "Seller must have an ID in Multiple Seller Mode"); - long? sellerId = _appSettings.FeatureFlags.SingleSeller ? null : seller.SellerIdLong; + long? sellerId = _appSettings.FeatureFlags.SingleSeller ? null : seller.IdLong; var requiresApproval = openBookingFlow == TestOpenBookingFlowEnumeration.OpenBookingApprovalFlow; switch (opportunityType) @@ -286,7 +286,7 @@ protected override async Task TriggerTestAction(OpenBookingSimulateAction simula // Similar to the RPDE logic, this needs to render and return an new hypothetical OrderItem from the database based on the supplied opportunity IDs protected override async Task GetOrderItems(List> orderItemContexts, StoreBookingFlowContext flowContext, OrderStateContext stateContext) { - // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SellerIdComponents (this is not required if using a Single Seller) + // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SimpleIdComponents (this is not required if using a Single Seller) // Additionally this method must check that there are enough spaces in each entry @@ -377,7 +377,7 @@ protected override async Task GetOrderItems(List /// True if Order found, False if Order not found - public override async Task CustomerCancelOrderItems(OrderIdComponents orderId, SellerIdComponents sellerId, List orderItemIds) + public override async Task CustomerCancelOrderItems(OrderIdComponents orderId, SimpleIdComponents sellerId, List orderItemIds) { try { return await FakeBookingSystem.Database.CancelOrderItems( orderId.ClientId, - sellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */, + sellerId.IdLong ?? null /* Hack to allow this to work in Single Seller mode too */, orderId.uuid, orderItemIds.Where(x => x.OrderItemIdLong.HasValue).Select(x => x.OrderItemIdLong.Value).ToList(), true); } @@ -52,9 +52,9 @@ public override async Task CustomerCancelOrderItems(OrderIdComponents orde /// Note sellerId will always be null in Single Seller mode /// /// True if OrderProposal found, False if OrderProposal not found - public override async Task CustomerRejectOrderProposal(OrderIdComponents orderId, SellerIdComponents sellerId) + public override async Task CustomerRejectOrderProposal(OrderIdComponents orderId, SimpleIdComponents sellerId) { - return await FakeBookingSystem.Database.RejectOrderProposal(orderId.ClientId, sellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */, orderId.uuid, true); + return await FakeBookingSystem.Database.RejectOrderProposal(orderId.ClientId, sellerId.IdLong ?? null /* Hack to allow this to work in Single Seller mode too */, orderId.uuid, true); } public override async Task TriggerTestAction(OpenBookingSimulateAction simulateAction, OrderIdComponents orderId) @@ -240,7 +240,7 @@ public override async ValueTask CreateLease( flowContext.Broker.Name, flowContext.Broker.Url, flowContext.Broker.Telephone, - flowContext.SellerId.SellerIdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode + flowContext.SellerId.IdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode flowContext.Customer?.Email, leaseExpires, databaseTransaction.FakeDatabaseTransaction); @@ -264,13 +264,13 @@ public override ValueTask UpdateLease( return new ValueTask(); } - public override async Task DeleteLease(OrderIdComponents orderId, SellerIdComponents sellerId) + public override async Task DeleteLease(OrderIdComponents orderId, SimpleIdComponents sellerId) { // Note if no lease support, simply do nothing here await FakeBookingSystem.Database.DeleteLease( orderId.ClientId, orderId.uuid, - sellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */ + sellerId.IdLong ?? null /* Hack to allow this to work in Single Seller mode too */ ); } @@ -287,7 +287,7 @@ public override async ValueTask CreateOrder(Order responseOrder, StoreBookingFlo flowContext.Broker.Name, flowContext.Broker.Url, flowContext.Broker.Telephone, - flowContext.SellerId.SellerIdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode + flowContext.SellerId.IdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode customerType == CustomerType.None ? null : flowContext.Customer.Email, customerType, customerType == CustomerType.None ? null : (customerType == CustomerType.Organization ? flowContext.Customer.Name : null), @@ -327,7 +327,7 @@ public override ValueTask UpdateOrder(Order responseOrder, StoreBookingFlowConte flowContext.Broker.Name, flowContext.Broker.Url, flowContext.Broker.Telephone, - flowContext.SellerId.SellerIdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode + flowContext.SellerId.IdLong ?? null, // Small hack to allow use of FakeDatabase when in Single Seller mode customerType == CustomerType.None ? null : flowContext.Customer.Email, customerType, customerType == CustomerType.None ? null : (customerType == CustomerType.Organization ? flowContext.Customer.Name : null), @@ -356,12 +356,12 @@ public override ValueTask UpdateOrderProposal(OrderProposal responseOrderProposa return new ValueTask(); } - public override async Task DeleteOrder(OrderIdComponents orderId, SellerIdComponents sellerId) + public override async Task DeleteOrder(OrderIdComponents orderId, SimpleIdComponents sellerId) { var result = await FakeBookingSystem.Database.DeleteOrder( orderId.ClientId, orderId.uuid, - sellerId.SellerIdLong ?? null /* Small hack to allow use of FakeDatabase when in Single Seller mode */); + sellerId.IdLong ?? null /* Small hack to allow use of FakeDatabase when in Single Seller mode */); switch (result) { case FakeDatabaseDeleteOrderResult.OrderSuccessfullyDeleted: @@ -377,14 +377,14 @@ public override async Task DeleteOrder(OrderIdComponents orde } } - public override async Task CreateOrderFromOrderProposal(OrderIdComponents orderId, SellerIdComponents sellerId, Uri orderProposalVersion, Order order) + public override async Task CreateOrderFromOrderProposal(OrderIdComponents orderId, SimpleIdComponents sellerId, Uri orderProposalVersion, Order order) { // TODO more elegantly extract version UUID from orderProposalVersion (probably much further up the stack?) var version = new Guid(orderProposalVersion.ToString().Split('/').Last()); var result = await FakeBookingSystem.Database.BookOrderProposal( orderId.ClientId, - sellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */, + sellerId.IdLong ?? null /* Hack to allow this to work in Single Seller mode too */, orderId.uuid, version); // TODO return enum to allow errors cases to be handled in the engine @@ -440,11 +440,11 @@ public static Order RenderOrderFromDatabaseResult(Uri orderId, OrderTable dbOrde return order; } - public override async Task GetOrderStatus(OrderIdComponents orderId, SellerIdComponents sellerId, ILegalEntity seller) + public override async Task GetOrderStatus(OrderIdComponents orderId, SimpleIdComponents sellerId, ILegalEntity seller) { var (getOrderResult, dbOrder, dbOrderItems) = await FakeBookingSystem.Database.GetOrderAndOrderItems( orderId.ClientId, - sellerId.SellerIdLong ?? null /* Hack to allow this to work in Single Seller mode too */, + sellerId.IdLong ?? null /* Hack to allow this to work in Single Seller mode too */, orderId.uuid); if (getOrderResult == FakeDatabaseGetOrderResult.OrderWasNotFound) throw new OpenBookingException(new UnknownOrderError()); diff --git a/Examples/BookingSystem.AspNetFramework/Stores/SellerStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/SellerStore.cs index d97bc2dd..fe73c309 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/SellerStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/SellerStore.cs @@ -23,12 +23,12 @@ public AcmeSellerStore(bool useSingleSellerMode) } // If the Seller is not found, simply return null to generate the correct Open Booking error - protected override async ValueTask GetSeller(SellerIdComponents sellerIdComponents) + protected override async ValueTask GetSeller(SimpleIdComponents simpleIdComponents) { // Note both examples are shown below to demonstrate options available. Only one block of the if statement below is required for an actual implementation. if (_useSingleSellerMode) { - // For Single Seller booking systems, no ID will be available from sellerIdComponents, and this data should instead come from your configuration table + // For Single Seller booking systems, no ID will be available from simpleIdComponents, and this data should instead come from your configuration table return new Organization { Id = RenderSingleSellerId(), @@ -56,10 +56,10 @@ protected override async ValueTask GetSeller(SellerIdComponents se }; } - // Otherwise it may be looked up based on supplied sellerIdComponents which are extracted from the sellerId. + // Otherwise it may be looked up based on supplied simpleIdComponents which are extracted from the sellerId. using (var db = FakeBookingSystem.Database.Mem.Database.Open()) { - var seller = db.SingleById(sellerIdComponents.SellerIdLong); + var seller = db.SingleById(simpleIdComponents.IdLong); if (seller == null) { // Seller not found @@ -68,7 +68,7 @@ protected override async ValueTask GetSeller(SellerIdComponents se return seller.IsIndividual ? new Person { - Id = RenderSellerId(new SellerIdComponents { SellerIdLong = seller.Id }), + Id = RenderSellerId(new SimpleIdComponents { IdLong = seller.Id }), Name = seller.Name, TaxMode = seller.IsTaxGross ? TaxMode.TaxGross : TaxMode.TaxNet, LegalName = seller.Name, @@ -84,7 +84,7 @@ protected override async ValueTask GetSeller(SellerIdComponents se IsOpenBookingAllowed = true, } : (ILegalEntity)new Organization { - Id = RenderSellerId(new SellerIdComponents { SellerIdLong = seller.Id }), + Id = RenderSellerId(new SimpleIdComponents { IdLong = seller.Id }), Name = seller.Name, TaxMode = seller.IsTaxGross ? TaxMode.TaxGross : TaxMode.TaxNet, LegalName = seller.Name, diff --git a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs index e9192353..fee3bae6 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs @@ -29,12 +29,12 @@ protected override async Task CreateOpportunityWithinTestDat OpportunityType opportunityType, TestOpportunityCriteriaEnumeration criteria, TestOpenBookingFlowEnumeration openBookingFlow, - SellerIdComponents seller) + SimpleIdComponents seller) { - if (!_appSettings.FeatureFlags.SingleSeller && !seller.SellerIdLong.HasValue) + if (!_appSettings.FeatureFlags.SingleSeller && !seller.IdLong.HasValue) throw new OpenBookingException(new OpenBookingError(), "Seller must have an ID in Multiple Seller Mode"); - long? sellerId = _appSettings.FeatureFlags.SingleSeller ? null : seller.SellerIdLong; + long? sellerId = _appSettings.FeatureFlags.SingleSeller ? null : seller.IdLong; var requiresApproval = openBookingFlow == TestOpenBookingFlowEnumeration.OpenBookingApprovalFlow; switch (opportunityType) @@ -306,7 +306,7 @@ protected override async Task TriggerTestAction(OpenBookingSimulateAction simula // Similar to the RPDE logic, this needs to render and return an new hypothetical OrderItem from the database based on the supplied opportunity IDs protected override async Task GetOrderItems(List> orderItemContexts, StoreBookingFlowContext flowContext, OrderStateContext stateContext) { - // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SellerIdComponents (this is not required if using a Single Seller) + // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SimpleIdComponents (this is not required if using a Single Seller) // Additionally this method must check that there are enough spaces in each entry @@ -398,7 +398,7 @@ protected override async Task GetOrderItems(List RenderOrdersRPDEPageForFeed(OrderType feedType, str public async Task GetOrderStatus(string clientId, Uri sellerId, string uuidString, Uri customerAccountId = null) { - var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.Order); - var result = await ProcessGetOrderStatus(orderId, sellerIdComponents, seller, customerAccountIdComponents); + var (orderId, simpleIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.Order); + var result = await ProcessGetOrderStatus(orderId, simpleIdComponents, seller, customerAccountIdComponents); if (result == null) { throw new OpenBookingException(new UnknownOrderError()); @@ -402,10 +402,10 @@ private async Task ProcessCheckpoint(string clientId, Uri selle { throw new OpenBookingException(new UnexpectedOrderTypeError(), "OrderQuote is required for C1 and C2"); } - var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, orderType); + var (orderId, simpleIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, orderType); using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { - var orderResponse = await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, flowStage, orderQuote), orderQuote); + var orderResponse = await ProcessFlowRequest(ValidateFlowRequest(orderId, simpleIdComponents, seller, customerAccountIdComponents, flowStage, orderQuote), orderQuote); // Return a 409 status code if any OrderItem level errors exist return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(orderResponse), @@ -422,12 +422,12 @@ public async Task ProcessOrderCreationB(string clientId, Uri se { throw new OpenBookingException(new UnexpectedOrderTypeError(), "Order is required for B"); } - var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.Order); + var (orderId, simpleIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.Order); using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { var response = order.OrderProposalVersion != null ? - await ProcessOrderCreationFromOrderProposal(orderId, settings.OrderIdTemplate, seller, sellerIdComponents, customerAccountIdComponents, order) : - await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, FlowStage.B, order), order); + await ProcessOrderCreationFromOrderProposal(orderId, settings.OrderIdTemplate, seller, simpleIdComponents, customerAccountIdComponents, order) : + await ProcessFlowRequest(ValidateFlowRequest(orderId, simpleIdComponents, seller, customerAccountIdComponents, FlowStage.B, order), order); // Return a 409 status code if any OrderItem level errors exist return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(response), @@ -445,21 +445,21 @@ public async Task ProcessOrderProposalCreationP(string clientId { throw new OpenBookingException(new UnexpectedOrderTypeError(), "OrderProposal is required for P"); } - var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.OrderProposal); + var (orderId, simpleIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.OrderProposal); using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { - return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, FlowStage.P, order), order)), HttpStatusCode.Created); + return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(await ProcessFlowRequest(ValidateFlowRequest(orderId, simpleIdComponents, seller, customerAccountIdComponents, FlowStage.P, order), order)), HttpStatusCode.Created); } } - private SimpleIdComponents GetSellerIdComponentsFromApiKey(Uri sellerId) + private SimpleIdComponents GetSimpleIdComponentsFromApiKey(Uri sellerId) { - // Return empty SellerIdComponents in Single Seller mode, as it is not required in the API Key + // Return empty SimpleIdComponents in Single Seller mode, as it is not required in the API Key if (settings.HasSingleSeller == true) return new SimpleIdComponents(); - var sellerIdComponents = settings.SellerIdTemplate.GetIdComponents(sellerId); - if (sellerIdComponents == null) throw new OpenBookingException(new InvalidAPITokenError()); - return sellerIdComponents; + var simpleIdComponents = settings.SellerIdTemplate.GetIdComponents(sellerId); + if (simpleIdComponents == null) throw new OpenBookingException(new InvalidAPITokenError()); + return simpleIdComponents; } private SimpleIdComponents GetCustomerAccountIdComponentsFromApiKey(Uri customerAccountId) @@ -467,7 +467,7 @@ private SimpleIdComponents GetCustomerAccountIdComponentsFromApiKey(Uri customer // Ignore null (if not authenticated with a Customer Account) if (customerAccountId == null) return null; - // Return empty SellerIdComponents in Single Seller mode, as it is not required in the API Key + // Return empty SimpleIdComponents in Single Seller mode, as it is not required in the API Key if (settings.CustomerAccountIdTemplate == null) throw new OpenBookingException(new InternalLibraryConfigurationError(), "CustomerAccount bookings are not enabled. Please set a CustomerAccountIdTemplate."); var customerAccountIdComponents = settings.CustomerAccountIdTemplate.GetIdComponents(customerAccountId); @@ -480,7 +480,7 @@ public async Task DeleteOrder(string clientId, Uri sellerId, st var orderId = new OrderIdComponents { ClientId = clientId, OrderType = OrderType.Order, uuid = ConvertToGuid(uuidString) }; using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { - var result = await ProcessOrderDeletion(orderId, GetSellerIdComponentsFromApiKey(sellerId), GetCustomerAccountIdComponentsFromApiKey(customerAccountId)); + var result = await ProcessOrderDeletion(orderId, GetSimpleIdComponentsFromApiKey(sellerId), GetCustomerAccountIdComponentsFromApiKey(customerAccountId)); switch (result) { case DeleteOrderResult.OrderSuccessfullyDeleted: @@ -500,7 +500,7 @@ public async Task DeleteOrderQuote(string clientId, Uri sellerI var orderId = new OrderIdComponents { ClientId = clientId, OrderType = OrderType.OrderQuote, uuid = ConvertToGuid(uuidString) }; using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { - await ProcessOrderQuoteDeletion(orderId, GetSellerIdComponentsFromApiKey(sellerId), GetCustomerAccountIdComponentsFromApiKey(customerAccountId)); + await ProcessOrderQuoteDeletion(orderId, GetSimpleIdComponentsFromApiKey(sellerId), GetCustomerAccountIdComponentsFromApiKey(customerAccountId)); return ResponseContent.OpenBookingNoContentResponse(); } } @@ -513,7 +513,7 @@ public async Task ProcessOrderUpdate(string clientId, Uri selle using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { Order order = OpenActiveSerializer.Deserialize(orderJson); - SimpleIdComponents sellerIdComponents = GetSellerIdComponentsFromApiKey(sellerId); + SimpleIdComponents simpleIdComponents = GetSimpleIdComponentsFromApiKey(sellerId); SimpleIdComponents customerAccountIdComponents = GetCustomerAccountIdComponentsFromApiKey(customerAccountId); if (order == null || order.GetType() != typeof(Order)) @@ -559,7 +559,7 @@ public async Task ProcessOrderUpdate(string clientId, Uri selle throw new OpenBookingException(new OrderItemNotWithinOrderError()); } - await ProcessCustomerCancellation(orderId, sellerIdComponents, customerAccountIdComponents, settings.OrderIdTemplate, orderItemIds); + await ProcessCustomerCancellation(orderId, simpleIdComponents, customerAccountIdComponents, settings.OrderIdTemplate, orderItemIds); return ResponseContent.OpenBookingNoContentResponse(); } @@ -574,7 +574,7 @@ public async Task ProcessOrderProposalUpdate(string clientId, U using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { OrderProposal orderProposal = OpenActiveSerializer.Deserialize(orderProposalJson); - SimpleIdComponents sellerIdComponents = GetSellerIdComponentsFromApiKey(sellerId); + SimpleIdComponents simpleIdComponents = GetSimpleIdComponentsFromApiKey(sellerId); SimpleIdComponents customerAccountIdComponents = GetCustomerAccountIdComponentsFromApiKey(customerAccountId); if (orderProposal == null || orderProposal.GetType() != typeof(Order)) @@ -599,7 +599,7 @@ public async Task ProcessOrderProposalUpdate(string clientId, U throw new OpenBookingException(new PatchNotAllowedOnPropertyError(), "Only 'https://openactive.io/CustomerRejected' is permitted for this property."); } - await ProcessOrderProposalCustomerRejection(orderId, sellerIdComponents, customerAccountIdComponents, settings.OrderIdTemplate); + await ProcessOrderProposalCustomerRejection(orderId, simpleIdComponents, customerAccountIdComponents, settings.OrderIdTemplate); return ResponseContent.OpenBookingNoContentResponse(); } @@ -706,11 +706,11 @@ async Task IBookingEngine.InsertTestOpportunity(string testData } if (seller?.Id == null) throw new OpenBookingException(new SellerMismatchError(), "No seller ID was specified"); - var sellerIdComponents = settings.SellerIdTemplate.GetIdComponents(seller.Id); - if (sellerIdComponents == null) throw new OpenBookingException(new SellerMismatchError(), "Seller ID format was invalid"); + var simpleIdComponents = settings.SellerIdTemplate.GetIdComponents(seller.Id); + if (simpleIdComponents == null) throw new OpenBookingException(new SellerMismatchError(), "Seller ID format was invalid"); // Returns a matching Event subclass that will only include "@type" and "@id" properties - var createdEvent = await this.InsertTestOpportunity(testDatasetIdentifier, opportunityType.Value, genericEvent.TestOpportunityCriteria.Value, genericEvent.TestOpenBookingFlow.Value, sellerIdComponents); + var createdEvent = await this.InsertTestOpportunity(testDatasetIdentifier, opportunityType.Value, genericEvent.TestOpportunityCriteria.Value, genericEvent.TestOpenBookingFlow.Value, simpleIdComponents); if (createdEvent.Type != genericEvent.Type) { @@ -752,7 +752,7 @@ async Task IBookingEngine.TriggerTestAction(string actionJson) protected abstract Task TriggerTestAction(OpenBookingSimulateAction simulateAction, OrderIdTemplate orderIdTemplate); - private async Task<(OrderIdComponents orderId, SimpleIdComponents sellerIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents)> ConstructIdsFromRequest(string clientId, Uri authenticationSellerId, Uri authenticationCustomerAccountId, string uuidString, OrderType orderType) + private async Task<(OrderIdComponents orderId, SimpleIdComponents simpleIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents)> ConstructIdsFromRequest(string clientId, Uri authenticationSellerId, Uri authenticationCustomerAccountId, string uuidString, OrderType orderType) { var orderId = new OrderIdComponents { @@ -763,22 +763,22 @@ async Task IBookingEngine.TriggerTestAction(string actionJson) // TODO: Add more request validation rules here - SimpleIdComponents sellerIdComponents = GetSellerIdComponentsFromApiKey(authenticationSellerId); + SimpleIdComponents simpleIdComponents = GetSimpleIdComponentsFromApiKey(authenticationSellerId); SimpleIdComponents customerAccountIdComponents = GetCustomerAccountIdComponentsFromApiKey(authenticationCustomerAccountId); - ILegalEntity seller = await settings.SellerStore.GetSellerById(sellerIdComponents); + ILegalEntity seller = await settings.SellerStore.GetSellerById(simpleIdComponents); if (seller == null) { throw new OpenBookingException(new SellerNotFoundError()); } - return (orderId, sellerIdComponents, seller, customerAccountIdComponents); + return (orderId, simpleIdComponents, seller, customerAccountIdComponents); } //TODO: Should we move Seller into the Abstract level? Perhaps too much complexity - protected BookingFlowContext ValidateFlowRequest(OrderIdComponents orderId, SimpleIdComponents sellerIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents, FlowStage stage, TOrder order) where TOrder : Order, new() + protected BookingFlowContext ValidateFlowRequest(OrderIdComponents orderId, SimpleIdComponents simpleIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents, FlowStage stage, TOrder order) where TOrder : Order, new() { // If being called from Order Status then expect Seller to already be a full object var sellerIdFromOrder = stage == FlowStage.OrderStatus ? order?.Seller.Object?.Id : order?.Seller.IdReference; @@ -843,7 +843,7 @@ async Task IBookingEngine.TriggerTestAction(string actionJson) OrderId = orderId, OrderIdTemplate = settings.OrderIdTemplate, Seller = seller, - SellerId = sellerIdComponents, + SellerId = simpleIdComponents, CustomerAccountId = customerAccountIdComponents, TaxPayeeRelationship = taxPayeeRelationship, Payer = payer, diff --git a/OpenActive.Server.NET/GlobalSuppressions.cs b/OpenActive.Server.NET/GlobalSuppressions.cs index 2496c6c9..a7327f6b 100644 --- a/OpenActive.Server.NET/GlobalSuppressions.cs +++ b/OpenActive.Server.NET/GlobalSuppressions.cs @@ -35,7 +35,7 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'OpenBookingException.OpenBookingException(OpenBookingError error)', validate parameter 'error' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.OpenBookingHelper.OpenBookingException.#ctor(OpenActive.NET.OpenBookingError)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'OpenBookingException.OpenBookingException(OpenBookingError error, string message)', validate parameter 'error' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.OpenBookingHelper.OpenBookingException.#ctor(OpenActive.NET.OpenBookingError,System.String)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'OpenBookingException.OpenBookingException(OpenBookingError error, string message, Exception innerException)', validate parameter 'error' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.OpenBookingHelper.OpenBookingException.#ctor(OpenActive.NET.OpenBookingError,System.String,System.Exception)")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'void OpportunityDataRpdeFeedGenerator.SetConfiguration(OpportunityTypeConfiguration opportunityTypeConfiguration, Uri jsonLdIdBaseUrl, int rpdePageSize, IBookablePairIdTemplate bookablePairIdTemplate, SingleIdTemplate sellerTemplate, Uri openDataFeedBaseUrl)', validate parameter 'opportunityTypeConfiguration' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.OpenBookingHelper.OpportunityDataRpdeFeedGenerator`2.SetConfiguration(OpenActive.DatasetSite.NET.OpportunityTypeConfiguration,System.Uri,System.Int32,OpenActive.Server.NET.OpenBookingHelper.IBookablePairIdTemplate,OpenActive.Server.NET.OpenBookingHelper.SingleIdTemplate{OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents},System.Uri)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'void OpportunityDataRpdeFeedGenerator.SetConfiguration(OpportunityTypeConfiguration opportunityTypeConfiguration, Uri jsonLdIdBaseUrl, int rpdePageSize, IBookablePairIdTemplate bookablePairIdTemplate, SingleIdTemplate sellerTemplate, Uri openDataFeedBaseUrl)', validate parameter 'opportunityTypeConfiguration' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.OpenBookingHelper.OpportunityDataRpdeFeedGenerator`2.SetConfiguration(OpenActive.DatasetSite.NET.OpportunityTypeConfiguration,System.Uri,System.Int32,OpenActive.Server.NET.OpenBookingHelper.IBookablePairIdTemplate,OpenActive.Server.NET.OpenBookingHelper.SingleIdTemplate{OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents},System.Uri)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1810:Initialize all static fields in 'OrderCalculations' when those fields are declared and remove the explicit static constructor", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.OpenBookingHelper.OrderCalculations.#cctor")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'void OrderCalculations.AugmentOrderWithCalculations(TOrder order, StoreBookingFlowContext context, bool businessToConsumerTaxCalculation, bool businessToBusinessTaxCalculation)', validate parameter 'order' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.OpenBookingHelper.OrderCalculations.AugmentOrderWithCalculations``1(``0,OpenActive.Server.NET.StoreBooking.StoreBookingFlowContext,System.Boolean,System.Boolean)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1801:Parameter businessToConsumerTaxCalculation of method AugmentOrderWithCalculations is never used. Remove the parameter or use it in the method body.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.OpenBookingHelper.OrderCalculations.AugmentOrderWithCalculations``1(``0,OpenActive.Server.NET.StoreBooking.StoreBookingFlowContext,System.Boolean,System.Boolean)")] @@ -48,24 +48,24 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.OpenBookingHelper.RpdeFeedModifiedTimestampAndIdString`2.GetRpdePage(System.Nullable{System.Int64},System.String)~System.Threading.Tasks.Task{OpenActive.NET.Rpde.Version1.RpdePage}")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.OpenBookingHelper.RpdeOrderingStrategyRouter.GetRpdePage(OpenActive.Server.NET.OpenBookingHelper.IRpdeFeedGenerator,System.String,System.Nullable{System.Int64},System.String,System.Nullable{System.Int64})~System.Threading.Tasks.Task{OpenActive.NET.Rpde.Version1.RpdePage}")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1054:Change the type of parameter uriTemplate of method SingleIdTemplate.SingleIdTemplate(string) from string to System.Uri, or provide an overload to SingleIdTemplate.SingleIdTemplate(string) that allows uriTemplate to be passed as a System.Uri object.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.OpenBookingHelper.SingleIdTemplate`1.#ctor(System.String)")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1716:In virtual/interface member IOpportunityStore.SetConfiguration(IBookablePairIdTemplate, SingleIdTemplate), rename parameter template so that it no longer conflicts with the reserved language keyword 'template'. Using a reserved keyword as the name of a parameter on a virtual/interface member makes it harder for consumers in other languages to override/implement the member.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.IOpportunityStore.SetConfiguration(OpenActive.Server.NET.OpenBookingHelper.IBookablePairIdTemplate,OpenActive.Server.NET.OpenBookingHelper.SingleIdTemplate{OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents})")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.OpportunityStore`3.OpenActive#Server#NET#StoreBooking#IOpportunityStore#CreateOpportunityWithinTestDataset(System.String,OpenActive.DatasetSite.NET.OpportunityType,OpenActive.NET.TestOpportunityCriteriaEnumeration,OpenActive.NET.TestOpenBookingFlowEnumeration,OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents)~System.Threading.Tasks.Task{OpenActive.NET.Event}")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1716:In virtual/interface member IOpportunityStore.SetConfiguration(IBookablePairIdTemplate, SingleIdTemplate), rename parameter template so that it no longer conflicts with the reserved language keyword 'template'. Using a reserved keyword as the name of a parameter on a virtual/interface member makes it harder for consumers in other languages to override/implement the member.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.IOpportunityStore.SetConfiguration(OpenActive.Server.NET.OpenBookingHelper.IBookablePairIdTemplate,OpenActive.Server.NET.OpenBookingHelper.SingleIdTemplate{OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents})")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.OpportunityStore`3.OpenActive#Server#NET#StoreBooking#IOpportunityStore#CreateOpportunityWithinTestDataset(System.String,OpenActive.DatasetSite.NET.OpportunityType,OpenActive.NET.TestOpportunityCriteriaEnumeration,OpenActive.NET.TestOpenBookingFlowEnumeration,OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents)~System.Threading.Tasks.Task{OpenActive.NET.Event}")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.OpportunityStore`3.OpenActive#Server#NET#StoreBooking#IOpportunityStore#DeleteTestDataset(System.String)~System.Threading.Tasks.Task")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.OpportunityStore`3.OpenActive#Server#NET#StoreBooking#IOpportunityStore#TriggerTestAction(OpenActive.NET.OpenBookingSimulateAction,OpenActive.Server.NET.OpenBookingHelper.IBookableIdComponents)~System.Threading.Tasks.Task")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1801:Parameter belongsToOrderProposal of method SetOrderItemId is never used. Remove the parameter or use it in the method body.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.OrderItemContext`1.SetOrderItemId(OpenActive.Server.NET.StoreBooking.StoreBookingFlowContext,System.Nullable{System.Int64},System.String,System.Boolean)")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'void OrderItemContext.SetResponseOrderItem(OrderItem item, SellerIdComponents sellerId, StoreBookingFlowContext flowContext)', validate parameter 'flowContext' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.OrderItemContext`1.SetResponseOrderItem(OpenActive.NET.OrderItem,OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents,OpenActive.Server.NET.StoreBooking.StoreBookingFlowContext)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'void OrderItemContext.SetResponseOrderItem(OrderItem item, SimpleIdComponents sellerId, StoreBookingFlowContext flowContext)', validate parameter 'flowContext' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.OrderItemContext`1.SetResponseOrderItem(OpenActive.NET.OrderItem,OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents,OpenActive.Server.NET.StoreBooking.StoreBookingFlowContext)")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.DeleteTestDataset(System.String)~System.Threading.Tasks.Task")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.GetOrderItemContexts(System.Collections.Generic.List{OpenActive.NET.OrderItem},OpenActive.Server.NET.StoreBooking.StoreBookingFlowContext,OpenActive.Server.NET.StoreBooking.IStateContext)~System.Threading.Tasks.Task{System.}")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.InsertTestOpportunity(System.String,OpenActive.DatasetSite.NET.OpportunityType,OpenActive.NET.TestOpportunityCriteriaEnumeration,OpenActive.NET.TestOpenBookingFlowEnumeration,OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents)~System.Threading.Tasks.Task{OpenActive.NET.Event}")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessCustomerCancellation(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents,OpenActive.Server.NET.OpenBookingHelper.OrderIdTemplate,System.Collections.Generic.List{OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents})~System.Threading.Tasks.Task")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.InsertTestOpportunity(System.String,OpenActive.DatasetSite.NET.OpportunityType,OpenActive.NET.TestOpportunityCriteriaEnumeration,OpenActive.NET.TestOpenBookingFlowEnumeration,OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents)~System.Threading.Tasks.Task{OpenActive.NET.Event}")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessCustomerCancellation(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents,OpenActive.Server.NET.OpenBookingHelper.OrderIdTemplate,System.Collections.Generic.List{OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents})~System.Threading.Tasks.Task")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'Task StoreBookingEngine.ProcessFlowRequest(BookingFlowContext request, TOrder order)', validate parameter 'order' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessFlowRequest``1(OpenActive.Server.NET.OpenBookingHelper.BookingFlowContext,``0)~System.Threading.Tasks.Task{``0}")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessFlowRequest``1(OpenActive.Server.NET.OpenBookingHelper.BookingFlowContext,``0)~System.Threading.Tasks.Task{``0}")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessGetOrderStatus(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents,OpenActive.NET.ILegalEntity)~System.Threading.Tasks.Task{OpenActive.NET.Order}")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessOrderCreationFromOrderProposal(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.OrderIdTemplate,OpenActive.NET.ILegalEntity,OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents,OpenActive.NET.Order)~System.Threading.Tasks.Task{OpenActive.NET.Order}")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'Task StoreBookingEngine.ProcessOrderCreationFromOrderProposal(OrderIdComponents orderId, OrderIdTemplate orderIdTemplate, ILegalEntity seller, SellerIdComponents sellerId, Order order)', validate parameter 'order' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessOrderCreationFromOrderProposal(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.OrderIdTemplate,OpenActive.NET.ILegalEntity,OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents,OpenActive.NET.Order)~System.Threading.Tasks.Task{OpenActive.NET.Order}")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessOrderDeletion(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents)~System.Threading.Tasks.Task{OpenActive.Server.NET.StoreBooking.DeleteOrderResult}")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessOrderProposalCustomerRejection(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents,OpenActive.Server.NET.OpenBookingHelper.OrderIdTemplate)~System.Threading.Tasks.Task")] -[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessOrderQuoteDeletion(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.SellerIdComponents)~System.Threading.Tasks.Task")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessGetOrderStatus(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents,OpenActive.NET.ILegalEntity)~System.Threading.Tasks.Task{OpenActive.NET.Order}")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessOrderCreationFromOrderProposal(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.OrderIdTemplate,OpenActive.NET.ILegalEntity,OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents,OpenActive.NET.Order)~System.Threading.Tasks.Task{OpenActive.NET.Order}")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'Task StoreBookingEngine.ProcessOrderCreationFromOrderProposal(OrderIdComponents orderId, OrderIdTemplate orderIdTemplate, ILegalEntity seller, SimpleIdComponents sellerId, Order order)', validate parameter 'order' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessOrderCreationFromOrderProposal(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.OrderIdTemplate,OpenActive.NET.ILegalEntity,OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents,OpenActive.NET.Order)~System.Threading.Tasks.Task{OpenActive.NET.Order}")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessOrderDeletion(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents)~System.Threading.Tasks.Task{OpenActive.Server.NET.StoreBooking.DeleteOrderResult}")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessOrderProposalCustomerRejection(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents,OpenActive.Server.NET.OpenBookingHelper.OrderIdTemplate)~System.Threading.Tasks.Task")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.ProcessOrderQuoteDeletion(OpenActive.Server.NET.OpenBookingHelper.OrderIdComponents,OpenActive.Server.NET.OpenBookingHelper.SimpleIdComponents)~System.Threading.Tasks.Task")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2007:Consider calling ConfigureAwait on the awaited task", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.TriggerTestAction(OpenActive.NET.OpenBookingSimulateAction,OpenActive.Server.NET.OpenBookingHelper.OrderIdTemplate)~System.Threading.Tasks.Task")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA1062:In externally visible method 'Task StoreBookingEngine.TriggerTestAction(OpenBookingSimulateAction simulateAction, OrderIdTemplate orderIdTemplate)', validate parameter 'simulateAction' is non-null before using it. If appropriate, throw an ArgumentNullException when the argument is null or add a Code Contract precondition asserting non-null argument.", Justification = "", Scope = "member", Target = "~M:OpenActive.Server.NET.StoreBooking.StoreBookingEngine.TriggerTestAction(OpenActive.NET.OpenBookingSimulateAction,OpenActive.Server.NET.OpenBookingHelper.OrderIdTemplate)~System.Threading.Tasks.Task")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Build", "CA2227:Change 'IdConfiguration' to be read-only by removing the property setter.", Justification = "", Scope = "member", Target = "~P:OpenActive.Server.NET.OpenBookingHelper.BookingEngineSettings.IdConfiguration")] diff --git a/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs b/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs index 9c8dae30..4e85b4f9 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs @@ -32,9 +32,9 @@ protected Uri RenderOrderItemId(OrderType orderType, Guid uuid, long orderItemId return this.OrderIdTemplate.RenderOrderItemId(orderType, uuid, orderItemId); } - protected Uri RenderSellerId(SimpleIdComponents sellerIdComponents) + protected Uri RenderSellerId(SimpleIdComponents simpleIdComponents) { - return this.SellerIdTemplate.RenderId(sellerIdComponents); + return this.SellerIdTemplate.RenderId(simpleIdComponents); } protected Uri RenderSingleSellerId() diff --git a/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs b/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs index e2ce1432..6de4d80d 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs @@ -14,9 +14,9 @@ internal void SetConfiguration(SingleIdTemplate template) this.IdTemplate = template; } - protected Uri RenderSellerId(SimpleIdComponents sellerIdComponents) + protected Uri RenderSellerId(SimpleIdComponents simpleIdComponents) { - return this.IdTemplate.RenderId(sellerIdComponents); + return this.IdTemplate.RenderId(simpleIdComponents); } protected Uri RenderSingleSellerId() @@ -24,10 +24,10 @@ protected Uri RenderSingleSellerId() return this.IdTemplate.RenderId(new SimpleIdComponents()); } - internal async ValueTask GetSellerById(SimpleIdComponents sellerIdComponents) + internal async ValueTask GetSellerById(SimpleIdComponents simpleIdComponents) { // TODO: Include validation on the OrderItem created, to ensure it includes all the required fields - return await GetSeller(sellerIdComponents); + return await GetSeller(simpleIdComponents); } protected abstract ValueTask GetSeller(SimpleIdComponents sellerId); diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index 7f32ba5c..5fd4ec9d 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -419,13 +419,13 @@ private static void CheckOrderIntegrity(Order requestOrder, Order responseOrder) } } - protected override async Task ProcessGetOrderStatus(OrderIdComponents orderId, SimpleIdComponents sellerIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents) + protected override async Task ProcessGetOrderStatus(OrderIdComponents orderId, SimpleIdComponents simpleIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents) { // Get Order without OrderItems expanded - var order = await storeBookingEngineSettings.OrderStore.GetOrderStatus(orderId, sellerIdComponents, seller); + var order = await storeBookingEngineSettings.OrderStore.GetOrderStatus(orderId, simpleIdComponents, seller); // Get flowContext from resulting Order, treating it like a request (which also validates it like a request) - var flowContext = AugmentContextFromOrder(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, FlowStage.OrderStatus, order), order); + var flowContext = AugmentContextFromOrder(ValidateFlowRequest(orderId, simpleIdComponents, seller, customerAccountIdComponents, FlowStage.OrderStatus, order), order); // Expand OrderItems based on the flowContext // Get contexts from OrderItems From c2a0194d1e6eeacc98930f78338eaa62ff0a23ad Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Fri, 27 May 2022 10:47:06 +0100 Subject: [PATCH 11/33] Revert `simpleIdComponents` to `sellerIdComponents` --- .../BookingSystem.AspNetCore.csproj | 2 +- .../Stores/SellerStore.cs | 8 +-- .../BookingSystem.AspNetFramework.csproj | 2 +- .../Stores/SellerStore.cs | 8 +-- .../packages.config | 2 +- .../OpenActive.FakeDatabase.NET.Tests.csproj | 2 +- .../OpenActive.FakeDatabase.NET.csproj | 2 +- .../OpenActive.Server.NET.Tests.csproj | 2 +- .../CustomBookingEngine.cs | 50 +++++++++---------- .../OpenActive.Server.NET.csproj | 2 +- .../Model/OrderModelSupport.cs | 4 +- .../OpenBookingHelper/Stores/SellerStore.cs | 8 +-- .../StoreBookingEngine/StoreBookingEngine.cs | 6 +-- 13 files changed, 49 insertions(+), 49 deletions(-) diff --git a/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj b/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj index 0c5bd068..d0c2b4e9 100644 --- a/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj +++ b/Examples/BookingSystem.AspNetCore/BookingSystem.AspNetCore.csproj @@ -11,7 +11,7 @@ true - + diff --git a/Examples/BookingSystem.AspNetCore/Stores/SellerStore.cs b/Examples/BookingSystem.AspNetCore/Stores/SellerStore.cs index fe73c309..5dda02fa 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/SellerStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/SellerStore.cs @@ -23,12 +23,12 @@ public AcmeSellerStore(bool useSingleSellerMode) } // If the Seller is not found, simply return null to generate the correct Open Booking error - protected override async ValueTask GetSeller(SimpleIdComponents simpleIdComponents) + protected override async ValueTask GetSeller(SimpleIdComponents sellerIdComponents) { // Note both examples are shown below to demonstrate options available. Only one block of the if statement below is required for an actual implementation. if (_useSingleSellerMode) { - // For Single Seller booking systems, no ID will be available from simpleIdComponents, and this data should instead come from your configuration table + // For Single Seller booking systems, no ID will be available from sellerIdComponents, and this data should instead come from your configuration table return new Organization { Id = RenderSingleSellerId(), @@ -56,10 +56,10 @@ protected override async ValueTask GetSeller(SimpleIdComponents si }; } - // Otherwise it may be looked up based on supplied simpleIdComponents which are extracted from the sellerId. + // Otherwise it may be looked up based on supplied sellerIdComponents which are extracted from the sellerId. using (var db = FakeBookingSystem.Database.Mem.Database.Open()) { - var seller = db.SingleById(simpleIdComponents.IdLong); + var seller = db.SingleById(sellerIdComponents.IdLong); if (seller == null) { // Seller not found diff --git a/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj b/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj index f1fcd9a2..3db64a3f 100644 --- a/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj +++ b/Examples/BookingSystem.AspNetFramework/BookingSystem.AspNetFramework.csproj @@ -196,7 +196,7 @@ ..\..\packages\OpenActive.DatasetSite.NET.4.2.0\lib\net45\OpenActive.DatasetSite.NET.dll - ..\..\packages\OpenActive.NET.15.2.10\lib\netstandard2.0\OpenActive.NET.dll + ..\..\packages\OpenActive.NET.15.2.11\lib\netstandard2.0\OpenActive.NET.dll ..\..\packages\Schema.NET.7.0.1\lib\net461\Schema.NET.dll diff --git a/Examples/BookingSystem.AspNetFramework/Stores/SellerStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/SellerStore.cs index fe73c309..5dda02fa 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/SellerStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/SellerStore.cs @@ -23,12 +23,12 @@ public AcmeSellerStore(bool useSingleSellerMode) } // If the Seller is not found, simply return null to generate the correct Open Booking error - protected override async ValueTask GetSeller(SimpleIdComponents simpleIdComponents) + protected override async ValueTask GetSeller(SimpleIdComponents sellerIdComponents) { // Note both examples are shown below to demonstrate options available. Only one block of the if statement below is required for an actual implementation. if (_useSingleSellerMode) { - // For Single Seller booking systems, no ID will be available from simpleIdComponents, and this data should instead come from your configuration table + // For Single Seller booking systems, no ID will be available from sellerIdComponents, and this data should instead come from your configuration table return new Organization { Id = RenderSingleSellerId(), @@ -56,10 +56,10 @@ protected override async ValueTask GetSeller(SimpleIdComponents si }; } - // Otherwise it may be looked up based on supplied simpleIdComponents which are extracted from the sellerId. + // Otherwise it may be looked up based on supplied sellerIdComponents which are extracted from the sellerId. using (var db = FakeBookingSystem.Database.Mem.Database.Open()) { - var seller = db.SingleById(simpleIdComponents.IdLong); + var seller = db.SingleById(sellerIdComponents.IdLong); if (seller == null) { // Seller not found diff --git a/Examples/BookingSystem.AspNetFramework/packages.config b/Examples/BookingSystem.AspNetFramework/packages.config index 9a7e8054..113f2ce4 100644 --- a/Examples/BookingSystem.AspNetFramework/packages.config +++ b/Examples/BookingSystem.AspNetFramework/packages.config @@ -81,7 +81,7 @@ - + diff --git a/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj b/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj index b5db3fdd..3ffa730d 100644 --- a/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj +++ b/Fakes/OpenActive.FakeDatabase.NET.Tests/OpenActive.FakeDatabase.NET.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj b/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj index 54e5a698..d327f35d 100644 --- a/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj +++ b/Fakes/OpenActive.FakeDatabase.NET/OpenActive.FakeDatabase.NET.csproj @@ -33,7 +33,7 @@ - + diff --git a/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj b/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj index 93104867..090509c6 100644 --- a/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj +++ b/OpenActive.Server.NET.Tests/OpenActive.Server.NET.Tests.csproj @@ -11,7 +11,7 @@ - + diff --git a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs index 24b69166..afa86e7b 100644 --- a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs +++ b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs @@ -331,8 +331,8 @@ private async Task RenderOrdersRPDEPageForFeed(OrderType feedType, str public async Task GetOrderStatus(string clientId, Uri sellerId, string uuidString, Uri customerAccountId = null) { - var (orderId, simpleIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.Order); - var result = await ProcessGetOrderStatus(orderId, simpleIdComponents, seller, customerAccountIdComponents); + var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.Order); + var result = await ProcessGetOrderStatus(orderId, sellerIdComponents, seller, customerAccountIdComponents); if (result == null) { throw new OpenBookingException(new UnknownOrderError()); @@ -402,10 +402,10 @@ private async Task ProcessCheckpoint(string clientId, Uri selle { throw new OpenBookingException(new UnexpectedOrderTypeError(), "OrderQuote is required for C1 and C2"); } - var (orderId, simpleIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, orderType); + var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, orderType); using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { - var orderResponse = await ProcessFlowRequest(ValidateFlowRequest(orderId, simpleIdComponents, seller, customerAccountIdComponents, flowStage, orderQuote), orderQuote); + var orderResponse = await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, flowStage, orderQuote), orderQuote); // Return a 409 status code if any OrderItem level errors exist return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(orderResponse), @@ -422,12 +422,12 @@ public async Task ProcessOrderCreationB(string clientId, Uri se { throw new OpenBookingException(new UnexpectedOrderTypeError(), "Order is required for B"); } - var (orderId, simpleIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.Order); + var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.Order); using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { var response = order.OrderProposalVersion != null ? - await ProcessOrderCreationFromOrderProposal(orderId, settings.OrderIdTemplate, seller, simpleIdComponents, customerAccountIdComponents, order) : - await ProcessFlowRequest(ValidateFlowRequest(orderId, simpleIdComponents, seller, customerAccountIdComponents, FlowStage.B, order), order); + await ProcessOrderCreationFromOrderProposal(orderId, settings.OrderIdTemplate, seller, sellerIdComponents, customerAccountIdComponents, order) : + await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, FlowStage.B, order), order); // Return a 409 status code if any OrderItem level errors exist return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(response), @@ -445,10 +445,10 @@ public async Task ProcessOrderProposalCreationP(string clientId { throw new OpenBookingException(new UnexpectedOrderTypeError(), "OrderProposal is required for P"); } - var (orderId, simpleIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.OrderProposal); + var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.OrderProposal); using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { - return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(await ProcessFlowRequest(ValidateFlowRequest(orderId, simpleIdComponents, seller, customerAccountIdComponents, FlowStage.P, order), order)), HttpStatusCode.Created); + return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, FlowStage.P, order), order)), HttpStatusCode.Created); } } @@ -457,9 +457,9 @@ private SimpleIdComponents GetSimpleIdComponentsFromApiKey(Uri sellerId) // Return empty SimpleIdComponents in Single Seller mode, as it is not required in the API Key if (settings.HasSingleSeller == true) return new SimpleIdComponents(); - var simpleIdComponents = settings.SellerIdTemplate.GetIdComponents(sellerId); - if (simpleIdComponents == null) throw new OpenBookingException(new InvalidAPITokenError()); - return simpleIdComponents; + var sellerIdComponents = settings.SellerIdTemplate.GetIdComponents(sellerId); + if (sellerIdComponents == null) throw new OpenBookingException(new InvalidAPITokenError()); + return sellerIdComponents; } private SimpleIdComponents GetCustomerAccountIdComponentsFromApiKey(Uri customerAccountId) @@ -513,7 +513,7 @@ public async Task ProcessOrderUpdate(string clientId, Uri selle using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { Order order = OpenActiveSerializer.Deserialize(orderJson); - SimpleIdComponents simpleIdComponents = GetSimpleIdComponentsFromApiKey(sellerId); + SimpleIdComponents sellerIdComponents = GetSimpleIdComponentsFromApiKey(sellerId); SimpleIdComponents customerAccountIdComponents = GetCustomerAccountIdComponentsFromApiKey(customerAccountId); if (order == null || order.GetType() != typeof(Order)) @@ -559,7 +559,7 @@ public async Task ProcessOrderUpdate(string clientId, Uri selle throw new OpenBookingException(new OrderItemNotWithinOrderError()); } - await ProcessCustomerCancellation(orderId, simpleIdComponents, customerAccountIdComponents, settings.OrderIdTemplate, orderItemIds); + await ProcessCustomerCancellation(orderId, sellerIdComponents, customerAccountIdComponents, settings.OrderIdTemplate, orderItemIds); return ResponseContent.OpenBookingNoContentResponse(); } @@ -574,7 +574,7 @@ public async Task ProcessOrderProposalUpdate(string clientId, U using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { OrderProposal orderProposal = OpenActiveSerializer.Deserialize(orderProposalJson); - SimpleIdComponents simpleIdComponents = GetSimpleIdComponentsFromApiKey(sellerId); + SimpleIdComponents sellerIdComponents = GetSimpleIdComponentsFromApiKey(sellerId); SimpleIdComponents customerAccountIdComponents = GetCustomerAccountIdComponentsFromApiKey(customerAccountId); if (orderProposal == null || orderProposal.GetType() != typeof(Order)) @@ -599,7 +599,7 @@ public async Task ProcessOrderProposalUpdate(string clientId, U throw new OpenBookingException(new PatchNotAllowedOnPropertyError(), "Only 'https://openactive.io/CustomerRejected' is permitted for this property."); } - await ProcessOrderProposalCustomerRejection(orderId, simpleIdComponents, customerAccountIdComponents, settings.OrderIdTemplate); + await ProcessOrderProposalCustomerRejection(orderId, sellerIdComponents, customerAccountIdComponents, settings.OrderIdTemplate); return ResponseContent.OpenBookingNoContentResponse(); } @@ -706,11 +706,11 @@ async Task IBookingEngine.InsertTestOpportunity(string testData } if (seller?.Id == null) throw new OpenBookingException(new SellerMismatchError(), "No seller ID was specified"); - var simpleIdComponents = settings.SellerIdTemplate.GetIdComponents(seller.Id); - if (simpleIdComponents == null) throw new OpenBookingException(new SellerMismatchError(), "Seller ID format was invalid"); + var sellerIdComponents = settings.SellerIdTemplate.GetIdComponents(seller.Id); + if (sellerIdComponents == null) throw new OpenBookingException(new SellerMismatchError(), "Seller ID format was invalid"); // Returns a matching Event subclass that will only include "@type" and "@id" properties - var createdEvent = await this.InsertTestOpportunity(testDatasetIdentifier, opportunityType.Value, genericEvent.TestOpportunityCriteria.Value, genericEvent.TestOpenBookingFlow.Value, simpleIdComponents); + var createdEvent = await this.InsertTestOpportunity(testDatasetIdentifier, opportunityType.Value, genericEvent.TestOpportunityCriteria.Value, genericEvent.TestOpenBookingFlow.Value, sellerIdComponents); if (createdEvent.Type != genericEvent.Type) { @@ -752,7 +752,7 @@ async Task IBookingEngine.TriggerTestAction(string actionJson) protected abstract Task TriggerTestAction(OpenBookingSimulateAction simulateAction, OrderIdTemplate orderIdTemplate); - private async Task<(OrderIdComponents orderId, SimpleIdComponents simpleIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents)> ConstructIdsFromRequest(string clientId, Uri authenticationSellerId, Uri authenticationCustomerAccountId, string uuidString, OrderType orderType) + private async Task<(OrderIdComponents orderId, SimpleIdComponents sellerIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents)> ConstructIdsFromRequest(string clientId, Uri authenticationSellerId, Uri authenticationCustomerAccountId, string uuidString, OrderType orderType) { var orderId = new OrderIdComponents { @@ -763,22 +763,22 @@ async Task IBookingEngine.TriggerTestAction(string actionJson) // TODO: Add more request validation rules here - SimpleIdComponents simpleIdComponents = GetSimpleIdComponentsFromApiKey(authenticationSellerId); + SimpleIdComponents sellerIdComponents = GetSimpleIdComponentsFromApiKey(authenticationSellerId); SimpleIdComponents customerAccountIdComponents = GetCustomerAccountIdComponentsFromApiKey(authenticationCustomerAccountId); - ILegalEntity seller = await settings.SellerStore.GetSellerById(simpleIdComponents); + ILegalEntity seller = await settings.SellerStore.GetSellerById(sellerIdComponents); if (seller == null) { throw new OpenBookingException(new SellerNotFoundError()); } - return (orderId, simpleIdComponents, seller, customerAccountIdComponents); + return (orderId, sellerIdComponents, seller, customerAccountIdComponents); } //TODO: Should we move Seller into the Abstract level? Perhaps too much complexity - protected BookingFlowContext ValidateFlowRequest(OrderIdComponents orderId, SimpleIdComponents simpleIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents, FlowStage stage, TOrder order) where TOrder : Order, new() + protected BookingFlowContext ValidateFlowRequest(OrderIdComponents orderId, SimpleIdComponents sellerIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents, FlowStage stage, TOrder order) where TOrder : Order, new() { // If being called from Order Status then expect Seller to already be a full object var sellerIdFromOrder = stage == FlowStage.OrderStatus ? order?.Seller.Object?.Id : order?.Seller.IdReference; @@ -843,7 +843,7 @@ async Task IBookingEngine.TriggerTestAction(string actionJson) OrderId = orderId, OrderIdTemplate = settings.OrderIdTemplate, Seller = seller, - SellerId = simpleIdComponents, + SellerId = sellerIdComponents, CustomerAccountId = customerAccountIdComponents, TaxPayeeRelationship = taxPayeeRelationship, Payer = payer, diff --git a/OpenActive.Server.NET/OpenActive.Server.NET.csproj b/OpenActive.Server.NET/OpenActive.Server.NET.csproj index b2ef917a..44cbc065 100644 --- a/OpenActive.Server.NET/OpenActive.Server.NET.csproj +++ b/OpenActive.Server.NET/OpenActive.Server.NET.csproj @@ -31,7 +31,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs b/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs index 4e85b4f9..9c8dae30 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Model/OrderModelSupport.cs @@ -32,9 +32,9 @@ protected Uri RenderOrderItemId(OrderType orderType, Guid uuid, long orderItemId return this.OrderIdTemplate.RenderOrderItemId(orderType, uuid, orderItemId); } - protected Uri RenderSellerId(SimpleIdComponents simpleIdComponents) + protected Uri RenderSellerId(SimpleIdComponents sellerIdComponents) { - return this.SellerIdTemplate.RenderId(simpleIdComponents); + return this.SellerIdTemplate.RenderId(sellerIdComponents); } protected Uri RenderSingleSellerId() diff --git a/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs b/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs index 6de4d80d..e2ce1432 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/Stores/SellerStore.cs @@ -14,9 +14,9 @@ internal void SetConfiguration(SingleIdTemplate template) this.IdTemplate = template; } - protected Uri RenderSellerId(SimpleIdComponents simpleIdComponents) + protected Uri RenderSellerId(SimpleIdComponents sellerIdComponents) { - return this.IdTemplate.RenderId(simpleIdComponents); + return this.IdTemplate.RenderId(sellerIdComponents); } protected Uri RenderSingleSellerId() @@ -24,10 +24,10 @@ protected Uri RenderSingleSellerId() return this.IdTemplate.RenderId(new SimpleIdComponents()); } - internal async ValueTask GetSellerById(SimpleIdComponents simpleIdComponents) + internal async ValueTask GetSellerById(SimpleIdComponents sellerIdComponents) { // TODO: Include validation on the OrderItem created, to ensure it includes all the required fields - return await GetSeller(simpleIdComponents); + return await GetSeller(sellerIdComponents); } protected abstract ValueTask GetSeller(SimpleIdComponents sellerId); diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index 5fd4ec9d..7f32ba5c 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -419,13 +419,13 @@ private static void CheckOrderIntegrity(Order requestOrder, Order responseOrder) } } - protected override async Task ProcessGetOrderStatus(OrderIdComponents orderId, SimpleIdComponents simpleIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents) + protected override async Task ProcessGetOrderStatus(OrderIdComponents orderId, SimpleIdComponents sellerIdComponents, ILegalEntity seller, SimpleIdComponents customerAccountIdComponents) { // Get Order without OrderItems expanded - var order = await storeBookingEngineSettings.OrderStore.GetOrderStatus(orderId, simpleIdComponents, seller); + var order = await storeBookingEngineSettings.OrderStore.GetOrderStatus(orderId, sellerIdComponents, seller); // Get flowContext from resulting Order, treating it like a request (which also validates it like a request) - var flowContext = AugmentContextFromOrder(ValidateFlowRequest(orderId, simpleIdComponents, seller, customerAccountIdComponents, FlowStage.OrderStatus, order), order); + var flowContext = AugmentContextFromOrder(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, FlowStage.OrderStatus, order), order); // Expand OrderItems based on the flowContext // Get contexts from OrderItems From 40a30b1fa78cfb273ea3462fdffd7caf2b6115ad Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Fri, 27 May 2022 11:09:01 +0100 Subject: [PATCH 12/33] Fix for missing settings --- .../CustomBookingEngine/CustomBookingEngine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs index afa86e7b..95f9577b 100644 --- a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs +++ b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs @@ -67,7 +67,7 @@ public CustomBookingEngine(BookingEngineSettings settings, Uri openBookingAPIBas } settings.OrderIdTemplate.RequiredBaseUrl = openBookingAPIBaseUrl; settings.SellerIdTemplate.RequiredBaseUrl = settings.JsonLdIdBaseUrl; - settings.CustomerAccountIdTemplate.RequiredBaseUrl = settings.CustomerAccountIdBaseUrl; + if (settings.CustomerAccountIdTemplate != null)Fix for settings.CustomerAccountIdTemplate.RequiredBaseUrl = settings.CustomerAccountIdBaseUrl; // Create a lookup of each IdTemplate to pass into the appropriate RpdeGenerator // TODO: Output better error if there is a feed assigned across two templates From ae4633634442f72ff68b6d005462284ebcc71813 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Fri, 27 May 2022 12:14:17 +0100 Subject: [PATCH 13/33] Fix for AugmentOrderWithTotals --- .../CustomBookingEngine/CustomBookingEngine.cs | 2 +- .../StoreBookingEngine/StoreBookingEngine.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs index 95f9577b..2c6beefd 100644 --- a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs +++ b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs @@ -67,7 +67,7 @@ public CustomBookingEngine(BookingEngineSettings settings, Uri openBookingAPIBas } settings.OrderIdTemplate.RequiredBaseUrl = openBookingAPIBaseUrl; settings.SellerIdTemplate.RequiredBaseUrl = settings.JsonLdIdBaseUrl; - if (settings.CustomerAccountIdTemplate != null)Fix for settings.CustomerAccountIdTemplate.RequiredBaseUrl = settings.CustomerAccountIdBaseUrl; + if (settings.CustomerAccountIdTemplate != null) settings.CustomerAccountIdTemplate.RequiredBaseUrl = settings.CustomerAccountIdBaseUrl; // Create a lookup of each IdTemplate to pass into the appropriate RpdeGenerator // TODO: Output better error if there is a feed assigned across two templates diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index 7f32ba5c..da64dc81 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -695,13 +695,14 @@ public override async Task ProcessFlowRequest(BookingFlowContext OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList() }; - if (HasOrderItemErrors(responseGenericOrder)) - { + // TODO: AugmentOrderWithTotals is currently running twice - once here and once after the [Lease/Book/Propose]OrderItems calls in case the price has changed due to the Customer Account's entitlements. This could be optimised. + //if (HasOrderItemErrors(responseGenericOrder)) + //{ // Ensure that the Order is augmented with totals, and do not continue to process the Order if there are already OrderItem level errors OrderCalculations.AugmentOrderWithTotals(responseGenericOrder, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); Console.WriteLine($"## {flowContext.OrderId.uuid} | LEAVING CRITICAL SECTION {flowContext.Stage.ToString()} for {flowContext?.Customer?.Email ?? "?"}"); return responseGenericOrder; - } + //} try { From e1655e512f537f996d5087b2ce34c5ae8ec1309c Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Fri, 27 May 2022 12:16:25 +0100 Subject: [PATCH 14/33] Second fix attempt --- .../StoreBookingEngine/StoreBookingEngine.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index da64dc81..2e24f713 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -695,14 +695,14 @@ public override async Task ProcessFlowRequest(BookingFlowContext OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList() }; - // TODO: AugmentOrderWithTotals is currently running twice - once here and once after the [Lease/Book/Propose]OrderItems calls in case the price has changed due to the Customer Account's entitlements. This could be optimised. - //if (HasOrderItemErrors(responseGenericOrder)) - //{ + // TODO: AugmentOrderWithTotals is currently running twice - once here and once after the [Lease/Book/Propose]OrderItems calls in case the price has changed due to the Customer Account's entitlements. This could be optimised, and perhaps it could be moved into the if block below. + OrderCalculations.AugmentOrderWithTotals(responseGenericOrder, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); + if (HasOrderItemErrors(responseGenericOrder)) + { // Ensure that the Order is augmented with totals, and do not continue to process the Order if there are already OrderItem level errors - OrderCalculations.AugmentOrderWithTotals(responseGenericOrder, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); Console.WriteLine($"## {flowContext.OrderId.uuid} | LEAVING CRITICAL SECTION {flowContext.Stage.ToString()} for {flowContext?.Customer?.Email ?? "?"}"); return responseGenericOrder; - //} + } try { From e6b2093d2e1f6f94aa5ea3e62ad51540d87c50e8 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Fri, 27 May 2022 12:34:17 +0100 Subject: [PATCH 15/33] Fix for IncompleteOrderItemError --- .../OpenBookingHelper/IdTransforms/IdTemplate.cs | 4 ++-- .../StoreBookingEngine/StoreBookingEngine.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs b/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs index ad6fbfb5..8ef8bdf5 100644 --- a/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs +++ b/OpenActive.Server.NET/OpenBookingHelper/IdTransforms/IdTemplate.cs @@ -123,7 +123,7 @@ public override IBookableIdComponents GetOpportunityReference(Uri opportunityId, { // Require both opportunityId and offerId to not be null if (opportunityId == null) throw new ArgumentNullException(nameof(opportunityId)); - // if (offerId == null) throw new ArgumentNullException(nameof(offerId)); + if (offerId == null) throw new ArgumentNullException(nameof(offerId)); // As inheritance is in use, the Offer must be resolved against either: Opportunity with Offer; or Opportunity and _parent_ Offer // Note in OpenActive Modelling Specification 2.0 this behaviour is only applicable to SessionSeries and ScheduledSession @@ -229,7 +229,7 @@ public virtual IBookableIdComponents GetOpportunityReference(Uri opportunityId, // Require both opportunityId and offerId to not be null if (opportunityId == null) throw new ArgumentNullException(nameof(opportunityId)); - // if (offerId == null) throw new ArgumentNullException(nameof(offerId)); + if (offerId == null) throw new ArgumentNullException(nameof(offerId)); // Without inheritance, the Offer must be resolved against either: Opportunity with Offer; or _parent_ Opportunity and parent Offer // Note that if any URL templates to be used for one of the checks below are null, the result for that check will be null diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index 2e24f713..a934d8d6 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -478,14 +478,14 @@ private List GetOrderItemContexts(List sourceOrder } /* - TODO: Check if Customer Account auth and throw if not + TODO: Check if Customer Account auth and only throw the error below if not + */ if (acceptedOfferId == null) { return new UnknownOrderItemContext(index, orderItem, new IncompleteOrderItemError(), "acceptedOffer @id was not provided"); } - */ - + var idComponents = base.ResolveOpportunityID(orderedItemId, acceptedOfferId); if (idComponents == null) From d8e017093dd9b270cba6581426282b4bd795f604 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Fri, 27 May 2022 12:51:47 +0100 Subject: [PATCH 16/33] Further fix for IncompleteOrderItemError --- .../StoreBookingEngine/StoreBookingEngine.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index a934d8d6..4b0733ac 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -585,24 +585,7 @@ private async Task> GetOrderItemContextGroups(List( - "{+BaseUrl}/api/customer-accounts/{IdGuid}" - ); - // Hack to add CustomerAccountId - context.CustomerAccountId = template.GetIdComponents(order.Customer.HasAccount.IdReference); - } */ + } else { // Reflect back only those customer fields that are supported From d6c689a4fafd40dbaaf834c5904e83c2a953b992 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Fri, 27 May 2022 13:12:42 +0100 Subject: [PATCH 17/33] Fix for OrderRequiresApproval --- .../StoreBookingEngine/StoreBookingEngine.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index 4b0733ac..2f6721ed 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -679,11 +679,20 @@ public override async Task ProcessFlowRequest(BookingFlowContext }; // TODO: AugmentOrderWithTotals is currently running twice - once here and once after the [Lease/Book/Propose]OrderItems calls in case the price has changed due to the Customer Account's entitlements. This could be optimised, and perhaps it could be moved into the if block below. + // Ensure that the Order is augmented with totals OrderCalculations.AugmentOrderWithTotals(responseGenericOrder, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); if (HasOrderItemErrors(responseGenericOrder)) { - // Ensure that the Order is augmented with totals, and do not continue to process the Order if there are already OrderItem level errors + // Do not continue to process the Order if there are already OrderItem level errors Console.WriteLine($"## {flowContext.OrderId.uuid} | LEAVING CRITICAL SECTION {flowContext.Stage.ToString()} for {flowContext?.Customer?.Email ?? "?"}"); + + switch (responseGenericOrder) + { + case OrderQuote responseOrderQuote: + // Note OrderRequiresApproval is only required during C1 and C2 + responseOrderQuote.OrderRequiresApproval = orderItemContexts.Any(x => x.RequiresApproval); + break; + } return responseGenericOrder; } From 9badbeaab031c59b39713963df554ca86fa14348 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Fri, 27 May 2022 13:41:00 +0100 Subject: [PATCH 18/33] Remove critical section console logs --- .../StoreBookingEngine/StoreBookingEngine.cs | 393 +++++++++--------- .../Stores/OpportunityStore.cs | 2 +- 2 files changed, 194 insertions(+), 201 deletions(-) diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index 2f6721ed..a8af4beb 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -177,14 +177,15 @@ public void SetResponseOrderItem(OrderItem item, SimpleIdComponents sellerId, St { throw new OpenBookingException(new InternalLibraryError(), "The Opportunity @id within the response OrderItem must match the request OrderItem"); } + // TODO: Make the below checks conditional on whether this is a Customer Account request var requestAcceptedOfferId = RequestOrderItem?.AcceptedOffer.IdReference; if (requestAcceptedOfferId == null) { - //throw new OpenBookingException(new InternalLibraryError(), "Request must include an acceptedOffer for the OrderItem"); + throw new OpenBookingException(new InternalLibraryError(), "Request must include an acceptedOffer for the OrderItem"); } if (item?.AcceptedOffer.Object?.Id != requestAcceptedOfferId) { - //throw new OpenBookingException(new InternalLibraryError(), "The Offer ID within the response OrderItem must match the request OrderItem"); + throw new OpenBookingException(new InternalLibraryError(), "The Offer ID within the response OrderItem must match the request OrderItem"); } if (sellerId != flowContext.SellerId) @@ -654,8 +655,6 @@ public override async Task ProcessFlowRequest(BookingFlowContext // StateContext is useful for transferring state between stages of the flow, and initialising disposable resources for use throughout the flow using (var stateContext = await storeBookingEngineSettings.OrderStore.CreateOrderStateContext(flowContext)) { - Console.WriteLine($"## {flowContext.OrderId.uuid} | ENTERING CRITICAL SECTION {flowContext.Stage.ToString()} for {flowContext?.Customer?.Email ?? "?"}"); - // Runs before the flow starts, for both leasing and booking await storeBookingEngineSettings.OrderStore.Initialise(flowContext, stateContext); @@ -684,8 +683,6 @@ public override async Task ProcessFlowRequest(BookingFlowContext if (HasOrderItemErrors(responseGenericOrder)) { // Do not continue to process the Order if there are already OrderItem level errors - Console.WriteLine($"## {flowContext.OrderId.uuid} | LEAVING CRITICAL SECTION {flowContext.Stage.ToString()} for {flowContext?.Customer?.Email ?? "?"}"); - switch (responseGenericOrder) { case OrderQuote responseOrderQuote: @@ -697,259 +694,255 @@ public override async Task ProcessFlowRequest(BookingFlowContext } try + { + // Transaction is wrapped around all write operations + using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions + ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() + : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) { - // Transaction is wrapped around all write operations - using (IDatabaseTransaction dbTransaction = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions - ? storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.BeginOrderTransaction(flowContext.Stage)) + try { - try + switch (responseGenericOrder) { - switch (responseGenericOrder) - { - case OrderProposal responseOrderProposal: - // Proposal creation is atomic + case OrderProposal responseOrderProposal: + // Proposal creation is atomic - if (flowContext.Stage != FlowStage.P) - throw new OpenBookingException(new UnexpectedOrderTypeError()); + if (flowContext.Stage != FlowStage.P) + throw new OpenBookingException(new UnexpectedOrderTypeError()); - // Proposal creation is atomic - if (dbTransaction == null) - { - throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for OrderProposal Creation at P, to ensure the integrity of the booking made."); - } + // Proposal creation is atomic + if (dbTransaction == null) + { + throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for OrderProposal Creation at P, to ensure the integrity of the booking made."); + } - // Create the parent Order - var (version, orderProposalStatus) = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? - storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); + // Create the parent Order + var (version, orderProposalStatus) = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? + storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() + : await storeBookingEngineSettings.OrderStore.CreateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); - responseOrderProposal.OrderProposalVersion = new Uri($"{responseOrderProposal.Id}/versions/{version}"); - responseOrderProposal.OrderProposalStatus = orderProposalStatus; + responseOrderProposal.OrderProposalVersion = new Uri($"{responseOrderProposal.Id}/versions/{version}"); + responseOrderProposal.OrderProposalStatus = orderProposalStatus; - // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call - // Note the happens first to ensure no conflicts with new OrderItems that have since been added - foreach (var g in orderItemGroups) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); - } + // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call + // Note the happens first to ensure no conflicts with new OrderItems that have since been added + foreach (var g in orderItemGroups) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + } - // Book the OrderItems - foreach (var g in orderItemGroups) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); + // Book the OrderItems + foreach (var g in orderItemGroups) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.ProposeOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); - foreach (var ctx in g.OrderItemContexts) + foreach (var ctx in g.OrderItemContexts) + { + // Check that OrderItem Id was added + if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) { - // Check that OrderItem Id was added - if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) - { - throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in ProposeOrderItems for successfully booked items"); - } - - // Set the orderItemStatus to null (as it must always be so in the response of P) - ctx.ResponseOrderItem.OrderItemStatus = null; + throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in ProposeOrderItems for successfully booked items"); } + + // Set the orderItemStatus to null (as it must always be so in the response of P) + ctx.ResponseOrderItem.OrderItemStatus = null; } + } - // Update this in case ResponseOrderItem was overwritten in ProposeOrderItems - responseOrderProposal.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); + // Update this in case ResponseOrderItem was overwritten in ProposeOrderItems + responseOrderProposal.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); - // Add totals to the resulting Order - OrderCalculations.AugmentOrderWithTotals( - responseOrderProposal, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); + // Add totals to the resulting Order + OrderCalculations.AugmentOrderWithTotals( + responseOrderProposal, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); - if (!HasOrderItemErrors(responseOrderProposal)) - { - // Check for integrity, as if no OrderItem errors the price will be accurate - CheckOrderIntegrity(order, responseOrderProposal); - } - else - { - // Rollback for OrderItem errors - throw new SilentRollbackException(); - } + if (!HasOrderItemErrors(responseOrderProposal)) + { + // Check for integrity, as if no OrderItem errors the price will be accurate + CheckOrderIntegrity(order, responseOrderProposal); + } + else + { + // Rollback for OrderItem errors + throw new SilentRollbackException(); + } - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await storeBookingEngineSettings.OrderStore.UpdateOrderProposal(responseOrderProposal, flowContext, stateContext, dbTransaction); - break; + break; - case OrderQuote responseOrderQuote: - // Note behaviour here is to lease those items that are available to be leased, and return errors for everything else - // Leasing is optimistic, booking is atomic + case OrderQuote responseOrderQuote: + // Note behaviour here is to lease those items that are available to be leased, and return errors for everything else + // Leasing is optimistic, booking is atomic - if (!(flowContext.Stage == FlowStage.C1 || flowContext.Stage == FlowStage.C2)) - throw new OpenBookingException(new UnexpectedOrderTypeError()); + if (!(flowContext.Stage == FlowStage.C1 || flowContext.Stage == FlowStage.C2)) + throw new OpenBookingException(new UnexpectedOrderTypeError()); - responseOrderQuote.Lease = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? - storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() - : await storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); + responseOrderQuote.Lease = storeBookingEngineSettings.EnforceSyncWithinOrderTransactions ? + storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorkedAndReturnResult() + : await storeBookingEngineSettings.OrderStore.CreateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); + + // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call + // Note the happens first to ensure no conflicts with new OrderItems that have since been added + foreach (var g in orderItemGroups) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + } - // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call - // Note the happens first to ensure no conflicts with new OrderItems that have since been added + if (responseOrderQuote.Lease != null) + { foreach (var g in orderItemGroups) { if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await g.Store.CleanupOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + await g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); } + } - if (responseOrderQuote.Lease != null) - { - foreach (var g in orderItemGroups) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.LeaseOrderItems(responseOrderQuote.Lease, g.OrderItemContexts, flowContext, stateContext, dbTransaction); - } - } + // Update this in case ResponseOrderItem was overwritten in Lease + responseOrderQuote.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); - // Update this in case ResponseOrderItem was overwritten in Lease - responseOrderQuote.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); + // Note OrderRequiresApproval is only required during C1 and C2 + responseOrderQuote.OrderRequiresApproval = orderItemContexts.Any(x => x.RequiresApproval); - // Note OrderRequiresApproval is only required during C1 and C2 - responseOrderQuote.OrderRequiresApproval = orderItemContexts.Any(x => x.RequiresApproval); + // Add totals to the resulting Order + OrderCalculations.AugmentOrderWithTotals( + responseOrderQuote, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); - // Add totals to the resulting Order - OrderCalculations.AugmentOrderWithTotals( - responseOrderQuote, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); + // If "payment" has been supplied unnecessarily, simply do not return it + if (responseOrderQuote.Payment != null && responseOrderQuote.TotalPaymentDue.Price.Value == 0) + { + responseOrderQuote.Payment = null; + } - // If "payment" has been supplied unnecessarily, simply do not return it - if (responseOrderQuote.Payment != null && responseOrderQuote.TotalPaymentDue.Price.Value == 0) - { - responseOrderQuote.Payment = null; - } + // If no OrderItems are included, do not return a lease + if (responseOrderQuote.OrderedItem.Count == 0) + { + responseOrderQuote.Lease = null; + } - // If no OrderItems are included, do not return a lease - if (responseOrderQuote.OrderedItem.Count == 0) - { - responseOrderQuote.Lease = null; - } + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await storeBookingEngineSettings.OrderStore.UpdateLease(responseOrderQuote, flowContext, stateContext, dbTransaction); + break; - break; + case Order responseOrder: + if (flowContext.Stage != FlowStage.B) + throw new OpenBookingException(new UnexpectedOrderTypeError()); - case Order responseOrder: - if (flowContext.Stage != FlowStage.B) - throw new OpenBookingException(new UnexpectedOrderTypeError()); + if (dbTransaction == null) + { + throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for booking at B, to ensure the integrity of the booking made."); + } - if (dbTransaction == null) - { - throw new InternalOpenBookingException(new InternalLibraryConfigurationError(), "A transaction is required for booking at B, to ensure the integrity of the booking made."); - } + // Create the parent Order + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction); - // Create the parent Order + // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call + // Note the happens first to ensure no conflicts with new OrderItems that have since been added + foreach (var g in orderItemGroups) + { if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await storeBookingEngineSettings.OrderStore.CreateOrder(responseOrder, flowContext, stateContext, dbTransaction); + await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); + } - // Cleanup hook to allow cleanup of the OrderItems that are no longer included in this lease, but were added by a previous call - // Note the happens first to ensure no conflicts with new OrderItems that have since been added - foreach (var g in orderItemGroups) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.CleanupOrderItems(null, g.OrderItemContexts, flowContext, stateContext, dbTransaction); - } + // Book the OrderItems + foreach (var g in orderItemGroups) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); + else + await g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); - // Book the OrderItems - foreach (var g in orderItemGroups) + foreach (var ctx in g.OrderItemContexts) { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await g.Store.BookOrderItems(g.OrderItemContexts, flowContext, stateContext, dbTransaction); - - foreach (var ctx in g.OrderItemContexts) + // Check that OrderItem Id was added + if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) { - // Check that OrderItem Id was added - if ((ctx.ResponseOrderItemId == null || ctx.ResponseOrderItem.Id == null) && !ctx.HasErrors) - { - throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in BookOrderItems for successfully booked items"); - } - - // Set the orderItemStatus to be https://openactive.io/OrderConfirmed (as it must always be so in the response of B) - ctx.ResponseOrderItem.OrderItemStatus = OrderItemStatus.OrderItemConfirmed; + throw new OpenBookingException(new InternalLibraryError(), "SetOrderItemId must be called for each OrderItemContext in BookOrderItems for successfully booked items"); } - } - - // Update this in case ResponseOrderItem was overwritten in Book - responseOrder.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); - - // Add totals to the resulting Order - OrderCalculations.AugmentOrderWithTotals( - responseOrder, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); - if (!HasOrderItemErrors(responseOrder)) - { - // Check for integrity, as if no OrderItem errors the price will be accurate - CheckOrderIntegrity(order, responseOrder); - } - else - { - // Rollback for OrderItem errors - throw new SilentRollbackException(); + // Set the orderItemStatus to be https://openactive.io/OrderConfirmed (as it must always be so in the response of B) + ctx.ResponseOrderItem.OrderItemStatus = OrderItemStatus.OrderItemConfirmed; } + } - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); - else - await storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction); + // Update this in case ResponseOrderItem was overwritten in Book + responseOrder.OrderedItem = orderItemContexts.Select(x => x.ResponseOrderItem).ToList(); - break; + // Add totals to the resulting Order + OrderCalculations.AugmentOrderWithTotals( + responseOrder, flowContext, storeBookingEngineSettings.BusinessToConsumerTaxCalculation, storeBookingEngineSettings.BusinessToBusinessTaxCalculation, storeBookingEngineSettings.PrepaymentAlwaysRequired); - default: - throw new OpenBookingException(new UnexpectedOrderTypeError()); - } + if (!HasOrderItemErrors(responseOrder)) + { + // Check for integrity, as if no OrderItem errors the price will be accurate + CheckOrderIntegrity(order, responseOrder); + } + else + { + // Rollback for OrderItem errors + throw new SilentRollbackException(); + } - if (dbTransaction != null) - { if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Commit().CheckSyncValueTaskWorked(); + storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction).CheckSyncValueTaskWorked(); else - await dbTransaction.Commit(); - } + await storeBookingEngineSettings.OrderStore.UpdateOrder(responseOrder, flowContext, stateContext, dbTransaction); + + break; + + default: + throw new OpenBookingException(new UnexpectedOrderTypeError()); } - catch + + if (dbTransaction != null) { - if (dbTransaction != null) - { - if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) - dbTransaction.Rollback().CheckSyncValueTaskWorked(); - else - await dbTransaction.Rollback(); - } - throw; + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + dbTransaction.Commit().CheckSyncValueTaskWorked(); + else + await dbTransaction.Commit(); } } + catch + { + if (dbTransaction != null) + { + if (storeBookingEngineSettings.EnforceSyncWithinOrderTransactions) + dbTransaction.Rollback().CheckSyncValueTaskWorked(); + else + await dbTransaction.Rollback(); + } + throw; + } } - catch (SilentRollbackException) - { - // Catch the SilentRollbackException so it doesn't leave the method, but do nothing with it - // At this point it will have already triggered the rollback - } - finally - { - Console.WriteLine($"## {flowContext.OrderId.uuid} | LEAVING CRITICAL SECTION {flowContext.Stage.ToString()} for {flowContext?.Customer?.Email ?? "?"}"); - } + } + catch (SilentRollbackException) + { + // Catch the SilentRollbackException so it doesn't leave the method, but do nothing with it + // At this point it will have already triggered the rollback + } return responseGenericOrder; } diff --git a/OpenActive.Server.NET/StoreBookingEngine/Stores/OpportunityStore.cs b/OpenActive.Server.NET/StoreBookingEngine/Stores/OpportunityStore.cs index da2deab5..9323ed91 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/Stores/OpportunityStore.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/Stores/OpportunityStore.cs @@ -36,7 +36,7 @@ public interface IOpportunityStore //TODO: Remove duplication between this and RpdeBase if possible as they are using the same pattern? public abstract class OpportunityStore : ModelSupport, IOpportunityStore where TComponents : class, IBookableIdComponents, new() where TDatabaseTransaction : IDatabaseTransaction where TStateContext : IStateContext { - // async methoids that are never called in transactions + // async methods that are never called in transactions void IOpportunityStore.SetConfiguration(IBookablePairIdTemplate template, SingleIdTemplate sellerTemplate) { if (template as BookablePairIdTemplate == null) From caa5816462094d62c6370f9e27f4e7af37d2dc8f Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Fri, 27 May 2022 20:28:24 +0100 Subject: [PATCH 19/33] Return a 409 status code if any OrderItem level errors exist at P --- .../CustomBookingEngine/CustomBookingEngine.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs index 2c6beefd..91b7baa2 100644 --- a/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs +++ b/OpenActive.Server.NET/CustomBookingEngine/CustomBookingEngine.cs @@ -448,7 +448,11 @@ public async Task ProcessOrderProposalCreationP(string clientId var (orderId, sellerIdComponents, seller, customerAccountIdComponents) = await ConstructIdsFromRequest(clientId, sellerId, customerAccountId, uuidString, OrderType.OrderProposal); using (await asyncDuplicateLock.LockAsync(GetParallelLockKey(orderId))) { - return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, FlowStage.P, order), order)), HttpStatusCode.Created); + var response = await ProcessFlowRequest(ValidateFlowRequest(orderId, sellerIdComponents, seller, customerAccountIdComponents, FlowStage.P, order), order); + + // Return a 409 status code if any OrderItem level errors exist + return ResponseContent.OpenBookingResponse(OpenActiveSerializer.Serialize(response), + response.OrderedItem.Exists(x => x.Error?.Count > 0) ? HttpStatusCode.Conflict : HttpStatusCode.Created); } } From 59739c304e0a3ac119bfa4390fc1334fcf81c116 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Fri, 27 May 2022 20:41:44 +0100 Subject: [PATCH 20/33] Fix orderRequiresApproval --- OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index a8af4beb..9896c701 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -685,6 +685,8 @@ public override async Task ProcessFlowRequest(BookingFlowContext // Do not continue to process the Order if there are already OrderItem level errors switch (responseGenericOrder) { + case OrderProposal _: + break; case OrderQuote responseOrderQuote: // Note OrderRequiresApproval is only required during C1 and C2 responseOrderQuote.OrderRequiresApproval = orderItemContexts.Any(x => x.RequiresApproval); From d52e834b363266b9fa680734cfec13f3f825f675 Mon Sep 17 00:00:00 2001 From: Civ Sivakumaran Date: Wed, 1 Jun 2022 11:57:54 +0100 Subject: [PATCH 21/33] feat: fix attendee details being lost between P and B --- .../Properties/launchSettings.json | 2 +- .../Stores/OrderStore.cs | 43 ++++++++++++------- .../Stores/SessionStore.cs | 12 +++++- .../FakeBookingSystem.cs | 13 +++++- .../Models/OrderItemsTable.cs | 4 ++ .../StoreBookingEngine/StoreBookingEngine.cs | 18 +++++--- 6 files changed, 65 insertions(+), 27 deletions(-) diff --git a/Examples/BookingSystem.AspNetCore/Properties/launchSettings.json b/Examples/BookingSystem.AspNetCore/Properties/launchSettings.json index 30b0586b..68e35ced 100644 --- a/Examples/BookingSystem.AspNetCore/Properties/launchSettings.json +++ b/Examples/BookingSystem.AspNetCore/Properties/launchSettings.json @@ -10,7 +10,7 @@ "launchUrl": "https://localhost:5001/openactive", "applicationUrl": "https://localhost:5001", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_ENVIRONMENT": "no-auth", "ApplicationHostBaseUrl": "https://localhost:5001" } } diff --git a/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs b/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs index db699e6a..a1314336 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs @@ -13,7 +13,7 @@ public class OrderStateContext : IStateContext { // OrderStateContext will be disposed at the end of the flow public void Dispose() - { + { } } @@ -449,21 +449,33 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp if (getOrderResult == FakeDatabaseGetOrderResult.OrderWasNotFound) throw new OpenBookingException(new UnknownOrderError()); var orderIdUri = RenderOrderId(dbOrder.OrderMode == OrderMode.Proposal ? OrderType.OrderProposal : dbOrder.OrderMode == OrderMode.Lease ? OrderType.OrderQuote : OrderType.Order, new Guid(dbOrder.OrderId)); - var orderItems = dbOrderItems.Select((orderItem) => new OrderItem + var orderItems = dbOrderItems.Select((orderItem) => { - Id = dbOrder.OrderMode != OrderMode.Lease ? RenderOrderItemId(OrderType.Order, new Guid(dbOrder.OrderId), orderItem.Id) : null, - AcceptedOffer = new Offer + var hasAttendeeDetails = orderItem.AttendeeEmail != null || orderItem.AttendeeGivenName != null + || orderItem.AttendeeFamilyName != null || orderItem.AttendeeTelephone != null; + return new OrderItem { - Id = orderItem.OfferJsonLdId, - Price = orderItem.Price - }, - OrderedItem = orderItem.OpportunityJsonLdId, - OrderItemStatus = - orderItem.Status == BookingStatus.Confirmed ? OrderItemStatus.OrderItemConfirmed : - orderItem.Status == BookingStatus.CustomerCancelled ? OrderItemStatus.CustomerCancelled : - orderItem.Status == BookingStatus.SellerCancelled ? OrderItemStatus.SellerCancelled : - orderItem.Status == BookingStatus.Attended ? OrderItemStatus.AttendeeAttended : - orderItem.Status == BookingStatus.Absent ? OrderItemStatus.AttendeeAbsent : (OrderItemStatus?)null + Id = dbOrder.OrderMode != OrderMode.Lease ? RenderOrderItemId(OrderType.Order, new Guid(dbOrder.OrderId), orderItem.Id) : null, + AcceptedOffer = new Offer + { + Id = orderItem.OfferJsonLdId, + Price = orderItem.Price + }, + OrderedItem = orderItem.OpportunityJsonLdId, + OrderItemStatus = + orderItem.Status == BookingStatus.Confirmed ? OrderItemStatus.OrderItemConfirmed : + orderItem.Status == BookingStatus.CustomerCancelled ? OrderItemStatus.CustomerCancelled : + orderItem.Status == BookingStatus.SellerCancelled ? OrderItemStatus.SellerCancelled : + orderItem.Status == BookingStatus.Attended ? OrderItemStatus.AttendeeAttended : + orderItem.Status == BookingStatus.Absent ? OrderItemStatus.AttendeeAbsent : (OrderItemStatus?)null, + Attendee = hasAttendeeDetails ? new Person + { + GivenName = orderItem.AttendeeGivenName ?? null, + FamilyName = orderItem.AttendeeFamilyName ?? null, + Email = orderItem.AttendeeEmail ?? null, + Telephone = orderItem.AttendeeTelephone ?? null, + } : null, + }; }).ToList(); var order = RenderOrderFromDatabaseResult(orderIdUri, dbOrder, orderItems); @@ -473,7 +485,8 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp Id = orderItem.Id, AcceptedOffer = orderItem.AcceptedOffer.Object.Id, OrderedItem = orderItem.OrderedItem, - OrderItemStatus = orderItem.OrderItemStatus + OrderItemStatus = orderItem.OrderItemStatus, + Attendee = orderItem.Attendee }).ToList(); order.OrderedItem = mappedOrderItems; diff --git a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs index fee3bae6..0e3ba287 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs @@ -562,7 +562,11 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.Attendee.Email).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.GivenName).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.FamilyName).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.Telephone).ToList() ); switch (result) @@ -613,7 +617,11 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.Attendee.Email).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.GivenName).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.FamilyName).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.Telephone).ToList() ); switch (result) diff --git a/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs b/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs index 1cb861c5..3bcad2ab 100644 --- a/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs +++ b/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs @@ -838,7 +838,11 @@ public class BookedOrderItemInfo Uri opportunityJsonLdId, Uri offerJsonLdId, long numberOfSpaces, - bool proposal + bool proposal, + List attendeeEmail, + List attendeeGivenName, + List attendeeFamilyName, + List attendeeTelephone ) { var db = transaction.DatabaseConnection; @@ -882,8 +886,13 @@ bool proposal BarCodeText = thisClass.AttendanceMode != AttendanceMode.Online ? Faker.Random.String(length: 10, minChar: '0', maxChar: '9') : null, MeetingUrl = thisClass.AttendanceMode != AttendanceMode.Offline ? new Uri(Faker.Internet.Url()) : null, MeetingId = thisClass.AttendanceMode != AttendanceMode.Offline ? Faker.Random.String(length: 10, minChar: '0', maxChar: '9') : null, - MeetingPassword = thisClass.AttendanceMode != AttendanceMode.Offline ? Faker.Random.String(length: 10, minChar: '0', maxChar: '9') : null + MeetingPassword = thisClass.AttendanceMode != AttendanceMode.Offline ? Faker.Random.String(length: 10, minChar: '0', maxChar: '9') : null, + AttendeeEmail = attendeeEmail[i] ?? null, + AttendeeGivenName = attendeeGivenName[i] ?? null, + AttendeeFamilyName = attendeeFamilyName[i] ?? null, + AttendeeTelephone = attendeeTelephone[i] ?? null, }; + await db.SaveAsync(orderItem); bookedOrderItemInfos.Add(new BookedOrderItemInfo { diff --git a/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs b/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs index 004c19f4..a1b4464b 100644 --- a/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs +++ b/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs @@ -30,5 +30,9 @@ public class OrderItemsTable : Table public Uri MeetingUrl { get; set; } public string MeetingId { get; set; } public string MeetingPassword { get; set; } + public string AttendeeEmail { get; set; } + public string AttendeeGivenName { get; set; } + public string AttendeeFamilyName { get; set; } + public string AttendeeTelephone { get; set; } } } \ No newline at end of file diff --git a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs index 9896c701..d6185e8b 100644 --- a/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs +++ b/OpenActive.Server.NET/StoreBookingEngine/StoreBookingEngine.cs @@ -79,8 +79,10 @@ internal OrderItemContext(int index, IBookableIdComponents idComponents, OrderIt private List Errors = null; - public bool HasErrors { - get { + public bool HasErrors + { + get + { return Errors?.Count > 0; } } @@ -231,7 +233,7 @@ public void SetResponseOrderItem(OrderItem item, SimpleIdComponents sellerId, St item.Error = Errors; item.Position = RequestOrderItem?.Position; - + ResponseOrderItem = item; } @@ -261,7 +263,7 @@ public List ValidateDetails(FlowStage flowStage) /// public class StoreBookingEngine : CustomBookingEngine { - private class SilentRollbackException : Exception {} + private class SilentRollbackException : Exception { } /// /// Simple constructor @@ -486,7 +488,7 @@ private List GetOrderItemContexts(List sourceOrder return new UnknownOrderItemContext(index, orderItem, new IncompleteOrderItemError(), "acceptedOffer @id was not provided"); } - + var idComponents = base.ResolveOpportunityID(orderedItemId, acceptedOfferId); if (idComponents == null) @@ -629,12 +631,14 @@ private async Task> GetOrderItemContextGroups(List orderItemContexts) { + public void AugmentWithOpenBookingPrepaymentConflictErrors(List orderItemContexts) + { var contextsWithOpenBookingPrepaymentRequired = orderItemContexts.Where(x => x.ResponseOrderItem?.AcceptedOffer.Object?.Price > 0 && (x.ResponseOrderItem?.AcceptedOffer.Object?.OpenBookingPrepayment == null || x.ResponseOrderItem?.AcceptedOffer.Object?.OpenBookingPrepayment == RequiredStatusType.Required)).ToList(); var contextsWithOpenBookingPrepaymentUnavailable = orderItemContexts.Where(x => x.ResponseOrderItem?.AcceptedOffer.Object?.Price > 0 && x.ResponseOrderItem?.AcceptedOffer.Object?.OpenBookingPrepayment == RequiredStatusType.Unavailable).ToList(); // Add errors to any items with conflicting openBookingPrepayment values - if (contextsWithOpenBookingPrepaymentRequired.Count > 0 && contextsWithOpenBookingPrepaymentUnavailable.Count > 0) { + if (contextsWithOpenBookingPrepaymentRequired.Count > 0 && contextsWithOpenBookingPrepaymentUnavailable.Count > 0) + { foreach (var ctx in contextsWithOpenBookingPrepaymentRequired.Concat(contextsWithOpenBookingPrepaymentUnavailable)) { ctx.AddError(new OpportunityIsInConflictError(), "A single Order cannot contain items with prepayment Unavailable, and also items with prepayment Required."); From a76f6968624f7abc1f0535683c84b1be84147d4b Mon Sep 17 00:00:00 2001 From: Civ Sivakumaran Date: Wed, 1 Jun 2022 12:32:53 +0100 Subject: [PATCH 22/33] update framework from core --- .../Stores/OrderStore.cs | 43 +- .../Stores/SessionStore.cs | 12 +- .../BookingSystem.AspNetFramework/Web.config | 766 +++++++++--------- 3 files changed, 430 insertions(+), 391 deletions(-) diff --git a/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs index db699e6a..a1314336 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs @@ -13,7 +13,7 @@ public class OrderStateContext : IStateContext { // OrderStateContext will be disposed at the end of the flow public void Dispose() - { + { } } @@ -449,21 +449,33 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp if (getOrderResult == FakeDatabaseGetOrderResult.OrderWasNotFound) throw new OpenBookingException(new UnknownOrderError()); var orderIdUri = RenderOrderId(dbOrder.OrderMode == OrderMode.Proposal ? OrderType.OrderProposal : dbOrder.OrderMode == OrderMode.Lease ? OrderType.OrderQuote : OrderType.Order, new Guid(dbOrder.OrderId)); - var orderItems = dbOrderItems.Select((orderItem) => new OrderItem + var orderItems = dbOrderItems.Select((orderItem) => { - Id = dbOrder.OrderMode != OrderMode.Lease ? RenderOrderItemId(OrderType.Order, new Guid(dbOrder.OrderId), orderItem.Id) : null, - AcceptedOffer = new Offer + var hasAttendeeDetails = orderItem.AttendeeEmail != null || orderItem.AttendeeGivenName != null + || orderItem.AttendeeFamilyName != null || orderItem.AttendeeTelephone != null; + return new OrderItem { - Id = orderItem.OfferJsonLdId, - Price = orderItem.Price - }, - OrderedItem = orderItem.OpportunityJsonLdId, - OrderItemStatus = - orderItem.Status == BookingStatus.Confirmed ? OrderItemStatus.OrderItemConfirmed : - orderItem.Status == BookingStatus.CustomerCancelled ? OrderItemStatus.CustomerCancelled : - orderItem.Status == BookingStatus.SellerCancelled ? OrderItemStatus.SellerCancelled : - orderItem.Status == BookingStatus.Attended ? OrderItemStatus.AttendeeAttended : - orderItem.Status == BookingStatus.Absent ? OrderItemStatus.AttendeeAbsent : (OrderItemStatus?)null + Id = dbOrder.OrderMode != OrderMode.Lease ? RenderOrderItemId(OrderType.Order, new Guid(dbOrder.OrderId), orderItem.Id) : null, + AcceptedOffer = new Offer + { + Id = orderItem.OfferJsonLdId, + Price = orderItem.Price + }, + OrderedItem = orderItem.OpportunityJsonLdId, + OrderItemStatus = + orderItem.Status == BookingStatus.Confirmed ? OrderItemStatus.OrderItemConfirmed : + orderItem.Status == BookingStatus.CustomerCancelled ? OrderItemStatus.CustomerCancelled : + orderItem.Status == BookingStatus.SellerCancelled ? OrderItemStatus.SellerCancelled : + orderItem.Status == BookingStatus.Attended ? OrderItemStatus.AttendeeAttended : + orderItem.Status == BookingStatus.Absent ? OrderItemStatus.AttendeeAbsent : (OrderItemStatus?)null, + Attendee = hasAttendeeDetails ? new Person + { + GivenName = orderItem.AttendeeGivenName ?? null, + FamilyName = orderItem.AttendeeFamilyName ?? null, + Email = orderItem.AttendeeEmail ?? null, + Telephone = orderItem.AttendeeTelephone ?? null, + } : null, + }; }).ToList(); var order = RenderOrderFromDatabaseResult(orderIdUri, dbOrder, orderItems); @@ -473,7 +485,8 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp Id = orderItem.Id, AcceptedOffer = orderItem.AcceptedOffer.Object.Id, OrderedItem = orderItem.OrderedItem, - OrderItemStatus = orderItem.OrderItemStatus + OrderItemStatus = orderItem.OrderItemStatus, + Attendee = orderItem.Attendee }).ToList(); order.OrderedItem = mappedOrderItems; diff --git a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs index fee3bae6..0e3ba287 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs @@ -562,7 +562,11 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.Attendee.Email).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.GivenName).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.FamilyName).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.Telephone).ToList() ); switch (result) @@ -613,7 +617,11 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.Attendee.Email).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.GivenName).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.FamilyName).ToList(), + ctxGroup.Select(x => x.RequestOrderItem.Attendee.Telephone).ToList() ); switch (result) diff --git a/Examples/BookingSystem.AspNetFramework/Web.config b/Examples/BookingSystem.AspNetFramework/Web.config index 9f65f8e6..b55cc1da 100644 --- a/Examples/BookingSystem.AspNetFramework/Web.config +++ b/Examples/BookingSystem.AspNetFramework/Web.config @@ -1,380 +1,398 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From b65b3e7f58b9fea4bb071dda21bf155a90bd257b Mon Sep 17 00:00:00 2001 From: Civ Sivakumaran Date: Wed, 1 Jun 2022 16:29:49 +0100 Subject: [PATCH 23/33] fix 500 errors and pass object to fake db --- .../Stores/FacilityStore.cs | 24 +++++++++++++-- .../Stores/OrderStore.cs | 8 ++--- .../Stores/SessionStore.cs | 28 ++++++++++++----- .../Stores/FacilityStore.cs | 24 +++++++++++++-- .../Stores/OrderStore.cs | 8 ++--- .../Stores/SessionStore.cs | 28 ++++++++++++----- .../FakeBookingSystem.cs | 30 ++++++++++++------- 7 files changed, 112 insertions(+), 38 deletions(-) diff --git a/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs b/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs index 070e7e6b..defd8a99 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs @@ -510,7 +510,17 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.Attendee != null) + .Select(x => new FakeDbPerson + { + GivenName = x.RequestOrderItem.Attendee.GivenName, + FamilyName = x.RequestOrderItem.Attendee.FamilyName, + Email = x.RequestOrderItem.Attendee.Email, + Telephone = x.RequestOrderItem.Attendee.Telephone, + }) + .ToList() ); switch (result) @@ -561,7 +571,17 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.Attendee != null) + .Select(x => new FakeDbPerson + { + GivenName = x.RequestOrderItem.Attendee.GivenName, + FamilyName = x.RequestOrderItem.Attendee.FamilyName, + Email = x.RequestOrderItem.Attendee.Email, + Telephone = x.RequestOrderItem.Attendee.Telephone, + }) + .ToList() ); switch (result) diff --git a/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs b/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs index a1314336..6b5cc902 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs @@ -470,10 +470,10 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp orderItem.Status == BookingStatus.Absent ? OrderItemStatus.AttendeeAbsent : (OrderItemStatus?)null, Attendee = hasAttendeeDetails ? new Person { - GivenName = orderItem.AttendeeGivenName ?? null, - FamilyName = orderItem.AttendeeFamilyName ?? null, - Email = orderItem.AttendeeEmail ?? null, - Telephone = orderItem.AttendeeTelephone ?? null, + GivenName = orderItem.AttendeeGivenName, + FamilyName = orderItem.AttendeeFamilyName, + Email = orderItem.AttendeeEmail, + Telephone = orderItem.AttendeeTelephone, } : null, }; }).ToList(); diff --git a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs index 0e3ba287..4b0a1bec 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs @@ -563,10 +563,16 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.Attendee.Email).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.GivenName).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.FamilyName).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.Telephone).ToList() + ctxGroup + .Where(x => x.RequestOrderItem.Attendee != null) + .Select(x => new FakeDbPerson + { + GivenName = x.RequestOrderItem.Attendee.GivenName, + FamilyName = x.RequestOrderItem.Attendee.FamilyName, + Email = x.RequestOrderItem.Attendee.Email, + Telephone = x.RequestOrderItem.Attendee.Telephone, + }) + .ToList() ); switch (result) @@ -618,10 +624,16 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.Attendee.Email).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.GivenName).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.FamilyName).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.Telephone).ToList() + ctxGroup + .Where(x => x.RequestOrderItem.Attendee != null) + .Select(x => new FakeDbPerson + { + GivenName = x.RequestOrderItem.Attendee.GivenName, + FamilyName = x.RequestOrderItem.Attendee.FamilyName, + Email = x.RequestOrderItem.Attendee.Email, + Telephone = x.RequestOrderItem.Attendee.Telephone, + }) + .ToList() ); switch (result) diff --git a/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs index 070e7e6b..defd8a99 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs @@ -510,7 +510,17 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.Attendee != null) + .Select(x => new FakeDbPerson + { + GivenName = x.RequestOrderItem.Attendee.GivenName, + FamilyName = x.RequestOrderItem.Attendee.FamilyName, + Email = x.RequestOrderItem.Attendee.Email, + Telephone = x.RequestOrderItem.Attendee.Telephone, + }) + .ToList() ); switch (result) @@ -561,7 +571,17 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.Attendee != null) + .Select(x => new FakeDbPerson + { + GivenName = x.RequestOrderItem.Attendee.GivenName, + FamilyName = x.RequestOrderItem.Attendee.FamilyName, + Email = x.RequestOrderItem.Attendee.Email, + Telephone = x.RequestOrderItem.Attendee.Telephone, + }) + .ToList() ); switch (result) diff --git a/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs index a1314336..6b5cc902 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs @@ -470,10 +470,10 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp orderItem.Status == BookingStatus.Absent ? OrderItemStatus.AttendeeAbsent : (OrderItemStatus?)null, Attendee = hasAttendeeDetails ? new Person { - GivenName = orderItem.AttendeeGivenName ?? null, - FamilyName = orderItem.AttendeeFamilyName ?? null, - Email = orderItem.AttendeeEmail ?? null, - Telephone = orderItem.AttendeeTelephone ?? null, + GivenName = orderItem.AttendeeGivenName, + FamilyName = orderItem.AttendeeFamilyName, + Email = orderItem.AttendeeEmail, + Telephone = orderItem.AttendeeTelephone, } : null, }; }).ToList(); diff --git a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs index 0e3ba287..4b0a1bec 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs @@ -563,10 +563,16 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.Attendee.Email).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.GivenName).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.FamilyName).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.Telephone).ToList() + ctxGroup + .Where(x => x.RequestOrderItem.Attendee != null) + .Select(x => new FakeDbPerson + { + GivenName = x.RequestOrderItem.Attendee.GivenName, + FamilyName = x.RequestOrderItem.Attendee.FamilyName, + Email = x.RequestOrderItem.Attendee.Email, + Telephone = x.RequestOrderItem.Attendee.Telephone, + }) + .ToList() ); switch (result) @@ -618,10 +624,16 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.Attendee.Email).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.GivenName).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.FamilyName).ToList(), - ctxGroup.Select(x => x.RequestOrderItem.Attendee.Telephone).ToList() + ctxGroup + .Where(x => x.RequestOrderItem.Attendee != null) + .Select(x => new FakeDbPerson + { + GivenName = x.RequestOrderItem.Attendee.GivenName, + FamilyName = x.RequestOrderItem.Attendee.FamilyName, + Email = x.RequestOrderItem.Attendee.Email, + Telephone = x.RequestOrderItem.Attendee.Telephone, + }) + .ToList() ); switch (result) diff --git a/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs b/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs index 3bcad2ab..cb049373 100644 --- a/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs +++ b/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs @@ -161,6 +161,14 @@ public class BookingPartnerAdministratorTable public List Claims { get; set; } } + public class FakeDbPerson + { + public string GivenName { get; set; } + public string FamilyName { get; set; } + public string Email { get; set; } + public string Telephone { get; set; } + } + public class FakeDatabase { private const float ProportionWithRequiresAttendeeValidation = 1f / 10; @@ -839,10 +847,7 @@ public class BookedOrderItemInfo Uri offerJsonLdId, long numberOfSpaces, bool proposal, - List attendeeEmail, - List attendeeGivenName, - List attendeeFamilyName, - List attendeeTelephone + List attendees ) { var db = transaction.DatabaseConnection; @@ -887,10 +892,10 @@ List attendeeTelephone MeetingUrl = thisClass.AttendanceMode != AttendanceMode.Offline ? new Uri(Faker.Internet.Url()) : null, MeetingId = thisClass.AttendanceMode != AttendanceMode.Offline ? Faker.Random.String(length: 10, minChar: '0', maxChar: '9') : null, MeetingPassword = thisClass.AttendanceMode != AttendanceMode.Offline ? Faker.Random.String(length: 10, minChar: '0', maxChar: '9') : null, - AttendeeEmail = attendeeEmail[i] ?? null, - AttendeeGivenName = attendeeGivenName[i] ?? null, - AttendeeFamilyName = attendeeFamilyName[i] ?? null, - AttendeeTelephone = attendeeTelephone[i] ?? null, + AttendeeEmail = attendees.Count > 0 ? attendees[i].Email : null, + AttendeeGivenName = attendees.Count > 0 ? attendees[i].GivenName : null, + AttendeeFamilyName = attendees.Count > 0 ? attendees[i].FamilyName : null, + AttendeeTelephone = attendees.Count > 0 ? attendees[i].Telephone : null, }; await db.SaveAsync(orderItem); @@ -920,7 +925,8 @@ List attendeeTelephone Uri opportunityJsonLdId, Uri offerJsonLdId, long numberOfSpaces, - bool proposal + bool proposal, + List attendees ) { var db = transaction.DatabaseConnection; @@ -961,7 +967,11 @@ bool proposal Price = thisSlot.Price.Value, PinCode = Faker.Random.String(6, minChar: '0', maxChar: '9'), ImageUrl = Faker.Image.PlaceholderUrl(width: 25, height: 25), - BarCodeText = Faker.Random.String(length: 10, minChar: '0', maxChar: '9') + BarCodeText = Faker.Random.String(length: 10, minChar: '0', maxChar: '9'), + AttendeeEmail = attendees.Count > 0 ? attendees[i].Email : null, + AttendeeGivenName = attendees.Count > 0 ? attendees[i].GivenName : null, + AttendeeFamilyName = attendees.Count > 0 ? attendees[i].FamilyName : null, + AttendeeTelephone = attendees.Count > 0 ? attendees[i].Telephone : null, }; await db.SaveAsync(orderItem); From c24ea697f01e7dd209fdfa13f33b39de60cb6b6c Mon Sep 17 00:00:00 2001 From: Civ Sivakumaran Date: Wed, 1 Jun 2022 18:07:30 +0100 Subject: [PATCH 24/33] feat: add additional details support --- Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs | 8 ++++++++ Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs | 4 +++- Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs | 8 ++++++++ Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs | 8 ++++++-- .../OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs | 1 + 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs b/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs index defd8a99..c2fe9806 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs @@ -520,6 +520,10 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.OrderItemIntakeFormResponse != null) + .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); @@ -581,6 +585,10 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.OrderItemIntakeFormResponse != null) + .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); diff --git a/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs b/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs index 6b5cc902..5db2ddeb 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs @@ -475,6 +475,7 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp Email = orderItem.AttendeeEmail, Telephone = orderItem.AttendeeTelephone, } : null, + OrderItemIntakeFormResponse = orderItem.AdditionalDetailsString != null ? OpenActiveSerializer.DeserializeList(orderItem.AdditionalDetailsString) : null, }; }).ToList(); var order = RenderOrderFromDatabaseResult(orderIdUri, dbOrder, orderItems); @@ -486,7 +487,8 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp AcceptedOffer = orderItem.AcceptedOffer.Object.Id, OrderedItem = orderItem.OrderedItem, OrderItemStatus = orderItem.OrderItemStatus, - Attendee = orderItem.Attendee + Attendee = orderItem.Attendee, + OrderItemIntakeFormResponse = orderItem.OrderItemIntakeFormResponse, }).ToList(); order.OrderedItem = mappedOrderItems; diff --git a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs index 4b0a1bec..5ff0cd7a 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs @@ -572,6 +572,10 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.OrderItemIntakeFormResponse != null) + .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); @@ -633,6 +637,10 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.OrderItemIntakeFormResponse != null) + .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); diff --git a/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs b/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs index cb049373..25357c00 100644 --- a/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs +++ b/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs @@ -847,7 +847,8 @@ public class BookedOrderItemInfo Uri offerJsonLdId, long numberOfSpaces, bool proposal, - List attendees + List attendees, + List additionalDetailsString ) { var db = transaction.DatabaseConnection; @@ -896,6 +897,7 @@ List attendees AttendeeGivenName = attendees.Count > 0 ? attendees[i].GivenName : null, AttendeeFamilyName = attendees.Count > 0 ? attendees[i].FamilyName : null, AttendeeTelephone = attendees.Count > 0 ? attendees[i].Telephone : null, + AdditionalDetailsString = additionalDetailsString.Count > 0 ? additionalDetailsString[i] : null, }; await db.SaveAsync(orderItem); @@ -926,7 +928,8 @@ List attendees Uri offerJsonLdId, long numberOfSpaces, bool proposal, - List attendees + List attendees, + List additionalDetailsString ) { var db = transaction.DatabaseConnection; @@ -972,6 +975,7 @@ List attendees AttendeeGivenName = attendees.Count > 0 ? attendees[i].GivenName : null, AttendeeFamilyName = attendees.Count > 0 ? attendees[i].FamilyName : null, AttendeeTelephone = attendees.Count > 0 ? attendees[i].Telephone : null, + AdditionalDetailsString = additionalDetailsString.Count > 0 ? additionalDetailsString[i] : null, }; await db.SaveAsync(orderItem); diff --git a/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs b/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs index a1b4464b..51b9f062 100644 --- a/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs +++ b/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs @@ -34,5 +34,6 @@ public class OrderItemsTable : Table public string AttendeeGivenName { get; set; } public string AttendeeFamilyName { get; set; } public string AttendeeTelephone { get; set; } + public string AdditionalDetailsString { get; set; } } } \ No newline at end of file From 38a5a5fb98663c035d4755b0732caa44546fa38f Mon Sep 17 00:00:00 2001 From: Civ Sivakumaran Date: Wed, 1 Jun 2022 18:09:10 +0100 Subject: [PATCH 25/33] undo launchSettings --- .../BookingSystem.AspNetCore/Properties/launchSettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/BookingSystem.AspNetCore/Properties/launchSettings.json b/Examples/BookingSystem.AspNetCore/Properties/launchSettings.json index 68e35ced..30b0586b 100644 --- a/Examples/BookingSystem.AspNetCore/Properties/launchSettings.json +++ b/Examples/BookingSystem.AspNetCore/Properties/launchSettings.json @@ -10,7 +10,7 @@ "launchUrl": "https://localhost:5001/openactive", "applicationUrl": "https://localhost:5001", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "no-auth", + "ASPNETCORE_ENVIRONMENT": "Development", "ApplicationHostBaseUrl": "https://localhost:5001" } } From b9e5dc6d60d912af71bf80a00648f2acec43cfe9 Mon Sep 17 00:00:00 2001 From: Civ Sivakumaran Date: Wed, 1 Jun 2022 18:34:46 +0100 Subject: [PATCH 26/33] update framework from core --- .../BookingSystem.AspNetFramework/Stores/FacilityStore.cs | 8 ++++++++ .../BookingSystem.AspNetFramework/Stores/OrderStore.cs | 4 +++- .../BookingSystem.AspNetFramework/Stores/SessionStore.cs | 8 ++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs index defd8a99..c2fe9806 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs @@ -520,6 +520,10 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.OrderItemIntakeFormResponse != null) + .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); @@ -581,6 +585,10 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.OrderItemIntakeFormResponse != null) + .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); diff --git a/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs index 6b5cc902..5db2ddeb 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs @@ -475,6 +475,7 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp Email = orderItem.AttendeeEmail, Telephone = orderItem.AttendeeTelephone, } : null, + OrderItemIntakeFormResponse = orderItem.AdditionalDetailsString != null ? OpenActiveSerializer.DeserializeList(orderItem.AdditionalDetailsString) : null, }; }).ToList(); var order = RenderOrderFromDatabaseResult(orderIdUri, dbOrder, orderItems); @@ -486,7 +487,8 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp AcceptedOffer = orderItem.AcceptedOffer.Object.Id, OrderedItem = orderItem.OrderedItem, OrderItemStatus = orderItem.OrderItemStatus, - Attendee = orderItem.Attendee + Attendee = orderItem.Attendee, + OrderItemIntakeFormResponse = orderItem.OrderItemIntakeFormResponse, }).ToList(); order.OrderedItem = mappedOrderItems; diff --git a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs index 4b0a1bec..5ff0cd7a 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs @@ -572,6 +572,10 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.OrderItemIntakeFormResponse != null) + .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); @@ -633,6 +637,10 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.OrderItemIntakeFormResponse != null) + .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); From 29a241fc62ee9d4c22375fbb548372bed6ad8235 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Wed, 1 Jun 2022 22:52:01 +0100 Subject: [PATCH 27/33] Fix framework build to windows-2019 --- .github/workflows/openactive-test-suite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/openactive-test-suite.yml b/.github/workflows/openactive-test-suite.yml index c291f27b..a312f7c1 100644 --- a/.github/workflows/openactive-test-suite.yml +++ b/.github/workflows/openactive-test-suite.yml @@ -83,7 +83,7 @@ jobs: connection_string: ${{ secrets.CONFORMANCE_CERTIFICATE_BLOB_STORAGE_CONNECTION_STRING }} sync: false framework: - runs-on: windows-latest + runs-on: windows-2019 strategy: fail-fast: false matrix: From fec47ac61963b524613d2b80219ab3050d524f89 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:28:43 +0100 Subject: [PATCH 28/33] Revert Examples/BookingSystem.AspNetFramework/Web.config --- .../BookingSystem.AspNetFramework/Web.config | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Examples/BookingSystem.AspNetFramework/Web.config b/Examples/BookingSystem.AspNetFramework/Web.config index b55cc1da..f746011d 100644 --- a/Examples/BookingSystem.AspNetFramework/Web.config +++ b/Examples/BookingSystem.AspNetFramework/Web.config @@ -30,24 +30,6 @@ - - - - - - - - - - - - - - - - - - From ed99636aecdd15a3f5d86f8565559d4a10557ef5 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:57:09 +0100 Subject: [PATCH 29/33] Fix comment --- Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs b/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs index c2fe9806..6b2af314 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs @@ -286,7 +286,7 @@ protected override async Task TriggerTestAction(OpenBookingSimulateAction simula // Similar to the RPDE logic, this needs to render and return an new hypothetical OrderItem from the database based on the supplied opportunity IDs protected override async Task GetOrderItems(List> orderItemContexts, StoreBookingFlowContext flowContext, OrderStateContext stateContext) { - // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SimpleIdComponents (this is not required if using a Single Seller) + // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SellerId (this is not required if using a Single Seller) // Additionally this method must check that there are enough spaces in each entry From ec66ae15dad62220a075220ad1a47e48cb36b9aa Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:58:20 +0100 Subject: [PATCH 30/33] Fix comment --- Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs index 5ff0cd7a..50fe0a28 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs @@ -306,7 +306,7 @@ protected override async Task TriggerTestAction(OpenBookingSimulateAction simula // Similar to the RPDE logic, this needs to render and return an new hypothetical OrderItem from the database based on the supplied opportunity IDs protected override async Task GetOrderItems(List> orderItemContexts, StoreBookingFlowContext flowContext, OrderStateContext stateContext) { - // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SimpleIdComponents (this is not required if using a Single Seller) + // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SellerId (this is not required if using a Single Seller) // Additionally this method must check that there are enough spaces in each entry From 3904b1267e6bcd74c09d5ba64a8357776e1a9f62 Mon Sep 17 00:00:00 2001 From: Nick Evans <2616208+nickevansuk@users.noreply.github.com> Date: Mon, 6 Jun 2022 12:02:58 +0100 Subject: [PATCH 31/33] Fix comments --- Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs index 5ff0cd7a..50fe0a28 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs @@ -306,7 +306,7 @@ protected override async Task TriggerTestAction(OpenBookingSimulateAction simula // Similar to the RPDE logic, this needs to render and return an new hypothetical OrderItem from the database based on the supplied opportunity IDs protected override async Task GetOrderItems(List> orderItemContexts, StoreBookingFlowContext flowContext, OrderStateContext stateContext) { - // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SimpleIdComponents (this is not required if using a Single Seller) + // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SellerId (this is not required if using a Single Seller) // Additionally this method must check that there are enough spaces in each entry From ca9e7f0f33d88e1a42a3a241d9144e7c77a0009c Mon Sep 17 00:00:00 2001 From: Civ Sivakumaran Date: Mon, 6 Jun 2022 12:47:25 +0100 Subject: [PATCH 32/33] review changes --- .../Stores/FacilityStore.cs | 36 +++++++++---------- .../Stores/OrderStore.cs | 10 +----- .../Stores/SessionStore.cs | 36 +++++++++---------- .../Stores/FacilityStore.cs | 36 +++++++++---------- .../Stores/SessionStore.cs | 36 +++++++++---------- .../FakeBookingSystem.cs | 26 ++++---------- .../Models/OrderItemsTable.cs | 5 +-- 7 files changed, 72 insertions(+), 113 deletions(-) diff --git a/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs b/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs index c2fe9806..779905e4 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/FacilityStore.cs @@ -512,18 +512,16 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.Attendee != null) - .Select(x => new FakeDbPerson - { - GivenName = x.RequestOrderItem.Attendee.GivenName, - FamilyName = x.RequestOrderItem.Attendee.FamilyName, - Email = x.RequestOrderItem.Attendee.Email, - Telephone = x.RequestOrderItem.Attendee.Telephone, - }) + .Select(x => + x.RequestOrderItem.Attendee == null + ? null + : OpenActiveSerializer.Serialize(x.RequestOrderItem.Attendee)) .ToList(), ctxGroup - .Where(x => x.RequestOrderItem.OrderItemIntakeFormResponse != null) - .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) + .Select(x => + x.RequestOrderItem.OrderItemIntakeFormResponse == null + ? null + : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); @@ -577,18 +575,16 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.Attendee != null) - .Select(x => new FakeDbPerson - { - GivenName = x.RequestOrderItem.Attendee.GivenName, - FamilyName = x.RequestOrderItem.Attendee.FamilyName, - Email = x.RequestOrderItem.Attendee.Email, - Telephone = x.RequestOrderItem.Attendee.Telephone, - }) + .Select(x => + x.RequestOrderItem.Attendee == null + ? null + : OpenActiveSerializer.Serialize(x.RequestOrderItem.Attendee)) .ToList(), ctxGroup - .Where(x => x.RequestOrderItem.OrderItemIntakeFormResponse != null) - .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) + .Select(x => + x.RequestOrderItem.OrderItemIntakeFormResponse == null + ? null + : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); diff --git a/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs b/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs index 5db2ddeb..838c1ded 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/OrderStore.cs @@ -451,8 +451,6 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp var orderIdUri = RenderOrderId(dbOrder.OrderMode == OrderMode.Proposal ? OrderType.OrderProposal : dbOrder.OrderMode == OrderMode.Lease ? OrderType.OrderQuote : OrderType.Order, new Guid(dbOrder.OrderId)); var orderItems = dbOrderItems.Select((orderItem) => { - var hasAttendeeDetails = orderItem.AttendeeEmail != null || orderItem.AttendeeGivenName != null - || orderItem.AttendeeFamilyName != null || orderItem.AttendeeTelephone != null; return new OrderItem { Id = dbOrder.OrderMode != OrderMode.Lease ? RenderOrderItemId(OrderType.Order, new Guid(dbOrder.OrderId), orderItem.Id) : null, @@ -468,13 +466,7 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp orderItem.Status == BookingStatus.SellerCancelled ? OrderItemStatus.SellerCancelled : orderItem.Status == BookingStatus.Attended ? OrderItemStatus.AttendeeAttended : orderItem.Status == BookingStatus.Absent ? OrderItemStatus.AttendeeAbsent : (OrderItemStatus?)null, - Attendee = hasAttendeeDetails ? new Person - { - GivenName = orderItem.AttendeeGivenName, - FamilyName = orderItem.AttendeeFamilyName, - Email = orderItem.AttendeeEmail, - Telephone = orderItem.AttendeeTelephone, - } : null, + Attendee = orderItem.AttendeeString != null ? OpenActiveSerializer.Deserialize(orderItem.AttendeeString) : null, OrderItemIntakeFormResponse = orderItem.AdditionalDetailsString != null ? OpenActiveSerializer.DeserializeList(orderItem.AdditionalDetailsString) : null, }; }).ToList(); diff --git a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs index 5ff0cd7a..7f250b35 100644 --- a/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetCore/Stores/SessionStore.cs @@ -564,18 +564,16 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.Attendee != null) - .Select(x => new FakeDbPerson - { - GivenName = x.RequestOrderItem.Attendee.GivenName, - FamilyName = x.RequestOrderItem.Attendee.FamilyName, - Email = x.RequestOrderItem.Attendee.Email, - Telephone = x.RequestOrderItem.Attendee.Telephone, - }) + .Select(x => + x.RequestOrderItem.Attendee == null + ? null + : OpenActiveSerializer.Serialize(x.RequestOrderItem.Attendee)) .ToList(), ctxGroup - .Where(x => x.RequestOrderItem.OrderItemIntakeFormResponse != null) - .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) + .Select(x => + x.RequestOrderItem.OrderItemIntakeFormResponse == null + ? null + : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); @@ -629,18 +627,16 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.Attendee != null) - .Select(x => new FakeDbPerson - { - GivenName = x.RequestOrderItem.Attendee.GivenName, - FamilyName = x.RequestOrderItem.Attendee.FamilyName, - Email = x.RequestOrderItem.Attendee.Email, - Telephone = x.RequestOrderItem.Attendee.Telephone, - }) + .Select(x => + x.RequestOrderItem.Attendee == null + ? null + : OpenActiveSerializer.Serialize(x.RequestOrderItem.Attendee)) .ToList(), ctxGroup - .Where(x => x.RequestOrderItem.OrderItemIntakeFormResponse != null) - .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) + .Select(x => + x.RequestOrderItem.OrderItemIntakeFormResponse == null + ? null + : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); diff --git a/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs index c2fe9806..a6227f0a 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs @@ -512,18 +512,16 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.Attendee != null) - .Select(x => new FakeDbPerson - { - GivenName = x.RequestOrderItem.Attendee.GivenName, - FamilyName = x.RequestOrderItem.Attendee.FamilyName, - Email = x.RequestOrderItem.Attendee.Email, - Telephone = x.RequestOrderItem.Attendee.Telephone, - }) + .Select(x => + x.RequestOrderItem.Attendee == null + ? null + : OpenActiveSerializer.Serialize(x.RequestOrderItem.Attendee)) .ToList(), ctxGroup - .Where(x => x.RequestOrderItem.OrderItemIntakeFormResponse != null) - .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) + .Select(x => + x.RequestOrderItem.OrderItemIntakeFormResponse != null + ? null + : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); @@ -577,18 +575,16 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.Attendee != null) - .Select(x => new FakeDbPerson - { - GivenName = x.RequestOrderItem.Attendee.GivenName, - FamilyName = x.RequestOrderItem.Attendee.FamilyName, - Email = x.RequestOrderItem.Attendee.Email, - Telephone = x.RequestOrderItem.Attendee.Telephone, - }) + .Select(x => + x.RequestOrderItem.Attendee == null + ? null + : OpenActiveSerializer.Serialize(x.RequestOrderItem.Attendee)) .ToList(), ctxGroup - .Where(x => x.RequestOrderItem.OrderItemIntakeFormResponse != null) - .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) + .Select(x => + x.RequestOrderItem.OrderItemIntakeFormResponse != null + ? null + : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); diff --git a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs index 5ff0cd7a..1c3fd3ad 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs @@ -564,18 +564,16 @@ protected override async ValueTask BookOrderItems(List x.RequestOrderItem.Attendee != null) - .Select(x => new FakeDbPerson - { - GivenName = x.RequestOrderItem.Attendee.GivenName, - FamilyName = x.RequestOrderItem.Attendee.FamilyName, - Email = x.RequestOrderItem.Attendee.Email, - Telephone = x.RequestOrderItem.Attendee.Telephone, - }) + .Select(x => + x.RequestOrderItem.Attendee == null + ? null + : OpenActiveSerializer.Serialize(x.RequestOrderItem.Attendee)) .ToList(), ctxGroup - .Where(x => x.RequestOrderItem.OrderItemIntakeFormResponse != null) - .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) + .Select(x => + x.RequestOrderItem.OrderItemIntakeFormResponse != null + ? null + : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); @@ -629,18 +627,16 @@ protected override async ValueTask ProposeOrderItems(List x.RequestOrderItem.Attendee != null) - .Select(x => new FakeDbPerson - { - GivenName = x.RequestOrderItem.Attendee.GivenName, - FamilyName = x.RequestOrderItem.Attendee.FamilyName, - Email = x.RequestOrderItem.Attendee.Email, - Telephone = x.RequestOrderItem.Attendee.Telephone, - }) + .Select(x => + x.RequestOrderItem.Attendee == null + ? null + : OpenActiveSerializer.Serialize(x.RequestOrderItem.Attendee)) .ToList(), ctxGroup - .Where(x => x.RequestOrderItem.OrderItemIntakeFormResponse != null) - .Select(x => OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) + .Select(x => + x.RequestOrderItem.OrderItemIntakeFormResponse != null + ? null + : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() ); diff --git a/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs b/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs index 25357c00..120a2482 100644 --- a/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs +++ b/Fakes/OpenActive.FakeDatabase.NET/FakeBookingSystem.cs @@ -161,14 +161,6 @@ public class BookingPartnerAdministratorTable public List Claims { get; set; } } - public class FakeDbPerson - { - public string GivenName { get; set; } - public string FamilyName { get; set; } - public string Email { get; set; } - public string Telephone { get; set; } - } - public class FakeDatabase { private const float ProportionWithRequiresAttendeeValidation = 1f / 10; @@ -847,7 +839,7 @@ public class BookedOrderItemInfo Uri offerJsonLdId, long numberOfSpaces, bool proposal, - List attendees, + List attendees, List additionalDetailsString ) { @@ -893,11 +885,8 @@ List additionalDetailsString MeetingUrl = thisClass.AttendanceMode != AttendanceMode.Offline ? new Uri(Faker.Internet.Url()) : null, MeetingId = thisClass.AttendanceMode != AttendanceMode.Offline ? Faker.Random.String(length: 10, minChar: '0', maxChar: '9') : null, MeetingPassword = thisClass.AttendanceMode != AttendanceMode.Offline ? Faker.Random.String(length: 10, minChar: '0', maxChar: '9') : null, - AttendeeEmail = attendees.Count > 0 ? attendees[i].Email : null, - AttendeeGivenName = attendees.Count > 0 ? attendees[i].GivenName : null, - AttendeeFamilyName = attendees.Count > 0 ? attendees[i].FamilyName : null, - AttendeeTelephone = attendees.Count > 0 ? attendees[i].Telephone : null, - AdditionalDetailsString = additionalDetailsString.Count > 0 ? additionalDetailsString[i] : null, + AttendeeString = attendees.Count > i ? attendees[i] : null, + AdditionalDetailsString = additionalDetailsString.Count > i ? additionalDetailsString[i] : null, }; await db.SaveAsync(orderItem); @@ -928,7 +917,7 @@ List additionalDetailsString Uri offerJsonLdId, long numberOfSpaces, bool proposal, - List attendees, + List attendees, List additionalDetailsString ) { @@ -971,11 +960,8 @@ List additionalDetailsString PinCode = Faker.Random.String(6, minChar: '0', maxChar: '9'), ImageUrl = Faker.Image.PlaceholderUrl(width: 25, height: 25), BarCodeText = Faker.Random.String(length: 10, minChar: '0', maxChar: '9'), - AttendeeEmail = attendees.Count > 0 ? attendees[i].Email : null, - AttendeeGivenName = attendees.Count > 0 ? attendees[i].GivenName : null, - AttendeeFamilyName = attendees.Count > 0 ? attendees[i].FamilyName : null, - AttendeeTelephone = attendees.Count > 0 ? attendees[i].Telephone : null, - AdditionalDetailsString = additionalDetailsString.Count > 0 ? additionalDetailsString[i] : null, + AttendeeString = attendees.Count > i ? attendees[i] : null, + AdditionalDetailsString = additionalDetailsString.Count > i ? additionalDetailsString[i] : null, }; await db.SaveAsync(orderItem); diff --git a/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs b/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs index 51b9f062..b6a72aa9 100644 --- a/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs +++ b/Fakes/OpenActive.FakeDatabase.NET/Models/OrderItemsTable.cs @@ -30,10 +30,7 @@ public class OrderItemsTable : Table public Uri MeetingUrl { get; set; } public string MeetingId { get; set; } public string MeetingPassword { get; set; } - public string AttendeeEmail { get; set; } - public string AttendeeGivenName { get; set; } - public string AttendeeFamilyName { get; set; } - public string AttendeeTelephone { get; set; } + public string AttendeeString { get; set; } public string AdditionalDetailsString { get; set; } } } \ No newline at end of file From f4b15b6953bbc1b0a73d38916a98bdde1b186c09 Mon Sep 17 00:00:00 2001 From: Civ Sivakumaran Date: Mon, 6 Jun 2022 12:54:15 +0100 Subject: [PATCH 33/33] add core -> framework changes --- .../Stores/FacilityStore.cs | 6 +++--- .../BookingSystem.AspNetFramework/Stores/OrderStore.cs | 10 +--------- .../Stores/SessionStore.cs | 4 ++-- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs index a6227f0a..236bcc4c 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/FacilityStore.cs @@ -286,7 +286,7 @@ protected override async Task TriggerTestAction(OpenBookingSimulateAction simula // Similar to the RPDE logic, this needs to render and return an new hypothetical OrderItem from the database based on the supplied opportunity IDs protected override async Task GetOrderItems(List> orderItemContexts, StoreBookingFlowContext flowContext, OrderStateContext stateContext) { - // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SimpleIdComponents (this is not required if using a Single Seller) + // Note the implementation of this method must also check that this OrderItem is from the Seller specified by context.SellerId (this is not required if using a Single Seller) // Additionally this method must check that there are enough spaces in each entry @@ -519,7 +519,7 @@ protected override async ValueTask BookOrderItems(List - x.RequestOrderItem.OrderItemIntakeFormResponse != null + x.RequestOrderItem.OrderItemIntakeFormResponse == null ? null : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() @@ -582,7 +582,7 @@ protected override async ValueTask ProposeOrderItems(List - x.RequestOrderItem.OrderItemIntakeFormResponse != null + x.RequestOrderItem.OrderItemIntakeFormResponse == null ? null : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() diff --git a/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs index 5db2ddeb..838c1ded 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/OrderStore.cs @@ -451,8 +451,6 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp var orderIdUri = RenderOrderId(dbOrder.OrderMode == OrderMode.Proposal ? OrderType.OrderProposal : dbOrder.OrderMode == OrderMode.Lease ? OrderType.OrderQuote : OrderType.Order, new Guid(dbOrder.OrderId)); var orderItems = dbOrderItems.Select((orderItem) => { - var hasAttendeeDetails = orderItem.AttendeeEmail != null || orderItem.AttendeeGivenName != null - || orderItem.AttendeeFamilyName != null || orderItem.AttendeeTelephone != null; return new OrderItem { Id = dbOrder.OrderMode != OrderMode.Lease ? RenderOrderItemId(OrderType.Order, new Guid(dbOrder.OrderId), orderItem.Id) : null, @@ -468,13 +466,7 @@ public override async Task GetOrderStatus(OrderIdComponents orderId, Simp orderItem.Status == BookingStatus.SellerCancelled ? OrderItemStatus.SellerCancelled : orderItem.Status == BookingStatus.Attended ? OrderItemStatus.AttendeeAttended : orderItem.Status == BookingStatus.Absent ? OrderItemStatus.AttendeeAbsent : (OrderItemStatus?)null, - Attendee = hasAttendeeDetails ? new Person - { - GivenName = orderItem.AttendeeGivenName, - FamilyName = orderItem.AttendeeFamilyName, - Email = orderItem.AttendeeEmail, - Telephone = orderItem.AttendeeTelephone, - } : null, + Attendee = orderItem.AttendeeString != null ? OpenActiveSerializer.Deserialize(orderItem.AttendeeString) : null, OrderItemIntakeFormResponse = orderItem.AdditionalDetailsString != null ? OpenActiveSerializer.DeserializeList(orderItem.AdditionalDetailsString) : null, }; }).ToList(); diff --git a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs index 5610bfd2..b3dc7358 100644 --- a/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs +++ b/Examples/BookingSystem.AspNetFramework/Stores/SessionStore.cs @@ -571,7 +571,7 @@ protected override async ValueTask BookOrderItems(List - x.RequestOrderItem.OrderItemIntakeFormResponse != null + x.RequestOrderItem.OrderItemIntakeFormResponse == null ? null : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList() @@ -634,7 +634,7 @@ protected override async ValueTask ProposeOrderItems(List - x.RequestOrderItem.OrderItemIntakeFormResponse != null + x.RequestOrderItem.OrderItemIntakeFormResponse == null ? null : OpenActiveSerializer.SerializeList(x.RequestOrderItem.OrderItemIntakeFormResponse)) .ToList()