Design advice

Design advice

Old forum URL: forums.lhotka.net/forums/t/4148.aspx


JimBalo posted on Thursday, January 10, 2008

Hi,

I am working on a fairly simple app (CSLA) dealing with payment scheduling of invoices, but I cannot seem to come up with a smooth design for this.  I am hoping one of you fellows would be able to give me some advice.

It is a WinForm app with a monthly calendar that shows an overview of invoice totals, etc. by day.  When a day is clicked, you get a detail screen for that day.  The detail screen contains:
A Vendor grid showing each vendor with amount due, payment total, etc.
An Invoice grid showing all invoices for the selected vendor
A Payment grid, showing all scheduled payments for the selected invoice.

I have attached a diagram of the main business objects (VendorCollection->Vendors->InvoiceCollection->Invoices->PaymentCollection->Payments).

I have about 4,000 open invoices which I first load into a InvoiceCollection, then I pass that InvoiceCollection to VendorCollection.Create(InvoiceCollection), which creates the hierarchy you can see in the attached diagram.  The Calendar is then populated from the VendorCollection. 

In the detail screen, the Vendor grid binds to the VendorCollection, the Invoice grid binds to the InvoiceCollection [_vendorCollection.GetVendor(CurrentVendorNo).InvoiceCollection] and the Payment grid binds to the Payment Collection (CurrentInvoice.PaymentCollection). 

What makes it complicated is that the user needs to be able to view and work with a variety of subsets of the vendors / invoices:
1) The Calendar on the main screen shows dayily totals (all vendors combined), but highlight the day box if any one vendor has a daily total exceeding a certain amount.
2) In the detail screen, the Vendor grid shows daily totals, monthly totals and grand totals for each vendor.
3) The user can select one or more vendors in the vendor grid and perform operations on all payments assigned to the invoices for those vendors for the day (for example: remove payments, change payment date, add payments).
4) The user can select one or more vendors in the vendor grid and perform operations on all payments assigned to the invoices for those vendors for the month.
5) The user can select one or more vendors in the vendor grid and perform operations on all payments assigned to the invoices for those vendors (no time-limiter)
6) The user can select one or more invoices in the invoice grid and perform operations on all payments assigned to those invoices.

I have tried two different approaches to handle this:
1) Have the collection objects return subsets of vendors / invoices, etc. and bind these sub-collections to the grids, etc.
2) Operate on the same all-inclusive VendorCollection (containing all vendors, invoices and payments for all days) and use a SetDateFilter method together with methods like RemovePaymentsForDay, etc.

The problem with 1) is that the DataBinding events fire in the subcollection, so other grids, etc. do not get updated.  Also, if you remove items from a subcollection, you have to setup a mechanism to also remove them from any other collections.  It becomes very messy trying to keep the collections synchronized.

The problem with 2) is that the collection objects get cluttered with a lot of methods / properties like AmountDue, AmountDueForMonth, PaymentTotalForDay, etc.

I am looking for a cleaner, smoother design for handling this. 

Any advice would be greatly appreciated. 

Thanks,
Jim

JimBalo replied on Friday, January 11, 2008

Anyone??

I realize that it is a lengthy post, but maybe someone can shed some light on how to address the overall issue of having a user work with subsets of collections of business objects via databinding? 

Thanks,

Jim

 

Lars replied on Friday, January 11, 2008

Jim,

I am having a very similar scenario, so I am also interested in a solution to this.  It has to be a fairly common scenario to work with "filtered" business object collections, but it turns out to be quite a challenge.  But I am new to CSLA and not an expert on OOD.

Maybe one of the csla / OOD gurus in the forum will be able to help...

Lars

Ps. One option would be to use a DataSet with DataViews filtering, but then you would of course lose all the benefits of having business logic encapsulated a' la Rocky. 

 

 

JoeFallon1 replied on Saturday, January 12, 2008

In my Web app, if I have two grids and I want to display the data in the 2nd grid based on the slected row of the first grid, then I fetch 2 collections and set the data source of the 2nd grid to a FilteredBindingList which uses the 2nd collection as a source and the first collection selected row PK as the filter.

The FilteredBindingList keeps the original full 2nd collection up to date with changes made by the user.

Search this forum for FilteredBindingList and you should find some good code samples.

Joe

 

JimBalo replied on Monday, January 14, 2008

Hi Joe and thanks for the reply,

I am using a FilteredBindingList whenever I need to bind a filtered subset to a grid already (that part was easy).  The part I find tricky / messy is when the user then wants to perform some action on this filtered subset (such as remove all payments for the subset of invoices or move all payments for the subset to a different date, etc.).   I have methods in InvoiceCollection already to handle all such actions and  in a perfect world, all such methods would be able to operate on a filtered subset as well.  But I do not know of a way to accomplish this in the real world, so instead I have created a bunch of filter-related methods instead. 

Example for InvoiceCollection:

SetDateFilter is called on InvoiceCollection, which in turn calls it on each Invoice which calls it on PaymentCollection which calls it on each Payment.

  public void SetDateFilter(DateTime date)
  {
      _filteredInvoices = new FilteredBindingList<Invoice>(this, InvoiceFilter);
      _filteredInvoices.ApplyFilter("", date);
     
      foreach (Invoice invoice in this)
          invoice.SetDateFilter(date);
  }

  public void ChangePayDates(DateTime newDate)
  {
      PaymentsForDay.ChangePayDates(newDate);
  }

  public InvoiceCollection InvoicesForDay
  {
      get
      {
          InvoiceCollection invoices = InvoiceCollection.GetEmpty();
          foreach (Invoice invoice in _filteredInvoices)
              invoices.Add(invoice);
         
          return invoices;
      }
  }

  public double InvoicesForDayPaymentTotal
  {
      get
      {
          double amount = 0;
         
          foreach (Invoice invoice in _filteredInvoices)
              amount += invoice.PaymentTotalForDay;

          return amount;
      }
  }

  public double InvoicesForDayDueAmountTotal
  {
      get
      {
          double amount = 0;

          foreach (Invoice invoice in _filteredInvoices)
              amount += invoice.InvoiceAmount;

          return amount;
      }
  }

There are many more filter related methods.  I am looking for a cleaner solution to the above...

Thanks,

Jim

 

 

Copyright (c) Marimer LLC