public override async Task <CreateCouponsArgument> Run( CreateCouponsArgument arg, CommercePipelineExecutionContext context) { await this._getManagedListCommand.PerformTransaction(context.CommerceContext, async() => { var policy = context.CommerceContext.GetPolicy <LoyaltyPointsPolicy>(); LoyaltyPointsEntity loyaltyPointsEntity = await _findEntityCommand.Process(context.CommerceContext, typeof(LoyaltyPointsEntity), Constants.EntityId, shouldCreate: true) as LoyaltyPointsEntity; if (loyaltyPointsEntity == null) { await context.AbortWithError("Unable to access or create LoyaltyPointsEntity {0}", "LoyaltyPointsEntityNotReturned", Constants.EntityId); return; } // Prevent simultaneous updates, in case multiple minion instances. Since these might be on scaled servers, mutex lock uses database field. // Lock is read before count is checked, so that when first process finishes and releases row, counts will be replenished. // Notes: // 1. If this pipeline aborts, the lock should be rolled back. // 2. Assuming transactions are enabled, the second process should never see IsLocked=true, as the read won't return until the lock is released. However, // this syntax should work on a non-transactional environment, and makes the intent of the code clearer. if (loyaltyPointsEntity.IsLocked) { await context.AbortWithError("{0} is locked. If this condition persists, unset this value through the database.", "EntityLocked", Constants.EntityId); return; } var list = await EnsureList(context, Constants.AvailableCouponsList); if (list == null) { await context.AbortWithError("Unable to create list {0}", "UnableToCreateList", Constants.AvailableCouponsList); return; } long count = await _getListCountCommand.Process(context.CommerceContext, Constants.AvailableCouponsList); context.Logger.LogDebug($"{this.Name}: List {Constants.AvailableCouponsList} has {count} items."); if (count <= policy.ReprovisionTriggerCount) { loyaltyPointsEntity.IsLocked = true; await _persistEntityCommand.Process(context.CommerceContext, loyaltyPointsEntity); context.Logger.LogDebug($"{this.Name}: List {Constants.AvailableCouponsList} is at or under reprovision count of {policy.ReprovisionTriggerCount}."); loyaltyPointsEntity.SequenceNumber++; string suffix = loyaltyPointsEntity.SequenceNumber.ToString(); string promotionName = string.Format(Constants.GeneratedPromotion, suffix); Promotion promotion = await GeneratePromotion(context, promotionName); if (promotion == null) { await context.AbortWithError("Unable to generate promotion {0}.", "PromotionNotFound", promotionName); return; } await AddCoupons(context, promotion, suffix); if (context.IsNullOrHasErrors()) { return; } await AllocateCoupons(context, promotion, suffix); if (context.IsNullOrHasErrors()) { return; } ApprovePromotion(context, promotion); await CopyCouponsToList(context, loyaltyPointsEntity, list); if (context.IsNullOrHasErrors()) { return; } loyaltyPointsEntity.IsLocked = false; await _persistEntityCommand.Process(context.CommerceContext, list); await _persistEntityCommand.Process(context.CommerceContext, promotion); await _persistEntityCommand.Process(context.CommerceContext, loyaltyPointsEntity); } }); return(arg); }
private async Task CopyCouponsToList(CommercePipelineExecutionContext context, LoyaltyPointsEntity entity, ManagedList targetList) { var policy = context.GetPolicy <LoyaltyPointsPolicy>(); string sourceListName = $"promotion-{policy.CouponPrefix}-{entity.SequenceNumber}-allocatedcoupons"; var coupons = await _getEntitiesInListCommand.Process(context.CommerceContext, sourceListName, 0, policy.CouponBlockSize); await _addListEntitiesPipeline.Run(new ListEntitiesArgument(coupons, targetList.Name), context); }