예제 #1
0
        public override void LoadCache(Action <string, object> act,
                                       params object[] args)
        {
            var db = new BillingwareDataContext();

            var statsBase = db.Transactions.GroupBy(a => a.AccountNumber);

            //sum and count of all debits and credits
            act($"sum.debit", db.Transactions.Where(t => t.TransactionType == TransactionType.Debit).Select(s => s.Amount).DefaultIfEmpty(0).Sum());
            act($"sum.credit", db.Transactions.Where(t => t.TransactionType == TransactionType.Credit).Select(s => s.Amount).DefaultIfEmpty(0).Sum());
            act($"count", db.Transactions.LongCount());
            act($"count.credit", db.Transactions.Where(t => t.TransactionType == TransactionType.Credit).LongCount());
            act($"count.debit", db.Transactions.Where(t => t.TransactionType == TransactionType.Debit).LongCount());


            //sum and count of individual debit and credits
            foreach (var item in statsBase)
            {
                act($"count.{item.Key}", item.LongCount());
                act($"count.debit.{item.Key}", item.LongCount(t => t.TransactionType == TransactionType.Debit));
                act($"count.credit.{item.Key}", item.LongCount(t => t.TransactionType == TransactionType.Credit));

                act($"sum.debit.{item.Key}", item.Where(t => t.TransactionType == TransactionType.Debit).Select(s => s.Amount).DefaultIfEmpty(0).Sum());
                act($"sum.credit.{item.Key}", item.Where(t => t.TransactionType == TransactionType.Credit).Select(s => s.Amount).DefaultIfEmpty(0).Sum());
            }
        }
예제 #2
0
        private void DoPersistTransaction(PersistTransaction req)
        {
            using (var db = new BillingwareDataContext())
            {
                var t = new Transaction
                {
                    AccountNumber       = req.AccountNumber,
                    Amount              = req.Amount,
                    TransactionType     = req.TransactionType,
                    Reference           = req.Reference,
                    BalanceAfter        = req.BalanceAfter,
                    BalanceBefore       = req.BalanceBefore,
                    ClientId            = req.ClientId,
                    ConditionFailReason = req.ConditionFailReason,
                    CreatedAt           = req.CreatedAt,
                    Honoured            = req.Honoured,
                    Narration           = req.Narration,
                    SatisfiedCondition  = req.SatisfiedCondition,
                    Ticket              = req.Ticket
                };

                //quick fix
                if (t.TransactionType == TransactionType.Debit)
                {
                    //if balance = 100, amount=10 =>  after=90 and before=100
                    var sumBefore = t.BalanceAfter + t.Amount;

                    if (t.BalanceBefore != sumBefore)
                    {
                        t.BalanceBefore = t.BalanceAfter + (t.Amount);
                    }
                }
                else
                {
                    //if balance = 90, amount=10 =>  after=100 and before=90
                    var sumBefore = t.BalanceAfter - t.Amount;

                    if (t.BalanceBefore != sumBefore)
                    {
                        t.BalanceBefore = t.BalanceAfter - (t.Amount);
                    }
                }

                db.Transactions.Add(t);
                db.SaveChanges();
            }

            //increase cache items
            var typeKey = req.TransactionType == TransactionType.Credit ? "credit" : "debit";

            _tranactionsStatsCache.Increase($"count", int.Parse("1"));
            _tranactionsStatsCache.Increase($"count.{typeKey}", int.Parse("1"));
            _tranactionsStatsCache.Increase($"count.{typeKey}.{req.AccountNumber}", int.Parse("1"));

            _tranactionsStatsCache.Increase($"sum.{typeKey}", req.Amount);
            _tranactionsStatsCache.Increase($"sum.{typeKey}.{req.AccountNumber}", req.Amount);
        }
예제 #3
0
        private void DoCreditAccountEvent(CreditAccount detail)
        {
            using (var db = new BillingwareDataContext())
            {
                var account = db.Accounts.FirstOrDefault(a => a.AccountNumber == detail.Request.AccountNumber);

                if (account == null)
                {
                    return;
                }

                account.Balance += detail.Request.Amount;

                db.Entry(account).State = EntityState.Modified;
                db.SaveChanges();
            }
        }
