Page 1 of 3

Example Business Object (Customer)

PostPosted: Wed Jun 28, 2017 6:02 pm
by James Bott
I am working on a new business object. This one is an object representing a single customer, not a customer table. I know, we are all used to working with tables and a single object is just a row of the table. Let's make it simpler--and more complex. What?

Generally we are only working with tables to find the row we need. After we find that row, we can make that into an object which can have it's own methods and can be passed to other objects. The object is more complex than a row of data, but much easier to work with and less prone to bugs. It is also a smart object--it can contain methods.

I believe that business objects are the most important advance one can make in programming.

As I have said before, the main concepts used in OOP are granularity, polymorphism, encapsulation, and inheritance. I am using all of these here.

Inheritance: This class is inheriting from my TRecord class which inherits from my TData class.
Granularity: This class is broken down into many small parts.
Polymorphism: Methods New(), End(), Display() are used in many other objects.
Encapsulation: This object looks up it's own data (and saves it too) in the customer table. No PUBLICS or PRIVATES are used.

To instanitate this object all you need one line of code and the CustNo.

oCustomer:= TCustomer():New(cCustNo)

That's it!

Now you can query the object for any info it contains (which can include complex calculations and reports).

Everything is encapsulated so you don't have to worry about workareas, aliases, or variable name conflicts.

You can also do a series of field updates with one call:

oCustomer:applyPayment( nAmount, date() )

This updates three fields and saves the changes. Simple.

Keep in mind that this is an incomplete class. I am still working on it.

So, look at the code and post your questions and/or comments.

James

Code: Select all  Expand view
/*
Purpose  : Customer Class
Author   : James Bott, jbott@compuserve.com
Date     : 06/27/2017 09:47:36 AM
Company  : Intellitech
Copyright: Copyright © 2017 Intellitech. All rights reserved.
Language : Fivewin/xHarbour
Updated  :
Notes    :

*/


#include "fivewin.ch"

Function Main()

   Local oCustomer, oCustomers
   Local cCustNo
   Field custno, company
   
   REQUEST DBFCDX
   rddsetdefault( "DBFCDX" )
   set deleted on
   
   ferase("arcust.cdx")
   
   // Just for testing
   // Must use TAG clause with CDXs
   use arcust exclusive
   index on CUSTNO tag "CUSTNO" to arcust
   index on upper(COMPANY) tag "COMPANY" to arcust
   use
   
   // Instantiate the object
   cCustNo:="10007"
   oCustomer:= TCustomer():new(cCustNo)
   oCustomer:display()
   
   /* Testing
   msgInfo( oCustomer:Company,"oCustomer:Company")
   msgInfo( oCustomer:getBalance(), "Customer Balance"  )
   msgInfo( oCustomer:paymentStatus(), "Payment Status" )
   msgInfo( oCustomer:PaymentTerms(), "Payment Terms" )
   msgInfo( oCustomer:YearToDateSales(),"Year To Date Sales"  )
   */

   
   oCustomer:end()

Return nil


//--- Customer class
CLASS TCustomer from TRecord
   METHOD New( cCustNo )
   METHOD ApplyPayment( nAmount, dDate)
   Method Display()
    METHOD End()
   Method GetBalance()
   Method PaymentStatus()
   Method PaymentTerms()
   Method PastDueAmount()
   Method PastDueDays()
   Method PTDSales() inline ::ptdsls
   Method YearToDateSales() inline ::ytdsls
   Method SalesHistory()
ENDCLASS

METHOD New( cCustNo ) CLASS TCustomer
   ::oTable:= TCustomers():New()
   ::oTable:setOrder(1)
   ::oTable:seek( cCustNo )
   ::Load()
RETURN Self

METHOD End() Class TCustomer
    ::oTable:End()
Return nil

METHOD ApplyPayment( nAmount, dDate ) CLASS TCustomer
   default dDate:= date()
   ::balance:= ::balance - nAmount
   ::lastPay:= dDate
   ::lpymt  := nAmount
   ::save()
RETURN nil

Method GetBalance() Class TCustomer
Return ::balance

Method PaymentStatus() Class TCustomer
Return "Paid Up"

Method PaymentTerms() Class TCustomer
Return "Net "+alltrim(str(::PDays))+" days"

Method PastDueAmount() Class TCustomer
Return 0

Method SalesHistory() Class TCustomer
   //Local oSalesHistory:= TSalesHistory():New(::custNo)
   //oSalesHistory:Display()
   //oSalesHistory:end()
