Querying data vs using command model through Saga

I have a question about some of the best practices, benefits and drawbacks of the following method I tried to used. Let me try to explain the use case.

We are importing bank transactions overnight that happened last day. Each transaction statement is processed one by one and is being matched to the existing customer through reference number which must be present in the statement. Reference number is unique per customer.

As payments are made by customers to pay monthly invoices for hire purchase contracts. First credit the money to customers local balance account. From customers balance account we need to allocate to 1 or multiple active hire purchases for that customer to cover their monthly invoice. In my Saga I get hold of customerId when processing transaction. Next we need to query active hire purchase payments for that customer and start allocation calculations.

Note the last saga event handler in the example, I am incrementing the counter for processed hire purchases and only execute the CalculateAllocationsInternalCommand when the counter matches to processed count.

My rationale with approach was, that each CollectPaymentsForAllocationCommand gets the most accurate result through event sourced aggregate.

My question is, should I be using query model to fetch customers active hire purchases with all their principal and interest balances, OR is the below approach fine to initially just query list if id-s and pass commands to other aggregates which send data back through events to Saga. In my example code if customer has 3 active hire purchases, then we need to dispatch 3 commands for other aggregate to send events back before Saga can continue processing.

Are there any unseen pitfalls I cannot imagine to myself with this approach? Maybe about concurrency or anything Saga specific? Or would I be just fine actually just querying all the needed data and start allocation calculation right away and get rid of 3 of these event handlers to reduce complexity.

@StartSaga
@SagaEventHandler(associationProperty = "transactionId")
fun on(event: TransactionCreatedEvent) {
  val referenceNumber = event.transactionModel.referenceNumber

  if (referenceNumber != null) {
    customerQueryGateway.findCustomerByReferenceNumber(referenceNumber)
      .map { commandGateway.send<Any>(MarkTransactionMatchedForCustomerInternalCommand(event.transactionId, it)) }
  }
}

@SagaEventHandler(associationProperty = "transactionId")
fun on(event: TransactionMatchedForCustomer) {
  associateWith("customerId", event.customerId.id.toString())
  
  commandGateway.send<Any>(TransferCreditToCustomerBalanceCommand(event.customerId, event.transactionId, event.creditAmount))
}

@SagaEventHandler(associationProperty = "customerId")
fun on(event: TransactionCreditedToCustomerEvent) {
  // here we queried only id-s instead of full models
  val hirePurchasesEligibleForAllocation = hirePurchaseQueryGateway.findHirePurchasesEligibleForAllocationByCustomer(event.customerId)

  val allocationId = AllocationId()
  this.allocationId = allocationId
  associateWith("allocationId", allocationId.id.toString())

  commandGateway.send<Any>(CreateAllocationInternalCommand(allocationId, event.customerId, hirePurchasesEligibleForAllocation))
}

@SagaEventHandler(associationProperty = "allocationId")
fun on(event: AllocationStartedEvent) {
  hirePurchaseIds = event.hirePurchaseIds

  commandGateway.send<Any>(CollectCustomerTransactionBalanceCommand(event.customerId))
}

@SagaEventHandler(associationProperty = "customerId")
fun on(event: CustomerTransactionBalanceCollectedEvent) {
  if (event.amount.isZero) {
    SagaLifecycle.end()
    return
  }

  customerTransactionBalanceAmount = event.amount

  hirePurchaseIds.forEach {
    associateWith("hirePurchaseId", it.id.toString())
    commandGateway.send<Any>(CollectPaymentsForAllocationCommand(it))
  }
}

@EndSaga
@SagaEventHandler(associationProperty = "hirePurchaseId")
fun on(event: PaymentsCollectedForAllocationEvent) {
  numberOfPaymentsCollected++

  commandGateway.sendAndWait<Any>(
    AddPaymentAmountsToAllocationInternalCommand(
      allocationId,
      event.hirePurchaseId,
      event.principalBalanceAmount,
      event.allocatablePrincipalAmount,
      event.allocatableInterestAmount
    )
  )

  // only calculate once all customers hire purchase payment data is collected through event sourced aggregates. Is this approach okay or is it easier to just query all the data at once and just process it?
  if (numberOfPaymentsCollected == hirePurchaseIds.size) {
    commandGateway.send<Any>(CalculateAllocationsInternalCommand(allocationId, customerTransactionBalanceAmount))
  }
}

Thanks