예제 #4
0
        private void DoCreateAccount(CreateAccount req)
        {
            var db = new BillingwareDataContext();

            db.Accounts.Add(new Account
            {
                AccountNumber = req.AccountNumber,
                Balance       = decimal.Zero,
                Extra         = req.Extra,
                Alias         = req.Alias,
                CreatedAt     = DateTime.Now
            });

            db.SaveChanges();


            Sender.Tell(new AccountCreated(new CommonStatusResponse(message: "Successful")), Self);
        }
예제 #5
0
        private void DoEditAccount(EditAccount req)
        {
            var db = new BillingwareDataContext();

            var account = db.Accounts.FirstOrDefault(a => a.AccountNumber == req.AccountNumber);

            if (account == null)
            {
                Sender.Tell(new AccountEdited(new CommonStatusResponse(message: "Not found", code: "404", subCode: "404.1")), Self);
                return;
            }

            account.Alias = req.Alias;
            account.Extra = req.Extra;

            db.Entry(account).State = EntityState.Modified;
            db.SaveChanges();

            Sender.Tell(new AccountEdited(new CommonStatusResponse(message: "Successful")), Self);
        }
예제 #6
0
        private void DoRequestAccountDebit(RequestAccountDebit request)
        {
            Log(CommonLogLevel.Debug, $"received request to debit {request.AccountNumber} with {request.Amount}", null,
                null);


            //we generate a unique ticket for every transaction
            var ticket = Guid.NewGuid().ToString("N");

            /*
             * 1. Find the account
             * 2. Execute conditions on account - we shall use Expression Trees
             * 3. Apply outcome
             * 4. Persist results
             */
            var db = new BillingwareDataContext();

            var account = db.Accounts.AsNoTracking().FirstOrDefault(a => a.AccountNumber == request.AccountNumber);

            if (account == null)
            {
                Sender.Tell(
                    new AccountDebitResponse(request.Reference, request.AccountNumber, ticket, request.Amount, decimal.Zero,
                                             decimal.Zero, new CommonStatusResponse(message: $"account {request.AccountNumber} not found.", code: "404", subCode: "404.1")), Self);
                return;
            }

            var generalConditions =
                db.Conditions.AsNoTracking().Where(c => c.Active && c.ConditionApplicatorType == ConditionApplicatorType.All).ToList()
            ;

            var specificConditions = db.Conditions.AsNoTracking().Where(c =>
                                                                        c.Active && c.ConditionApplicatorType == ConditionApplicatorType.One &&
                                                                        c.AppliedToAccountNumbers.Contains(request.AccountNumber)).ToList();



            var allTransactionsCount   = long.Parse(_tranactionsStatsCache.Find($"count.{request.AccountNumber}").Result.ToString());
            var debitTransactionSum    = decimal.Parse(_tranactionsStatsCache.Find($"sum.debit.{request.AccountNumber}").Result.ToString());
            var creditTransactionSum   = decimal.Parse(_tranactionsStatsCache.Find($"sum.credit.{request.AccountNumber}").Result.ToString());
            var debitTransactionCount  = long.Parse(_tranactionsStatsCache.Find($"count.debit.{request.AccountNumber}").Result.ToString());
            var creditTransactionCount = long.Parse(_tranactionsStatsCache.Find($"count.credit.{request.AccountNumber}").Result.ToString());

            // var allConditions = new List<Condition>();

            bool passedSpecificConditions = false;

            if (specificConditions.Any())
            {
                //allConditions.AddRange(specificConditions);

                var perAccountOutcomeList = ConditionEvaluatorHelper.EvaluateConditionAndGetOutcomeIds(account, specificConditions, new ClientPayloadData("debit", request.Reference, request.Amount),
                                                                                                       creditTransactionSum, creditTransactionCount, debitTransactionSum, debitTransactionCount,
                                                                                                       allTransactionsCount);


                if (perAccountOutcomeList.Any())
                {
                    //check if there's any "halt" type in the outcome list...then,
                    //raise an event to actually inspect the outcome of the condition evaluation
                    //it would also increment the stats cache values
                    var outcomes = db.Outcomes.AsNoTracking().Where(o => perAccountOutcomeList.Contains(o.Id) && o.Active)
                                   .OrderBy(i => i.Id);

                    if (!outcomes.Any())
                    {
                        Sender.Tell(
                            new AccountDebitResponse(request.Reference, request.AccountNumber, ticket, request.Amount, account.Balance, account.Balance
                                                     , new CommonStatusResponse(message: $"condition was satisfied but no active outcome could be applied.", code: "500", subCode: "500.1")), Self);
                        return;
                    }


                    if (outcomes.Any(o => o.OutcomeType == OutcomeType.Halt))
                    {
                        Sender.Tell(
                            new AccountDebitResponse(request.Reference, request.AccountNumber, ticket, request.Amount, account.Balance,
                                                     account.Balance, new CommonStatusResponse(message: $"condition was satisfied. but at least one outcome HALTs the process", code: "403", subCode: "403.1")), Self);
                        return;
                    }

                    passedSpecificConditions = true;
                    //Publish(new DebitAccount(request));

                    //Publish(new PersistTransaction(request.AccountNumber, DateTime.Now, request.Narration, request.Amount,
                    //    account.Balance, account.Balance - request.Amount, TransactionType.Debit, true, string.Empty, true,
                    //    request.ClientId, request.Reference, ticket));

                    //Sender.Tell(
                    //    new AccountDebitResponse(request.Reference, request.AccountNumber, ticket, request.Amount, account.Balance,
                    //        account.Balance - request.Amount, new CommonStatusResponse(message: "Successful")), Self);
                    //return;
                }
            }

            if (generalConditions.Any())
            {
                // allConditions.AddRange(generalConditions);

                var generalOutcomeList = ConditionEvaluatorHelper.EvaluateConditionAndGetOutcomeIds(account, generalConditions, new ClientPayloadData("debit", request.Reference, request.Amount),
                                                                                                    creditTransactionSum, creditTransactionCount, debitTransactionSum, debitTransactionCount,
                                                                                                    allTransactionsCount);


                if (generalOutcomeList.Any())
                {
                    //check if there's any "halt" type in the outcome list...then,
                    //raise an event to actually inspect the outcome of the condition evaluation
                    //it would also increment the stats cache values
                    var outcomes = db.Outcomes.AsNoTracking().Where(o => generalOutcomeList.Contains(o.Id) && o.Active)
                                   .OrderBy(i => i.Id);

                    if (!outcomes.Any())
                    {
                        //but.....
                        if (passedSpecificConditions)
                        {
                            Publish(new DebitAccount(request));

                            Publish(new PersistTransaction(request.AccountNumber, DateTime.Now, request.Narration, request.Amount,
                                                           account.Balance, account.Balance - request.Amount, TransactionType.Debit, true, string.Empty, true,
                                                           request.ClientId, request.Reference, ticket));

                            Sender.Tell(
                                new AccountDebitResponse(request.Reference, request.AccountNumber, ticket, request.Amount, account.Balance,
                                                         account.Balance - request.Amount, new CommonStatusResponse(message: "Successful")), Self);
                            return;
                        }
                        Sender.Tell(
                            new AccountDebitResponse(request.Reference, request.AccountNumber, ticket, request.Amount, account.Balance,
                                                     account.Balance, new CommonStatusResponse(message: $"condition was satisfied but no active outcome could be applied.", code: "500", subCode: "500.1")), Self);
                        return;
                    }


                    if (outcomes.Any(o => o.OutcomeType == OutcomeType.Halt))
                    {
                        //but.....
                        if (passedSpecificConditions)
                        {
                            Publish(new DebitAccount(request));

                            Publish(new PersistTransaction(request.AccountNumber, DateTime.Now, request.Narration, request.Amount,
                                                           account.Balance, account.Balance - request.Amount, TransactionType.Debit, true, string.Empty, true,
                                                           request.ClientId, request.Reference, ticket));

                            Sender.Tell(
                                new AccountDebitResponse(request.Reference, request.AccountNumber, ticket, request.Amount, account.Balance,
                                                         account.Balance - request.Amount, new CommonStatusResponse(message: "Successful")), Self);
                            return;
                        }
                        Sender.Tell(
                            new AccountDebitResponse(request.Reference, request.AccountNumber, ticket, request.Amount, account.Balance,
                                                     account.Balance, new CommonStatusResponse(message: $"condition was satisfied. but at least one outcome HALTs the process", code: "403", subCode: "403.1")), Self);
                        return;
                    }

                    Publish(new DebitAccount(request));

                    Publish(new PersistTransaction(request.AccountNumber, DateTime.Now, request.Narration, request.Amount,
                                                   account.Balance, account.Balance - request.Amount, TransactionType.Debit, true, string.Empty, true,
                                                   request.ClientId, request.Reference, ticket));

                    Sender.Tell(
                        new AccountDebitResponse(request.Reference, request.AccountNumber, ticket, request.Amount, account.Balance,
                                                 account.Balance - request.Amount, new CommonStatusResponse(message: "Successful")), Self);
                    return;
                }
            }


            //if there are no conditions so we still proceed with the intended action
            Publish(new DebitAccount(request));

            Publish(new PersistTransaction(request.AccountNumber, DateTime.Now, request.Narration, request.Amount,
                                           account.Balance, account.Balance - request.Amount, TransactionType.Debit, false, "no condition", true,
                                           request.ClientId, request.Reference, ticket));

            Sender.Tell(
                new AccountDebitResponse(request.Reference, request.AccountNumber, ticket, request.Amount, account.Balance,
                                         account.Balance - request.Amount, new CommonStatusResponse(message: "Successful")), Self);
            return;
        }