Return nil

Method PastDueDays() Class TCustomer
Return 0

Method Display() Class TCustomer
   Local cString:=""

   cString := cString + "Customer No: "+ ::custno + CRLF
   cString := cString + "Payment Status: "+ ::paymentStatus + CRLF
   cString := cString + "Payment Terms: "+ ::paymentTerms() + CRLF
   cString := cString + "Balance: " + TRANSFORM(::GetBalance(), "$999,999.99") + CRLF
   cString := cString + "Year-To-Date Sales: " + transform(::yeartodateSales(), "$999,999.99") + CRLF
   
   msgInfo(cString, ::Company)
   
Return nil




//---------------------------------------------------------------------------//
// Customer table class
Class TCustomers From TData
   Method New()
   Method End() inline ::close()
Endclass

Method New() Class TCustomers
   ::super:new(,"arcust")
   ::use()
   ::setOrder(1)
   ::gotop()
Return self

// EOF

Re: Example Business Object (Customer)

PostPosted: Wed Jun 28, 2017 9:34 pm
by karinha
Image

Re: Example Business Object (Customer)

PostPosted: Wed Jun 28, 2017 10:33 pm
by James Bott
João Santos,

Thanks for your interest.

Unfortunately, you cannot compile this code without my TData and TRecord classes which are both included in the TData package. TData is a commercial product available for purchase from my company. For more information see: http://gointellitech.com/program.html

Although you cannot compile it, I posted it here so you can see how it works and begin to understand what is possible with business objects.

On my website you will find my email address. If you send me an email (so I have your email address) I can send you a compiled copy of the code which you can test.

Saludos,
James

Re: Example Business Object (Customer)

PostPosted: Wed Jun 28, 2017 10:42 pm
by karinha
Thank you James. I understood. You have a commercial product. I do not think here is the ideal place for that. Here, we share codes with fivewinners friends. Thank you so much.

Re: Example Business Object (Customer)

PostPosted: Thu Jun 29, 2017 12:12 am
by James Bott
João,

I did not post it to sell my product. TData is the product I use to develop my apps, so naturally I used it to develop this business object.

I posted it just to show how you can build business objects. You don't have to use my product to build them, it just makes it much easier. You could build your own TRecord class to do the same thing. I guess I should have made that clear in my original posting.

I apologize if I offended you.

Regards,
James

Re: Example Business Object (Customer)

PostPosted: Thu Jun 29, 2017 12:01 pm
by karinha
Good morning, James. It did not offend me at all. I think I got it wrong. I do not understand much English. I apologize to you. Hugs.

Re: Example Business Object (Customer)

PostPosted: Thu Jun 29, 2017 12:07 pm
by Enrico Maria Giordano
James, I find your posts on OOP very stimulating. Thank you.

EMG

Re: Example Business Object (Customer)

PostPosted: Thu Jun 29, 2017 6:25 pm
by James Bott
Ok, in order to provide a working copy of the TCustomer class, I have made some modifications so it will run without my TData and TRecord classes. There are two files, one contains the class the other builds the test data file.

Give it a try.

James

TCustomer Class and test:
Code: Select all  Expand view
/*
Purpose  : Customer class
Author   : James Bott, jbott@compuserve.com
Date     : 06/29/2017 08:51:51 AM
Company  : Intellitech
Copyright: Copyright © 2017 Intellitech.
Language : Fivewin/xHarbour
Updated  :
Notes    : Works without TData and TRecord classes.
           Incomplete, limited functionality without all fields defined.

*/



#include "fivewin.ch"

Function Main()

   Local oCustomer, oCustomers
   Local cCustNo
   Field custno, company
   
   REQUEST DBFCDX
   rddsetdefault( "DBFCDX" )
   set deleted on
   
   ferase("arcust.cdx")
   
   // Just for testing
   // Must use TAG clause with CDXs
   use arcust exclusive
   index on CUSTNO tag "CUSTNO" to arcust
   index on upper(COMPANY) tag "COMPANY" to arcust
   use
   
   // Instantiate the object
   cCustNo:="100001"
   oCustomer:= TCustomer():new(cCustNo)
   oCustomer:display()
   
   
   // Testing
   //msgInfo( oCustomer:Custno,"oCustomer:CustNo")
   //msgInfo( oCustomer:Company,"oCustomer:Company")
   //msgInfo( oCustomer:getBalance(), "Customer Balance"  )
   //msgInfo( oCustomer:paymentStatus(), "Payment Status" )
   //msgInfo( oCustomer:PaymentTerms(), "Payment Terms" )
   //msgInfo( oCustomer:YearToDateSales(),"Year To Date Sales"  )
   
   
   oCustomer:end()

