A little business logic

Arjen Markus 2004-12-09: I am an engineer and I like to keep away from most business stuff, as it is a territory I do not understand very well :). Nevertheless, recent activities had me involved in something close to "business logic" and I decided, inspired by Colin McCormack, to try and set up a little business system.

Be warned: it is just a mere experiment, I have no serious intentions with it. I just wanted to explore a simple approach (see the comments in the source code).

In particular:

  • Not enough error checking
  • No procedure for reporting what maintenance is due (one of the things I had set as a goal, but I was too lazy)
  • I have not tested the replay facility yet - this was merely a thought :)

 # business.tcl --
 #     A little experiment with "business objects"
 #
 #     The idea is simple: we have a little company that sells
 #     a few products to various clients. These products require
 #     maintenance (and this is part of our income, of course).
 #     The system we implement here keeps track of the clients,
 #     what products they have bought and when maintenance is
 #     due. From the data we can make reports to get a feeling
 #     for how things are going ...
 #
 #     The implementation is fairly straightforward:
 #     - clients and products are identified by some unique ID
 #     - each client and product can have any number of attributes
 #     - some basic procs allow us to manipulate these "objects"
 #     - persistence is guaranteed by copying the commands to
 #       a "replay" file (so we do not need a genuine database
 #       in this implementation)
 #     - using the basic procs we build higher-level procs
 #       to create reports for instance.
 #
 namespace eval ::business {
     namespace export newid listids setattrib getattrib hasattrib
     namespace export newclient product sale reportSales reportClients

     variable data {}
     variable outfile [open "objects.sav" w]
 }

 # newid --
 #     Create a new ID
 #
 # Arguments:
 #     type         Type of object
 #     name         Name of the object
 # Result:
 #     Unique ID (unique within this run)
 #
 proc ::business::newid {type name} {
     variable data
     variable outfile

     puts $outfile [list newid $type $name]

     set id [llength $data]
     lappend data [list TYPE $type NAME $name]

     return $id
 }

 # whichid --
 #     Find the ID for a name
 #
 # Arguments:
 #     type         Type of object
 #     name         Name of the object
 # Result:
 #     Unique ID (unique within this run)
 #
 proc ::business::whichid {type name} {
     variable data

     set id    0
     set found 0
     foreach obj $data {
         if { [getattrib $id TYPE] == $type &&
              [getattrib $id NAME] == $name    } {
             set found 1
             break
         }
         incr id
     }
     if { $found } {
         return $id
     } else {
         return -code error "No such object: $name"
     }
 }

 # listids --
 #     Return a list of all IDs of given type
 #
 # Arguments:
 #     type         Type of objects
 # Result:
 #     List of all IDs of this type
 #
 proc ::business::listids {type} {
     variable data

     set ids   {}
     set index  0
     foreach obj $data {
         if { [lindex $obj 1] == $type } {
             lappend ids $index
         }
         incr index
     }
     return $ids
 }

 # setattrib --
 #     Set an attribute to a (new) value
 #
 # Arguments:
 #     id           ID of the object
 #     attrib       Name of the attribute
 #     value        Value for the attribute
 # Result:
 #     Nothing
 #
 proc ::business::setattrib {id attrib value} {
     variable data
     variable outfile

     puts $outfile [list setattrib $id $attrib $value]

     set obj [lindex $data $id]

     set index 1
     set found 0
     foreach {a v} $obj {
         if { $a == $attrib } {
             set found 1
             break
         }
         incr index 2
     }

     if { $found } {
         lset obj $index $value
     } else {
         lappend obj $attrib $value
     }
     lset data $id $obj
 }

 # getattrib --
 #     Get the value of an attribute
 #
 # Arguments:
 #     id           ID of the object
 #     attrib       Name of the attribute
 # Result:
 #     Value of the attribute
 #
 proc ::business::getattrib {id attrib} {
     variable data

     set obj [lindex $data $id]

     set found 0
     foreach {a v} $obj {
         if { $a == $attrib } {
             set found 1
             break
         }
     }

     if { $found } {
         return $v
     } else {
         return -code error "No such attribute: $attrib"
     }
 }

 # hasattrib --
 #     Check if the object has such an attribute
 #
 # Arguments:
 #     id           ID of the object
 #     attrib       Name of the attribute
 # Result:
 #    1 if there is such an attribute, 0 otherwise
 #
 proc ::business::hasattrib {id attrib} {
     variable data

     set obj [lindex $data $id]

     set found 0
     foreach {a v} $obj {
         if { $a == $attrib } {
             set found 1
             break
         }
     }

     return $found
 }

 # newclient --
 #     Define a new client
 #
 # Arguments:
 #     name         Name of the client
 # Result:
 #     New ID
 #
 proc ::business::newclient {name} {
     newid CLIENT $name
 }

 # product --
 #     Define a new product
 #
 # Arguments:
 #     name         Name of the product
 #     price        Price of a single item
 #     maintenance  Maintenance period (in days)
 # Result:
 #    New ID
 #
 proc ::business::product {name price maintenance} {
     set id [newid PRODUCT $name]
     setattrib $id PRICE $price
     setattrib $id MAINT $maintenance
     return $id
 }

 # sale --
 #     Register a sale
 #
 # Arguments:
 #     client       Name of the client
 #     product      Name of the product
 #     date         When was the sale made
 # Result:
 #     Nothing
 #
 proc ::business::sale {client product date} {
     set cid [whichid CLIENT $client]
     set pid [whichid PRODUCT $product]

     if { [hasattrib $cid SALES] } {
         set sales [getattrib $cid SALES]
     } else {
         set sales {}
     }
     lappend sales [list $pid $date]
     setattrib $cid SALES $sales
 }

 # reportSales --
 #     Print a report on the sales since some date
 #
 # Arguments:
 #     from         Date from which to report
 # Result:
 #     Printed report
 #
 proc ::business::reportSales {from} {
     set result 0
     foreach pid [listids PRODUCT] {
         set count($pid) 0
         set name($pid)  [getattrib $pid NAME]
     }
     foreach cid [listids CLIENT] {
         set sales [getattrib $cid SALES]

         foreach sale $sales {
             foreach {pid date} $sale {break}

             if { $date > $from } {
                 incr count($pid)
                 incr result [getattrib $pid PRICE]
             }
         }
     }

     puts "Sales since $from:"
     puts [format "%20s %10s" Product Number]
     foreach pid [listids PRODUCT] {
         puts [format "%20s %10d" $name($pid) $count($pid)]
     }
     puts "Result: $result (monetary units)"
 }

 # reportClients --
 #     Print a report on the clients and how much values they represent
 #
 # Arguments:
 #     None
 # Result:
 #     Printed report
 #
 proc ::business::reportClients {} {
     puts "Clients:"
     puts [format "%20s %10s %10s" Client Number "Total value"]

     foreach cid [listids CLIENT] {
         set value 0
         set count 0
         set name  [getattrib $cid NAME]

         set sales [getattrib $cid SALES]

         foreach sale $sales {
             foreach {pid date} $sale {break}

             incr count
             incr value [getattrib $pid PRICE]
         }

         puts [format "%20s %10d %10d" $name $count $value]
     }
 }

 # Far from finished, of course ... but let us test it anyway
 #
 namespace import ::business::*

 product   "A-machine" 10000 365
 product   "B-machine"  5300 120
 product   "C-machine"   300 365

 newclient "ABC"
 newclient "FF"

 sale ABC A-machine 2004/01/12
 sale ABC B-machine 2004/01/22
 sale ABC B-machine 2004/02/11
 sale FF  C-machine 2003/10/31
 sale FF  A-machine 2003/11/08
 sale ABC C-machine 2004/05/25
 sale FF  A-machine 2004/06/03

 reportSales 2004/01/01
 reportClients