예제 #7
0
        public void CreateSampleCondition()
        {
            //arrange

            //create condition for the following case:
            //when the request is "debit" and the account balance <=0, halt the process
            var whenRequestIsDebit = new Condition
            {
                Active = true,
                ConditionApplicatorType = ConditionApplicatorType.All,
                ConditionConnector      = ConditionConnector.And,
                Key           = ComparatorKey.Custom,
                KeyExpression = "$.Payload.TransactionType",
                Type          = ComparatorType.EqualTo,
                Name          = "halt_no_balance",
                Value         = "debit",
                OutcomeId     = 1 //same outcome
            };
            var whenAccountHasNoMoney = new Condition
            {
                Active = true,
                ConditionApplicatorType = ConditionApplicatorType.All,
                ConditionConnector      = ConditionConnector.None,
                Key       = ComparatorKey.Balance,
                Type      = ComparatorType.LessThanOrEqualTo,
                Name      = "halt_no_balance",
                Value     = "0",
                OutcomeId = 1 //same outcome
            };

            //when account has "allowOverdraft=true" in their Extras

            var whenAccountHasAllowOverdraftFlag = new Condition
            {
                Active = true,
                ConditionApplicatorType = ConditionApplicatorType.One,
                AppliedToAccountNumbers = "9237452718263673",
                ConditionConnector      = ConditionConnector.None,
                Key           = ComparatorKey.Custom,
                KeyExpression = "$.Extra.allowOverdraft",
                Type          = ComparatorType.EqualTo,
                Name          = "halt_no_balance",
                Value         = "true",
                OutcomeId     = 1 //same outcome
            };

            var outcome = new ConditionOutcome
            {
                Active      = true,
                OutcomeType = OutcomeType.Halt
            };



            //act
            var db = new BillingwareDataContext();

            db.Conditions.Add(whenRequestIsDebit);
            db.Conditions.Add(whenAccountHasNoMoney);
            db.Conditions.Add(whenAccountHasAllowOverdraftFlag);
            db.Outcomes.Add(outcome);
            db.SaveChanges();

            //assert
            Assert.True(whenRequestIsDebit.Id > 0);
        }
예제 #8
0
 public override void LoadCache(Action <string, object> act,
                                params object[] args)
 {
     var db = new BillingwareDataContext();
 }