SBD.GL General Ledger: Part 3

xaf
working-out-loud

#1

I have moved a lot of the data structures around this morning.
Kind of Entity Framework Lego building.

Looks like I forgot to put

  [DefaultProperty("Code")]

on my GstCategory class

Now I am puzzling over how to do a data entry screen for a bank statement.

Bank statements are formatted with a debit and credit column.
Cash book programs generally show a debit , credit, and account column.

This is not my data structure , and I want the data structure how it is ( so every amount has a debit and a credit account )

to make the detail list view display like a cash book I am going to add some extra properties

I use the interface here just to put a name to my thinking

    public interface ICashbookLine
    {
        Account Account { get; set; }
        Decimal DebitAmount { get; set; }
        Decimal CreditAmount { get; set; }
    }
 

Then I implement them inside the Transaction class

        [NotMapped]
        public Account Account
        {
            get => Amount > 0 ? DebitAccount  : CreditAccount;
            set
            {
                if (Amount > 0)
                {
                    DebitAccount = value  ;
                }
                CreditAccount =   value;
                HiddenAccount = TranHeader.LinkedAccount;
            }
        }

        [NotMapped]
        public Account HiddenAccount  // other side of the transaction = header linked account
        {
            get => Amount > 0 ? CreditAccount : DebitAccount;
            set
            {
                if (Amount > 0)
                {
                    CreditAccount = value;
                }
                DebitAccount = value;
            }
        }
       
        [NotMapped]
        public decimal CreditAmount {
            get => Amount > 0 ? 0 : 0- Amount;
            set => Amount = 0 - value;
        }

        [NotMapped]
        public decimal DebitAmount
        {
            get => Amount > 0 ? Amount : 0;
            set => Amount = value;
        }

        [Browsable(false)]
        [RuleFromBoolProperty("AccountOk", DefaultContexts.Save, "Account must not be a header account")]
        public bool AccountOk => Account.Header == false;

I did try experimenting with omitting the header records from the pick list by using a DataSourceProperty attribute and a function to create a list of the non header accounts.

However I found that caused the down arrow to behave strangely. Besides it is convenient to see the tree structure.

https://www.devexpress.com/Support/Center/Question/Details/T353187/how-to-make-more-of-a-filter-datasourceproperty


#2

This makes the transaction screen look like a cash book, but what if the header account gets changed?
Well we need to update all the hidden accounts.
I wanted to temporarily see the HiddenAccount in the child listview , so I could be sure it was updating.
After experimenting I found that I needed to call OnPropertyChanged at the end of the setter for Transaction.HiddenAccount and decorate TranHeader.LinkedAccount with [ImmediatePostData]

So In TranHeader.cs I have

        private Account _linkedAccount;
        [ImmediatePostData]
        public virtual Account LinkedAccount {
            get => _linkedAccount;
            set
            {
                _linkedAccount = value;
                foreach (var tran in Transactions)
                {
                  tran.HiddenAccount= _linkedAccount;
                }
            }
        }

and in Transaction I have

        [NotMapped]
        public Account HiddenAccount  // other side of the transaction = header linked account
        {
            get => Amount > 0 ? CreditAccount : DebitAccount;
            set
            {
                if (Amount > 0)
                {
                    CreditAccount = value;
                }
                DebitAccount = value;
                OnPropertyChanged();
            }
        }

Note OnPropertyChanged is required by the INotifyPropertyChanged interface

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

#3

Now experimenting with making the data entry like a bank statement page with an opening and closing balance.
For this I will need to order all the transactions so I can calculate the total of the previous pages.


#4

For the Transaction Header
I want to remember the last date entered in this data entry session.

So I look up the singleton pattern

        public override void OnCreated()
        {
            if ( InstanceWideMemvars.Instance.EntryDate == null);
            {
                InstanceWideMemvars.Instance.EntryDate = DateTime.Now;
            }
            Date = InstanceWideMemvars.Instance.EntryDate;
            base.OnCreated();
        }

#5

How to set the Opening Balance?

I decided this way

       private Account _linkedAccount;
        [ImmediatePostData]
        [XafDisplayName("Account")]
        public virtual Account LinkedAccount {
            get => _linkedAccount;
            set
            {
                _linkedAccount = value;
                foreach (var tran in Transactions)
                {
                  tran.HiddenAccount= _linkedAccount;
                }
        
                OpeningBalance = LinkedAccount != null ? HandyFunctions.GetOpeningBalance(this) : 0;
            }
        }

where

        public static decimal GetOpeningBalance(TranHeader header)
        {
            using (var db = new GLDbContext())
            {
                try
                {
                    if (header.LinkedAccount == null) return 0;
                    var credits = db.Transactions
                        .Where(x => x.CreditAccount.Id == header.LinkedAccount.Id && x.TranHeader.StatementNumber < header.StatementNumber);
                        
                    var creditTotal    =  credits.Any() ? credits.Sum(y => y.Amount) :0;
                    var debits = db.Transactions
                        .Where(x => x.DebitAccount.Id == header.LinkedAccount.Id && x.TranHeader.StatementNumber < header.StatementNumber);
                    var debitTotal = debits.Any() ? debits.Sum(y => y.Amount) : 0;
                    return header.LinkedAccount.OpeningBalance + creditTotal - debitTotal;

                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
            }
        }

Note if there is an error then XAF simply does not display the record. I put the handler in as a convenient spot to put a break point.


#6

Now to find out how to make the StatementNumber not look like a currency.

     [ModelDefault(ModelDefaultConstants.EditMask, ModelDefaultConstants.EditMaskGeneral)]
     [ModelDefault(ModelDefaultConstants.DisplayFormat,ModelDefaultConstants.DisplayFormatGeneral)]
        public decimal StatementNumber { get; set; }  

I have started this class so I don’t need to remember the property names

  public static class ModelDefaultConstants
    {
        public const  string AllowEdit = "AllowEdit";
        public  const string IsFalse =  "false";
        public const string IsTrue = "true";

        public const string EditMask = "EditMask";
        public const string DisplayFormat = "DisplayFormat";
        public const string EditMaskGeneral = "g";
        public const string DisplayFormatGeneral = "{0:g}";
    }