Return nil


//--- Customer class
CLASS TCustomer //from TRecord
   Data oTable  // parent table
   // Fields
   Data custno
   Data company
   Data balance
   Data pdays
   Data ytdsls
   
   METHOD New( cCustNo )
   METHOD ApplyPayment( nAmount, dDate)
   Method Display()
    METHOD End()
   Method GetBalance()
   Method PaymentStatus()
   Method PaymentTerms()
   Method PastDueAmount()
   Method PastDueDays()
   Method PTDSales() inline ::ptdsls
   Method YearToDateSales() inline ::ytdsls
   Method SalesHistory()
   Method Load()
   Method Save()
ENDCLASS

METHOD New( cCustNo ) CLASS TCustomer
   ::oTable:= TCustomers():New()
   ::oTable:setOrder(1)
   ::oTable:seek( cCustNo )
   ::Load()
RETURN Self

METHOD End() Class TCustomer
    ::oTable:End()
Return nil

METHOD ApplyPayment( nAmount, dDate ) CLASS TCustomer
   default dDate:= date()
   ::balance:= ::balance - nAmount
   ::lastPay:= dDate
   ::lpymt  := nAmount
   ::save()
RETURN nil

Method GetBalance() Class TCustomer
Return ::balance

Method PaymentStatus() Class TCustomer
Return "Paid Up"

Method PaymentTerms() Class TCustomer
Return "Net "+alltrim(str(::PDays))+" days"

Method PastDueAmount() Class TCustomer
Return 0

Method SalesHistory() Class TCustomer
   //Local oSalesHistory:= TSalesHistory():New(::custNo)
   //oSalesHistory:Display()
   //oSalesHistory:end()
Return nil

Method PastDueDays() Class TCustomer
Return 0

Method Display() Class TCustomer
   Local cString:=""

   cString := cString + "Customer No: "+ ::custno + CRLF
   cString := cString + "Payment Status: "+ ::paymentStatus() + CRLF
   cString := cString + "Payment Terms: "+ ::paymentTerms() + CRLF
   cString := cString + "Balance: " + TRANSFORM(::GetBalance(), "$999,999.99") + CRLF
   cString := cString + "Year-To-Date Sales: " + transform(::yeartodateSales(), "$999,999.99") + CRLF
   
   msgInfo(cString, ::Company)
   
Return nil

//---------------------------------------------------------------------------//
// Need these two methods if not using TRecord.
// TRecord handles all this automatically and has other features also.

Method Load() Class TCustomer
   ::custno := ::oTable:custno
   ::company := ::oTable:company
   ::balance := ::oTable:balance
   ::pdays   := ::oTable:pdays
   ::ytdsls  := ::oTable:ytdsls
   //... add rest of fields here
Return nil

Method Save() Class TCustomer
   ::oTable:custno := ::custno
   ::oTable:company := ::company
   ::oTable:balance := ::balance
   ::oTable:pdays   := ::pdays
   ::oTable:ytdsls  := ::ytdsls
   //... add rest of fields here
   ::oTable:save()
Return nil


//---------------------------------------------------------------------------//
// Customer table class
Class TCustomers From TDatabase
   Method New()
   Method End() inline ::close()
Endclass

Method New() Class TCustomers
   ::super:new(,"arcust")
   ::use()
   ::setOrder(1)
   ::gotop()
Return self

// EOF
 


This builds the data file:

Code: Select all  Expand view
/*
Purpose  : Build arcust.dbf file
Author   : James Bott, jbott@compuserve.com
Date     : 06/29/2017 09:25:08 AM
Company  : Intellitech
Copyright: Copyright © 2017 Intellitech
Language : Fivewin/xHarbour
Updated  :
Notes    :

*/


#include "fivewin.ch"

Function Main()
   ferase("arcust.dbf")
   genArcust()
   addRecords()
   //use arcust
   //browse()
   msgInfo("ARCUST.DBF has been built.")
Return nil

