/// <summary> /// Changes the state of the Working Order to reflect a filledly filled state. /// </summary> private static void OnFilledAction(Object[] key, params Object[] parameters) { // A middle tier context is also required for a transacted update. DataModelTransaction dataModelTransaction = DataModelTransaction.Current; // It is possible that the Working Order that is the object of this status update operation may have been deleted since the action was // created. This is not an error condition. If there is no Working Order to update, then the operation is just terminated prematurely. WorkingOrderRow workingOrderRow = DataModel.WorkingOrder.WorkingOrderKey.Find(key); if (workingOrderRow == null) { return; } workingOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(workingOrderRow); if (workingOrderRow.RowState == DataRowState.Detached) { return; } // The error status on a Working Order cannot be cleared with a fill. if (workingOrderRow.StatusId != StatusMap.FromCode(Status.Error)) { // The Working Order is 'Filled' when the quantity executed is the same as the quantity ordered. Decimal quantityOrdered = WorkingOrder.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); Decimal quantityExecuted = WorkingOrder.GetExecutionQuantity(dataModelTransaction, workingOrderRow); if (quantityOrdered == quantityExecuted) { UpdateWorkingOrderStatus(workingOrderRow, Status.Filled); } } }
/// <summary> /// Changes the state of the Working Order to reflect a filledly filled state. /// </summary> private static void ClearErrorAction(Object[] key, params Object[] parameters) { // A middle tier context is also required for a transacted update. DataModelTransaction dataModelTransaction = DataModelTransaction.Current; // It is possible that the Working Order that is the object of this status update operation may have been deleted since the action was // created. This is not an error condition. If there is no Working Order to update, then the operation is just terminated prematurely. WorkingOrderRow workingOrderRow = DataModel.WorkingOrder.WorkingOrderKey.Find(key); if (workingOrderRow == null) { return; } workingOrderRow.AcquireReaderLock(dataModelTransaction.TransactionId, DataModel.LockTimeout); dataModelTransaction.AddLock(workingOrderRow); if (workingOrderRow.RowState == DataRowState.Detached) { return; } // The 'Error' status is only cleared when all the Destination Orders are valid. Boolean isErrorStatus = false; foreach (DestinationOrderRow siblingOrderRow in workingOrderRow.GetDestinationOrderRows()) { siblingOrderRow.AcquireReaderLock(dataModelTransaction); if (siblingOrderRow.StatusId == StatusMap.FromCode(Status.Error)) { isErrorStatus = true; break; } } // The proper Working Order status must be evaluated when the error status is cleared. if (!isErrorStatus) { // The aggregates will determine the new state of the Working Order. Decimal quantityExecuted = WorkingOrder.GetExecutionQuantity(dataModelTransaction, workingOrderRow); Decimal quantityOrdered = WorkingOrder.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); // This restores the 'New' status when the canceled Destination Order was the only order with any fills. if (quantityExecuted == 0.0M && workingOrderRow.StatusId != StatusMap.FromCode(Status.New)) { UpdateWorkingOrderStatus(workingOrderRow, Status.New); } // This restores the 'Partially Filled' status when other executions remain. if (0.0M < quantityExecuted && quantityExecuted < quantityOrdered && workingOrderRow.StatusId != StatusMap.FromCode(Status.PartiallyFilled)) { UpdateWorkingOrderStatus(workingOrderRow, Status.PartiallyFilled); } // This restores the 'Filled' status when the quantity executed is the same as the quantity ordered. if (quantityExecuted == quantityOrdered && workingOrderRow.StatusId != StatusMap.FromCode(Status.Filled)) { UpdateWorkingOrderStatus(workingOrderRow, Status.Filled); } } }
/// <summary> /// Handler for validating Source Order records. /// </summary> /// <param name="sender">The object that originated the event.</param> /// <param name="e">The event arguments.</param> internal static void OnSourceOrderRowValidate(object sender, SourceOrderRowChangeEventArgs e) { Decimal currentQuantity; Decimal destinationOrderQuantity; DataModelTransaction dataModelTransaction; Decimal originalQuantity; Decimal sourceOrderQuantity; WorkingOrderRow workingOrderRow; // The Business Rules will be enforced on this Source Order. Note that it is locked at the point this handler is called. SourceOrderRow sourceOrderRow = e.Row; // The action on the row determines which rule to evaluate. switch (e.Action) { case DataRowAction.Change: // Reject the operation if the Working Order is overcommitted with a destination. Note that the order of the evaluation is important for // efficiency. That is, there is no need to sum up the Source and Destination Orders, which requires locking several records, if the Source // Order quantity has been incremented as there's no chance it will be over committed. originalQuantity = (Decimal)sourceOrderRow[DataModel.SourceOrder.OrderedQuantityColumn, DataRowVersion.Original]; currentQuantity = (Decimal)sourceOrderRow[DataModel.SourceOrder.OrderedQuantityColumn, DataRowVersion.Current]; if (originalQuantity < currentQuantity) { // A middle tier context is required to lock the rows so the quantities can be aggregated. dataModelTransaction = DataModelTransaction.Current; // Once its determined that the quantity has increased, the Working Order row will need to be locked to examine the aggregate values of the // source and Destination Orders. workingOrderRow = e.Row.WorkingOrderRow; workingOrderRow.AcquireReaderLock(dataModelTransaction); // This is an illegal operation if the quantity sent to a destination is greater than the quantity ordered. sourceOrderQuantity = WorkingOrder.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); destinationOrderQuantity = WorkingOrder.GetDestinationOrderQuantity(dataModelTransaction, workingOrderRow); if (sourceOrderQuantity < destinationOrderQuantity) { throw new FaultException <DestinationQuantityFault>( new DestinationQuantityFault(workingOrderRow.WorkingOrderId, sourceOrderQuantity, destinationOrderQuantity)); } } break; case DataRowAction.Delete: // A middle tier context is required to lock the rows so the quantities can be aggregated. dataModelTransaction = DataModelTransaction.Current; // The main idea of this trigger is to prevent the over commitment of a Working Order when a Source Order is deleted. At this point, the // Source Order is deleted, so the original record must be used to find the parent Working Order that is effected. sourceOrderRow = e.Row; try { DataModel.DataLock.EnterReadLock(); workingOrderRow = ((WorkingOrderRow)(sourceOrderRow.GetParentRow(DataModel.SourceOrder.WorkingOrderSourceOrderRelation, DataRowVersion.Original))); } finally { DataModel.DataLock.ExitReadLock(); } // Lock the working order row while some aggregate values are calculated. workingOrderRow.AcquireReaderLock(dataModelTransaction); // Reject the operation if the Working Order is overcommitted with a destination. sourceOrderQuantity = WorkingOrder.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); destinationOrderQuantity = WorkingOrder.GetDestinationOrderQuantity(dataModelTransaction, workingOrderRow); if (sourceOrderQuantity < destinationOrderQuantity) { throw new FaultException <DestinationQuantityFault>( new DestinationQuantityFault(workingOrderRow.WorkingOrderId, sourceOrderQuantity, destinationOrderQuantity)); } break; } }
/// <summary> /// Handler for validating Destination Order records. /// </summary> /// <param name="sender">The object that originated the event.</param> /// <param name="e">The event arguments.</param> internal static void OnDestinationOrderRowValidate(object sender, DestinationOrderRowChangeEventArgs e) { Int32 currentStatusCode; Decimal destinationOrderQuantity; DataModelTransaction dataModelTransaction; Int32 previousStatusCode; Decimal sourceOrderQuantity; WorkingOrderRow workingOrderRow; // The Business Rules will be enforced on this Destination Order. Note that it is locked at the point this handler is called. DestinationOrderRow destinationOrderRow = e.Row; // The action on the row determines which rule to evaluate. switch (destinationOrderRow.RowState) { case DataRowState.Added: // This rule will reject the operation if the Working Order is overcommitted with a destination. dataModelTransaction = DataModelTransaction.Current; // This rule will throw an exception if the quantity sent to a destination is greater than the quantity ordered. The quantity ordered and // quantity sent can only be calcuated from the owning Working Order which must be locked in order to carry out the calculations. workingOrderRow = e.Row.WorkingOrderRow; workingOrderRow.AcquireReaderLock(dataModelTransaction); sourceOrderQuantity = WorkingOrder.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); destinationOrderQuantity = WorkingOrder.GetDestinationOrderQuantity(dataModelTransaction, workingOrderRow); if (sourceOrderQuantity < destinationOrderQuantity) { throw new FaultException <DestinationQuantityFault>( new DestinationQuantityFault(workingOrderRow.WorkingOrderId, sourceOrderQuantity, destinationOrderQuantity)); } break; case DataRowState.Modified: // Reject the operation if the Working Order is overcommitted with a destination. Note that the order of the evaluation is important for // efficiency. There is no need to sum up the source and Destination Orders if the quantity has been reduced as there's no chance it will be // over committed. Decimal originalQuantity = (Decimal)destinationOrderRow[DataModel.DestinationOrder.OrderedQuantityColumn, DataRowVersion.Original]; Decimal currentQuantity = (Decimal)destinationOrderRow[DataModel.DestinationOrder.OrderedQuantityColumn, DataRowVersion.Current]; if (originalQuantity < currentQuantity) { // Once it's determined that the order can be overcommitted, a middle tier context is required to lock the rows so the quantities can be // aggregated. dataModelTransaction = DataModelTransaction.Current; // This rule will throw an exception if the quantity sent to a destination is greater than the quantity ordered. The quantity ordered and // quantity sent can only be calcuated from the owning Working Order which must be locked in order to carry out the calculations. workingOrderRow = e.Row.WorkingOrderRow; workingOrderRow.AcquireReaderLock(dataModelTransaction); sourceOrderQuantity = WorkingOrder.GetSourceOrderQuantity(dataModelTransaction, workingOrderRow); destinationOrderQuantity = WorkingOrder.GetDestinationOrderQuantity(dataModelTransaction, workingOrderRow); if (sourceOrderQuantity < destinationOrderQuantity) { throw new FaultException <DestinationQuantityFault>( new DestinationQuantityFault(workingOrderRow.WorkingOrderId, sourceOrderQuantity, destinationOrderQuantity)); } } // This rule will examine the effects of a state change of the Destination Order. The stateChangeMatrix contains delegates to methods that // will determine what, if any, stats change should be applied to the Working Order due to the change in state of this Destination Order. previousStatusCode = Convert.ToInt32(StatusMap.FromId((Guid)destinationOrderRow[DataModel.DestinationOrder.StatusIdColumn, DataRowVersion.Original])); currentStatusCode = Convert.ToInt32(StatusMap.FromId((Guid)destinationOrderRow[DataModel.DestinationOrder.StatusIdColumn, DataRowVersion.Current])); if (previousStatusCode != currentStatusCode) { DestinationOrder.statusChangeMatrix[previousStatusCode, currentStatusCode](new Object[] { destinationOrderRow.WorkingOrderId }); } break; case DataRowState.Deleted: // This rule will examine the effects of a state change of the Destination Order. The stateChangeMatrix contains delegates to methods that // will determine what, if any, stats change should be applied to the Working Order due to the change in state of this Destination Order. previousStatusCode = Convert.ToInt32(StatusMap.FromId((Guid)destinationOrderRow[DataModel.DestinationOrder.StatusIdColumn, DataRowVersion.Original])); currentStatusCode = (Int32)Status.Deleted; Guid workingOrderId = (Guid)destinationOrderRow[DataModel.DestinationOrder.WorkingOrderIdColumn, DataRowVersion.Original]; DestinationOrder.statusChangeMatrix[previousStatusCode, currentStatusCode](new Object[] { workingOrderId }); break; } }