function genARCUST()
   local aStruc:={}
   aadd( aStruc, { "CUSTNO", "C",6, 0 } )
   aadd( aStruc, { "COMPANY", "C",35, 0 } )
   aadd( aStruc, { "CONTACT", "C",20, 0 } )
   aadd( aStruc, { "ASSIST", "C",20, 0 } )
   aadd( aStruc, { "ADDRESS1", "C",30, 0 } )
   aadd( aStruc, { "ADDRESS2", "C",30, 0 } )
   aadd( aStruc, { "CITY", "C",20, 0 } )
   aadd( aStruc, { "STATE", "C",10, 0 } )
   aadd( aStruc, { "ZIP", "C",10, 0 } )
   aadd( aStruc, { "COUNTRY", "C",20, 0 } )
   aadd( aStruc, { "PHONE", "C",20, 0 } )
   aadd( aStruc, { "TERR", "C",2, 0 } )
   aadd( aStruc, { "INDUST", "C",5, 0 } )
   aadd( aStruc, { "SALESMN", "C",2, 0 } )
   aadd( aStruc, { "SOURCE", "C",5, 0 } )
   aadd( aStruc, { "CODE", "C",2, 0 } )
   aadd( aStruc, { "TYPE", "C",8, 0 } )
   aadd( aStruc, { "PTERMS", "C",20, 0 } )
   aadd( aStruc, { "PDISC", "N",7, 3 } )
   aadd( aStruc, { "PDAYS", "N",3, 0 } )
   aadd( aStruc, { "PNET", "N",3, 0 } )
   aadd( aStruc, { "SVC", "N",7, 3 } )
   aadd( aStruc, { "TAX", "N",7, 3 } )
   aadd( aStruc, { "DISC", "N",7, 3 } )
   aadd( aStruc, { "LDATE", "D",8, 0 } )
   aadd( aStruc, { "LASTPAY", "D",8, 0 } )
   aadd( aStruc, { "ENTERED", "D",8, 0 } )
   aadd( aStruc, { "LIMIT", "N",7, 0 } )
   aadd( aStruc, { "BALANCE", "N",12, 2 } )
   aadd( aStruc, { "PTDSLS", "N",12, 2 } )
   aadd( aStruc, { "YTDSLS", "N",12, 2 } )
   aadd( aStruc, { "ONORDER", "N",12, 2 } )
   aadd( aStruc, { "CREDIT", "N",12, 2 } )
   aadd( aStruc, { "LPYMT", "N",12, 2 } )
   aadd( aStruc, { "LSALE", "N",12, 2 } )
   aadd( aStruc, { "GLLINK", "C",3, 0 } )
   aadd( aStruc, { "COMMENT", "C",65, 0 } )
   aadd( aStruc, { "HISTORY", "C",1, 0 } )
   aadd( aStruc, { "PRICECODE", "C",1, 0 } )
   aadd( aStruc, { "TAXCODE", "C",1, 0 } )
   aadd( aStruc, { "CURRENCY", "C",3, 0 } )
   aadd( aStruc, { "FLAGS", "C",10, 0 } )
   aadd( aStruc, { "CSTNUM1", "N",9, 0 } )
   aadd( aStruc, { "CSTNUM2", "N",9, 0 } )
   aadd( aStruc, { "SIGNATURE", "N",2, 0 } )
   aadd( aStruc, { "RATE", "N",2, 0 } )
   aadd( aStruc, { "INVNOTE", "C",45, 0 } )
   aadd( aStruc, { "EMAIL", "C",30, 0 } )
   dbcreate( "ARCUST.DBF" , aStruc, "DBFNTX" )
return nil

Function AddRecords()
   field custno, company, balance, pdays
   use arcust
   
   // For some reason we have to use REPLACE for the YTDSLS field.
   
   append blank
   custno:="100000"
   company:="Acme Widget Co"
   balance:= 0
   pdays:= 10
   replace ytdsls with 16897.23
   
   append blank
   custno:="100001"
   company:="First Furniture Co"
   balance:= 762.55
   pdays:= 10
   replace ytdsls with 23877.23

   append blank
   custno:="100002"
   company:="Southwest Auto"
   balance:= 1435.81
   pdays:= 10
   replace ytdsls with 11876.39  
   
   close
Return nil

// EOF

Re: Example Business Object (Customer)

PostPosted: Thu Jun 29, 2017 8:19 pm
by dtussman
James, there is a misspelling of the word "balance" in the save method

Re: Example Business Object (Customer)

PostPosted: Thu Jun 29, 2017 9:09 pm
by James Bott
Thanks, David, I never used the save method so I didn't see it. It is now fixed.

Did you try the code?

James

Re: Example Business Object (Customer)

PostPosted: Thu Jun 29, 2017 9:36 pm
by Marc Venken
Hey James,

Meanwhile, i have read you website about OOP part 1 & 2 : very interesting.

Your sample is working, but I have some questions :

What is exactly the 'more plus' that Tdata has against FW current version ?

I see that you refer in the info, but also in the sample for this :

Code: Select all  Expand view
//---------------------------------------------------------------------------//
// Need these two methods if not using TRecord.
// TRecord handles all this automatically and has other features also.

Method Load() Class TCustomer
   ::custno := ::oTable:custno
   ::company := ::oTable:company
   ::balance := ::oTable:balance
   ::pdays   := ::oTable:pdays
   ::ytdsls  := ::oTable:ytdsls
   //... add rest of fields here
Return nil

Method Save() Class TCustomer
   ::oTable:custno := ::custno
   ::oTable:company := ::company
   ::oTable:blanace := ::balance
   ::oTable:pdays   := ::pdays
   ::oTable:ytdsls  := ::ytdsls
   //... add rest of fields here
   ::oTable:save()
Return nil
 


But if I put this code into your sample

Code: Select all  Expand view
  use customer NEW
   select customer
   database oCustomer
   xbrowse(oCustomer)

   msginfo(oCustomer:First)
 


All the customer fields are there, so is this still actual ?

Re: Example Business Object (Customer)

PostPosted: Thu Jun 29, 2017 10:45 pm
by James Bott
Marc,

What is exactly the 'more plus' that Tdata has against FW current version ?


You can find answers to that here:
http://gointellitech.com/tdata.html

use customer NEW
   select customer
   database oCustomer
   xbrowse(oCustomer)

   msginfo(oCustomer:First)


In the above perhaps you have missed the difference between a customer object (singular) and the customers (plural) table? The customer object represents a single customer. It is independent of the table. It has its own data which is copied from the customers table. It is thus buffered and is only saved when (and if) you, or it, call(s) the Save Method of the customer object. The only time you would have to call it is during an edit of the data. Otherwise, like in the ApplyPayment() method, the object saves it's own data.

But the bigger point is that you get to have methods of the customer object which you don't have with the customers table object. The table object only contains methods pertaining to a table. Yes you could add methods to a table to manipulate a single record (such as SalesReport), but you are risking that the table pointer gets moved and you are then lumping properties of a table and properties of a single object into the same class. This violates granularity and really clouds your ability to understand the customer object. When you create a customer object it is separate from the table and it only contains data and methods pertinent to a customer.

It is also encapsulated so you can pass it around to other objects and/or routines and still use the customers table without affecting the customer object. When, and if, you change the data in the customer object, you don't have to worry about saving it, the object handles that. If it is well designed it is a smart object.

One of the reasons we need OOP is that code is so much more complicated than it used to be. Let's say you have 50 functions in a program. Each one can call the other 49 so that is 50 X 49 = 2450 possible interactions. Can you get your head around that? What if there are 100 or 200 functions? Yikes!

So if you build 5 objects and each has 10 methods, then it is a whole lot easier to understand. There are only 5 objects interacting and some of those methods can be hidden so that they are not visible outside the object. It's then gets even easier to grasp.

I'm all about easy.

James

Re: Example Business Object (Customer)

PostPosted: Thu Jun 29, 2017 10:52 pm
by James Bott
Marc,

I forgot to answer your question about TRecord. The TRecord class has automatic Load() and Save() methods. You never have to manually write these gather and scatter routines. And if you add or remove fields from the data file, they are automatically handled by TRecord.

TRecord also automatically handles new records. If the Save() method is called it checks to see if the record has an ID, if not, then it assumes it is a new record and saves it accordingly, otherwise it saves it to the existing record.

It is the base class I use for building business objects.

Or, without TRecord, you can manually write those routines for each file and keep them up to date.

James

Re: Example Business Object (Customer)

PostPosted: Fri Jun 30, 2017 12:12 am
by nageswaragunupudi
FWH has TDataRow

TRecord works only with DBF and TData

TDataRow works with dbf, tdatabase, tdata, recorsets, arrays, mysql and a lot more.

Re: Example Business Object (Customer)

PostPosted: Fri Nov 16, 2018 8:28 am
by Silvio.Falconi
Good Mr Rao,
can you make small samples using Tdatarow please