#!/usr/local/qddb/bin/qwish
#
# estimates.tcl --
#     This module contains two classes:
#         InvoiceOut .... manages the tkTable widget
#         Invoice ....... This class manages the Estimates window, including 
#                         interaction with the InvoiceOut tkTable class
#                         All estimates are stored in the Estimates qddb relation.
#
#     Unlike the invoices.tcl module, this module does not impact any of
#     the relations such as inventory, payments, clients, etc...
#
#     Estimates are completely independent of the other relations.
#
#     Basically, we duped the invoices.tcl code and stripped out the
#     parts that updated the client, inventory, and payment relations.

set ISPUTIL /var/lib/ISPutil
set tcl_precision 17
global gv_auto_print  ;# automatically print invoices after processing
set gv_auto_print on
lappend auto_path . $qddb_library/fx $blt_library $blt_library/dd_protocols \
	$ISPUTIL/bin/library

#
# InvoiceOut --
#     This class manages the tkTable widget
#
itcl_class InvoiceOut {
    constructor {config} {
	global gv_invoice DragDrop
	if {[winfo exists .invoice_out]} { return }
	set io ""
	frame $io.i -bd 4 -relief groove
	pack $io.i -side top -expand on -fill both -expand yes
	scrollbar $io.i.s -command [list $io.i.tbl toprow]
	pack $io.i.s -side right -fill y
	set tw $io.i.tbl
	tktable $io.i.tbl -rows $maxrows -cols $maxcols -rowtitle 1 -coltitle 1 \
		-anchor w -rowfirstmode on -maxheight 125 \
		-yscrollcmd [list $io.i.s set] -var gv_invoice
	$io.i.tbl batch off
	$io.i.tbl editmode auto-clear
	$io.i.tbl drawmode fast
	$io.i.tbl toprow 0
	$io.i.tbl leftcol 1
	$io.i.tbl colstretch unset 
	$io.i.tbl width {0 4} {1 4} {2 4} {3 5} {4 15} {5 30} {6 8} {7 3} {8 1}\
		{9 8}
	::set gv_invoice(0,0) " Item"
	::set gv_invoice(0,1) " B/O"
	::set gv_invoice(0,2) "Ship"
	::set gv_invoice(0,3) " Qty"
	::set gv_invoice(0,4) " Part"
	::set gv_invoice(0,5) " Description"
	::set gv_invoice(0,6) " Price   "
	::set gv_invoice(0,7) "D%"
	::set gv_invoice(0,8) "GST"
	::set gv_invoice(0,9) " Total "
	for {set i 1} {$i < $maxrows} {incr i} {
	    ::set gv_invoice($i,0) $i
	}
	pack $io.i.tbl -side top -fill both -expand yes
	$io.i.tbl colstretch all
	$io.i.tbl setcell 1 3
	$tw tag con bg -background bisque
	$tw tag coltag bg 0 1 2
	$tw tag rowtag bg 0
	$tw tag con rjust -anchor e
	$tw tag coltag rjust 6 7 
	$tw tag con bgalign -background bisque -anchor e
	$tw tag coltag bgalign 8 9
	bindings
    }
    method config {config} {}
    method moveRight {} {
	if { $ccol >= [expr $columns-2] } { 
	    set ccol 3
	    $tw setcell $crow $ccol
	} else { 
	    incr ccol
	    $tw setcell $crow $ccol 
	}
    }
    method moveLeft {} {
	if { $ccol == 3 } { 
	    set ccol [expr $columns-2]
	    $tw setcell $crow $ccol
	} else { 
	    incr ccol -1
	    $tw setcell $crow $ccol 
	}
    }
    method moveDown {} {
	if { $crow < $rowCount } { 
	    incr crow 
	    $tw setcell $crow $ccol 
	} else {
	    addRow
	}
    }
    method moveUp {} {
	if { $crow > 1 } { 
	    incr crow -1
	    $tw setcell $crow $ccol 
	}
    }
    method bButton { x y } {
	global gv_invoice
	focus .i.tbl
	set cell [$tw whatcell $x $y]
	set r [lindex $cell 0]	
	set c [lindex $cell 1]
	if { $r > $rowCount || $c == 1 || $c == 2 || $c == 9 } { return }
	# toggle the tax via mouse button only
	if { $c == 8 } {
	    if { $gv_invoice($r,9) == "" } { return }
	    if { $gv_invoice($r,$c) == "X" } { 
		set gv_invoice($r,$c) "" 
	    } else {
		set gv_invoice($r,$c) "X" 
	    }
	    sumTotal
	    return
	}
	if [ validButtonClick ] {
	    set crow $r; set ccol $c; $tw setcell $crow $ccol 
	    ::set fx:status_variable "Ready to process estimate"
	} else {
	    ::set fx:status_variable "Error in current cell."
	}
    }
    method validButtonClick {} {
	global gv_invoice fx:status_variable
	set total $gv_invoice($crow,9)
	set curval [$tw getcellval]
	set oldval $gv_invoice($crow,$ccol)
	if { $crow == $rowCount } {
	    if { $gv_invoice($crow,3) == "" && \
		    $gv_invoice($crow,4) == "" && \
		    $gv_invoice($crow,5) == "" && \
		    $gv_invoice($crow,6) == "" && \
		    $gv_invoice($crow,7) == "" && \
		    $curval == "" } {
		return 1
	    } else {
		return 0
	    }
	}
	if { $oldval == $curval } { return 1 }
	if { $ccol == 3 } {  
	    if {[catch "expr int($curval)"] == 0} {
		recomputeLine $curval $gv_invoice($crow,6) $gv_invoice($crow,7)
		return 1
	    } else { 
		return 0 
	    }
	}
	if { $ccol == 4 } { $tw reread ; return 1 }
	if { $ccol == 5 } { $tw reread ; return 1 }
	if { $ccol == 6 } {
	    if [isnumber $curval] {
		set qty $gv_invoice($crow,3)
		recomputeLine $gv_invoice($crow,3) $curval $gv_invoice($crow,7)
		return 1
	    } else {
		return 0
	    }
	}
	if { $ccol == 7 } {
	    if [isnumber $curval] {
		recomputeLine $gv_invoice($crow,3) $gv_invoice($crow,6) $curval 
		::set fx:status_variable "Ready to process estimate"
		return 1
	    } else {
		::set fx:status_variable "Discount Rate entered is invalid."
		return 0
	    }
	}
	if [isnumber $total] {
	    return 1
	} else {
	    return 0
	}
    }
    #
    # if no change, just move the cursor to right one cell
    # else, based on column do appropriate updates
    #
    method bRight {} {
	global gv_invoice fx:status_variable
	set curval [$tw getcellval]
	set oldval $gv_invoice($crow,$ccol)
	if { $oldval == $curval } {
	    moveRight
	    return
	}
	if { $ccol == 3 } {  
	    if {[catch "expr int($curval)"] == 0} {
		FindPartStock
		moveRight
		::set fx:status_variable "Ready to process estimate"
	    } else {
		::set fx:status_variable "Quantity must be a valid number."
	    }
	    return
	}
	if { $ccol == 4 } { 
	    blt_busy hold .
	    FindPart [$tw getcellval] PartNumber
	    moveRight
	    blt_busy release .
	    return
	}
	if { $ccol == 5 } {
	    blt_busy hold .
	    FindPart [$tw getcellval] Description
	    moveRight
	    blt_busy release .
	    return
	}
	if { $ccol == 6 } {
	    if [isnumber $curval] {
		recomputeLine $gv_invoice($crow,3)  $curval $gv_invoice($crow,7)
		moveRight
		::set fx:status_variable "Ready to process estimate"
	    } else {
		::set fx:status_variable "Price entered is invalid."
	    }
	    return
	}
	if { $ccol == 7 } {
	    if [isnumber $curval] {
		recomputeLine $gv_invoice($crow,3)  $gv_invoice($crow,6) $curval
		moveRight
		::set fx:status_variable "Ready to process estimate"
	    } else {
		::set fx:status_variable "Discount Rate entered is invalid."
	    }
	    return
	}
    }
    #
    # if no change, just move the cursor to left one cell
    # else, based on column do appropriate updates
    #
    method bLeft {} {
	global gv_invoice fx:status_variable
	set curval [$tw getcellval]
	set oldval $gv_invoice($crow,$ccol)
	if { $oldval == $curval } {
	    moveLeft
	    return
	}
	if { $ccol == 3 } {  
	    if {[catch "expr int($curval)"] == 0} {
		FindPartStock
		moveLeft
		::set fx:status_variable "Ready to process estimate"
	    } else {
		::set fx:status_variable "Quantity must be a valid number."
	    }
	    return
	}
	if { $ccol == 4 } {
	    blt_busy hold .
	    FindPart [$tw getcellval] PartNumber
	    moveLeft
	    blt_busy release .
	    return
	}
	if { $ccol == 5 } {
	    blt_busy hold .
	    FindPart [$tw getcellval] PartNumber
	    moveLeft
	    blt_busy release .
	    return
	}
	if { $ccol == 6 } {
	    if [isnumber $curval] {
		recomputeLine $gv_invoice($crow,3) $curval $gv_invoice($crow,7)
		moveLeft
		::set fx:status_variable "Ready to process estimate"
	    } else {
		::set fx:status_variable "Price entered is invalid."
	    }
	    return
	}
	if { $ccol == 7 } {
	    if [isnumber $curval] {
		recomputeLine $gv_invoice($crow,3) $gv_invoice($crow,6) $curval
		moveLeft
		::set fx:status_variable "Ready to process estimate"
	    } else {
		::set fx:status_variable "Discount Rate entered is invalid."
	    }
	    return
	}
    }
    method validRowGoDown {} {
	global gv_invoice fx:status_variable
	set total $gv_invoice($crow,9)
	if [isnumber $total] {
	    moveDown
	    ::set fx:status_variable "Ready to process estimate."
	    return 1
	} else {
	    ::set fx:status_variable "Estimate item is incomplete."
	    return 0
	}
    }
    method validRowGoUp {} {
	global gv_invoice fx:status_variable
	set total $gv_invoice($crow,9)
	set curval [$tw getcellval]
	if { $crow == $rowCount } {
	    if { $gv_invoice($crow,3) == "" && \
		    $gv_invoice($crow,4) == "" && \
		    $gv_invoice($crow,5) == "" && \
		    $curval == "" } {
		moveUp
		::set fx:status_variable "Ready to process estimate."
		return 1
	    } else {
		::set fx:status_variable "Estimate item is incomplete."
		return 0
	    }
	}
	if [isnumber $total] {
	    moveUp
	    ::set fx:status_variable "Ready to process estimate."
	    return 1
	} else {
	    ::set fx:status_variable "Estimate item is incomplete."
	    return 0
	}
    }
    #
    # if value in total, move down a row.  If last
    # row in table, add a row to bottom for continuous inputting of
    # estimate items.
    #
    method bDown {} {
	global gv_invoice fx:status_variable
	set curval [$tw getcellval]
	set oldval $gv_invoice($crow,$ccol)
	if { $oldval == $curval } {
	    validRowGoDown
	    return
	}		
	if { $ccol == 3 } {  
	    if {[catch "expr int($curval)"] == 0} {
		FindPartStock
		validRowGoDown
		::set fx:status_variable "Ready to process estimate."
	    } else {
		::set fx:status_variable "Quantity must be a valid number."
	    }
	    return
	}
	if { $ccol == 4 } { 
	    blt_busy hold .
	    FindPart [$tw getcellval] PartNumber
	    validRowGoDown
	    blt_busy release .
	    return 
	}
	if { $ccol == 5 } {
	    blt_busy hold .
	    FindPart [$tw getcellval] Description
	    validRowGoDown
	    blt_busy release .
	    return 
	}
	if { $ccol == 6 } {
	    if [isnumber $curval] {
		recomputeLine $gv_invoice($crow,3) $curval $gv_invoice($crow,7)
		validRowGoDown
		::set fx:status_variable "Ready to process estimate"
	    } else {
		::set fx:status_variable "Price entered is invalid."
	    }
	    return
	}
	if { $ccol == 7 } {
	    if [isnumber $curval] {
		recomputeLine $gv_invoice($crow,3) $gv_invoice($crow,6) $curval
		validRowGoDown
		::set fx:status_variable "Ready to process estimate"
	    } else {
		::set fx:status_variable "Discount Rate entered is invalid."
	    }
	    return
	}
    }
    method bUp {} {
	global gv_invoice fx:status_variable
	set curval [$tw getcellval]
	set oldval $gv_invoice($crow,$ccol)
	if { $oldval == $curval } {
	    validRowGoUp
	    return
	}		
	if { $ccol == 3 } {  
	    if {[catch "expr int($curval)"] == 0} {
		FindPartStock
		validRowGoUp
		::set fx:status_variable "Ready to process estimate"
	    } else {
		::set fx:status_variable "Quantity must be a valid number."
	    }
	    return
	}
	if { $ccol == 4 } {
	    blt_busy hold .
	    FindPart [$tw getcellval] PartNumber
	    validRowGoUp
	    blt_busy release .
	    return 
	}
	if { $ccol == 5 } {
	    blt_busy hold .
	    FindPart [$tw getcellval] Description
	    validRowGoUp
	    blt_busy release .
	    return 
	}
	if { $ccol == 6 } {
	    if [isnumber $curval] {
		recomputeLine $gv_invoice($crow,3) $curval $gv_invoice($crow,7)
		validRowGoUp
		::set fx:status_variable "Ready to process estimate."
	    } else {
		::set fx:status_variable "Price entered is invalid."
	    }
	    return
	}
	if { $ccol == 7 } {
	    if [isnumber $curval] {
		recomputeLine $gv_invoice($crow,3) $gv_invoice($crow,6) $curval 
		validRowGoUp
		::set fx:status_variable "Ready to process estimate"
	    } else {
		::set fx:status_variable "Discount Rate entered is invalid."
	    }
	    return
	}
    }

    #
    # FindPartStock is called when Qty field is modified; 
    # this function finds the part# (if one is populated) and
    # uses the QtyOnHand attr to populate the B/O and Ship fields
    # with appropriate numbers.  If QtyOnHand is not a number(i.e. blank)
    # then QtyOnHand is set to zero.  Note, if more than one tuple is
    # found from search, then part# was not unique, hence set 
    # qtyonhand to zero.
    #
    method FindPartStock {} {
	global gv_invoice 
	set qty [$tw getcellval]
	set part $gv_invoice($crow,4)
	set search_attr PartNumber
	set print_attrs {QtyOnHand}

	if {[catch "qddb_schema open Inventory" s] != 0} {
	    puts "Cannot open Inventory Schema"; exit 1
	}
	set k [Fx_QddbSearchParser :: MultiSearch $s $part on $search_attr]
	if { $k == "" } {
	    set gv_invoice($crow,1) $qty
	    set gv_invoice($crow,2) 0
	    return
	}
	set k1 [qddb_keylist get $k]
	set t [isUniqueTuple $k1 $s]
	set last_search [qddb_rows select -attrs $search_attr \
		-print QtyOnHand $k]
	set myrow [lindex [lindex $last_search 0] 1]
	set res  [qddb_rows getval QtyOnHand $myrow]
        FindPartStockSearchResultsProc 0
	if { $t != "" } {
	    qddb_keylist delete $k
	    qddb_keylist delete $k1
	    qddb_tuple delete $t
	}
	qddb_schema close $s
	update idletasks 
	update
    }
    method FindPartStockSearchResultsProc { i } {
	global gv_invoice
	set i [lindex $res $i]
	#
	# if already a qty, don't overwrite it
	#
	set qoh [lindex $i 0]
	if ![isnumber $qoh] { set qoh 0 }
	set qty [$tw getcellval]
	if { $qoh > $qty } {
	    set gv_invoice($crow,2) $qty
	    set gv_invoice($crow,1) 0
	} else {
	    set gv_invoice($crow,2) $qoh
	    set gv_invoice($crow,1) [expr $qty-$qoh]
	}
	set gv_invoice($crow,3) $qty
	recomputeLine $gv_invoice($crow,3) $gv_invoice($crow,6) \
		$gv_invoice($crow,7)
	focus $tw
    }
    method FindPart { part searchattr } {
	global gv_myattr search_attrs fx:status_variable
	if {[winfo exists .search_results]} {
	    catch "search_results delete"
	    catch "destroy .search_results"
	    update
	}
	update idletasks
	set search_attrs $searchattr
	set print_attrs {Mfr PartNumber Description Price.SuggestedRetail \
		QtyOnHand}
	if {[catch "qddb_schema open Inventory" s] != 0} {
	    puts "Cannot open Inventory Schema"; exit 1
	}
	set k [Fx_QddbSearchParser :: MultiSearch $s $part on $searchattr]
	set klen [qddb_keylist length $k]
	if { $klen == 0 } {
	    ::set fx:status_variable "No matches found."
	    qddb_schema close $s
	    return
	}
	if { $klen > $maxsearch } {
	    tk_dialog .dialog "Search found $klen items." \
		    "Please narrow your search." info 0 OK  
	    ::set fx:status_variable \
		    "Search found $klen items, please narrow your search."
	    qddb_schema close $s
	    return
	}
	set last_search [qddb_rows select -suppress on -attrs $search_attrs \
		-print $search_attrs $k]
	set myrows {}
	foreach i $last_search { lappend myrows [lindex $i 1] }
	set srt [qddb_rows sort -ascending {Mfr PartNumber} {Mfr PartNumber} \
		$myrows] 
	set res {}
	foreach i $srt { lappend res [qddb_rows getval $print_attrs $i] }
	if { [llength $res] == 1 } {
	    FindPartSearchResultsProc 0
	    qddb_keylist delete $k
	    return
	}
	qddb_schema close $s
	toplevel .search_results
	wm title .search_results "Search Results"
	set widths2 { 10 10 30 8 }
	set headings { Mfr Part# Description Price }
	set alignment { left left left right }
	set separators { {} {} {} {} {} {} }
	set f .search_results.f2
	frame $f
	pack $f -side bottom -fill x
	button $f.select -text Close\
		-command {search_results OnSelect ; search_results delete ; \
		destroy .search_results}
	pack $f.select -side left -padx 10m -pady 2m -ipadx 10m -ipady 2m
	Fx_MultiColumnListBox search_results -w .search_results.f \
		-widths $widths2 \
		-headings $headings -numcols [llength $headings] \
		-align $alignment \
		-exportselection off \
		-separators $separators \
		-single_select on \
		-height 10 -onselect [list $this FindPartSearchResultsProc]
	search_results AppendRows $res
	search_results SortContents {Mfr PartNumber}
	search_results Format
	search_results Display
	tkwait window .search_results.f
	update idletasks                                    
    }
    method FindPartSearchResultsProc { i } {
	global gv_invoice gv_myattr
	set i [lindex $res $i]
	if ![isnumber $gv_invoice($crow,1)] {
	    set gv_invoice($crow,1) 1
	}
	#
	# if already a qty, don't overwrite it
	#
	set qoh [lindex $i 4]
	if ![isnumber $gv_invoice($crow,3)] { set gv_invoice($crow,3) 1	}
	if ![isnumber $qoh] { set qoh 0 }
	set qty $gv_invoice($crow,3)
	if { $qoh > $gv_invoice($crow,3) } {
	    set gv_invoice($crow,2) $qty
	    set gv_invoice($crow,1) 0
	} else {
	    set gv_invoice($crow,2) $qoh
	    set gv_invoice($crow,1) [expr $qty-$qoh]
	}
	set gv_invoice($crow,4) [lindex $i 1]
	set gv_invoice($crow,5) "[lindex $i 0] [lindex $i 2]"
	set gv_invoice($crow,6) [lindex $i 3]
	if {$gv_myattr(Reseller) == ""} {
	    set gv_invoice($crow,8) "X"
	} else {
	    set gv_invoice($crow,8) ""
	}
	recomputeLine $gv_invoice($crow,3) $gv_invoice($crow,6) \
		$gv_invoice($crow,7)
	focus $tw
    }
    method bindings {} {
	bind $tw <Any-Button>      "$this bButton %x %y; break"
	bind $tw <Right>           "$this bRight; break"
	bind $tw <Tab>             "$this bRight; break"
	bind $tw <Left>            "$this bLeft; break"
	bind $tw <Shift-Tab>       "$this bLeft; break"
	bind $tw <Up>              "$this bUp; break"
	bind $tw <Control-p>       "$this bUp; break"
	bind $tw <Down>            "$this bDown; break"
	bind $tw <Control-n>       "$this bDown; break"
	bind $tw <KeyPress-Return> "$this bDown; break"
	bind $tw <Control-d> { 
	    %W delete insert
	    break 
	}
	bind $tw <Control-k> { 
	    set p [ %W icursor ]
	    %W delete insert end 
	    %W icursor $p
	    break
	}
	bind $tw <Control-b> { 
	    set p [ %W icursor ]
	    if { $p != 0 } { %W icursor [ expr $p - 1 ] }
	    break
	}
	bind $tw <Control-f> {
	    set p [ %W icursor ]
	    set len [string length [%W getcellval]]
	    if { $p < $len } { %W icursor [ expr $p + 1 ] }
	    break
	}
	bind $tw <Control-a> { %W icursor 0 ; break}
	bind $tw <Control-e> { %W icursor end ; break}
    }
    method doNothingKey {k} {}
    method doNothingMouse {} {}
    method disableBindings {} {
	bind $tw <Any-Button> "$this doNothingMouse; break"
    }
    destructor { catch "destroy .invoice_out" }
    method addRow {} {
	incr rowCount
	set crow $rowCount
	set ccol 3
	$tw setcell $crow $ccol 
	sumTotal
    }
    #
    # Add an estimate item via DragDrop.
    #
    method dragDropRow {} {
	global DragDrop gv_invoice gv_myattr fx:mode_variable
	if { ${fx:mode_variable} != "Process Estimate Mode" } {
	    tk_dialog .dialog "Info" \
		    "Must be in Process Estimate Mode to add estimate item" \
		    info 0 Ok 
	    return 0
	}
	set qoh [lindex $DragDrop(inventory) 3]
	if { $qoh == 0 || $qoh == "" } {
	    set gv_invoice($rowCount,1) 1
	    set gv_invoice($rowCount,2) 0
	} else {
	    set gv_invoice($rowCount,1) 0
	    set gv_invoice($rowCount,2) 1
	}
	set gv_invoice($rowCount,3) 1
	set gv_invoice($rowCount,4) [lindex $DragDrop(inventory) 0]
	set gv_invoice($rowCount,5) [lindex $DragDrop(inventory) 1]
	set gv_invoice($rowCount,6) [lindex $DragDrop(inventory) 2]
	if {$gv_myattr(Reseller) == ""} {
	    set gv_invoice($rowCount,8) "X"
	} else {
	    set gv_invoice($rowCount,8) ""
	}
	set gv_invoice($rowCount,9) [lindex $DragDrop(inventory) 2]
	addRow
    }
    #
    # Add an invoice item. 
    # 
    method insertRow {r} {
	global gv_invoice
	::set gv_invoice($rowCount,1) [lindex $r 0]             ;# BackOrder
	::set gv_invoice($rowCount,2) [lindex $r 1]             ;# Ship
	::set gv_invoice($rowCount,3) [lindex $r 2]             ;# qty
	::set gv_invoice($rowCount,4) [lindex $r 3]             ;# part
	::set gv_invoice($rowCount,5) [lindex $r 4]             ;# description
	::set gv_invoice($rowCount,6) [format "%8.2f " [lindex $r 5]] ;# price
	::set gv_invoice($rowCount,7) [lindex $r 6]             ;# DiscountRate
	::set gv_invoice($rowCount,8) [lindex $r 7]             ;# tax
	::set gv_invoice($rowCount,9) [format "%8.2f " [lindex $r 8]] ;# total
	incr rowCount
	set crow $rowCount
        set ccol 3
	$tw setcell $crow $ccol 
	update
    }
    #
    # Delete an estimate item, by moving up, one by one, the rows
    # beneath the currently selected cell.  This function is
    # triggered from the Estimate/Delete item menubutton
    # Note, if current row is the insertion row, then just
    # clear the cells and set the selection to first column.
    #
    method deleteRow {} {
	global gv_invoice
	set cell [$tw setcell]
	set r [lindex $cell 0]
	set c [lindex $cell 1]

	if { $r != $rowCount } {
	    for { set i $r } { $i < $rowCount } { incr i } {
		for { set j 1 } { $j < $maxcols } { incr j } {
		    set gv_invoice($r,$j) $gv_invoice([expr $r+1],$j)
		}
	    }
	    incr rowCount -1
	}
	for { set j 1 } { $j < $maxcols } { incr j } {
	    set gv_invoice([expr $rowCount],$j) ""
	}
	set crow $rowCount
	set ccol 3
	$tw setcell $crow $ccol
	sumTotal
    }
    #
    # Each time a qty, price or discount cell is modified, then 
    # recomputeLine is invoked to compute the item's total
    #
    method recomputeLine { q p d } {
	global gv_invoice
	if ![isnumber $d] { 
	    set dr 0 
	} else {
	    set dr [expr $d * .01]
	}
	if { $q != "" && [isnumber $p] } {
	    set total [expr $q * $p * (1 - $dr)]
	    set gv_invoice($crow,6) [format "%8.2f" $p]
	    set gv_invoice($crow,9) [format "%8.2f" $total]
	    sumTotal
	}
    }
    #
    # sumTotal simply adds the total for each estimate
    # item and populates the totals attrs.
    # 
    method sumTotal {} {
	global gv_myattr gv_afterDiscount total totalTax discount \
		afterdiscount subtax
	global grandTotal subTax gv_invoice gv_pmt gv_pmtamt

	set total 0
	set totalTax 0
	# hack for when no items in invoice
	if [info exists gv_invoice(1,9)] {
	    for {set i 1} {$i <= $rowCount} {incr i} {
		# necessary when first adding row and total not entered yet.
		if { $gv_invoice($i,9) != "" } {
		    set total [expr $total+$gv_invoice($i,9)]
		}
		if { $gv_invoice($i,8) == "X" } {
		    set totalTax [expr $totalTax+$gv_invoice($i,9)]
		}
	    }
	}
	set total [currency_format $total ROUND]
	set totalTax [currency_format [expr $totalTax * $taxRate] ROUNDUP]
	set subtax [expr $total + $totalTax]
	set grandTotal [expr $subtax + $gv_myattr(Freight)]
	::set gv_myattr(Subtotal) [format "%10.2f" $total]
	::set gv_myattr(Tax) [format "%10.2f" $totalTax]
	::set gv_myattr(AfterTax) [format "%10.2f" $subtax]
	::set gv_myattr(TotalCharge) [format "%10.2f" $grandTotal]
	if { $gv_pmt == "Full" } {
	    ::set gv_pmtamt [format "%10.2f" $gv_myattr(TotalCharge)]
	}
    }
    method clearInvoice {} {
	global gv_invoice
	for {set i 1} { $i < 100 } { incr i } {
	    for {set j 1} { $j < 10 } { incr j } {
		::set gv_invoice($i,$j) ""
	    }
	}
	set crow 1
	set ccol 3
	$tw setcell $crow $ccol
	set rowCount 1
    }
    method applyResellerTax {} {
	global gv_myattr gv_invoice fx:mode_variable
	if {${fx:mode_variable} == "Search Mode"} {
	    return
	}
	update
	if {$gv_myattr(Reseller) == ""} { set tax 1 } { set tax 0 }
	for {set i 1} { $i < $rowCount } { incr i } {
	    if { $tax } { set gv_invoice($i,8) "X" } { set gv_invoice($i,8) "" }
	}
	if [isnumber $gv_invoice($rowCount,9)] {
	    if { $tax } { set gv_invoice($i,8) "X" } { set gv_invoice($i,8) "" }
	}
	sumTotal
	update
    }
    method get_rowCount {} { return $rowCount }
    method set_rowCount {val} { set rowCount $val }
    method qtyChange {} {}

    common columns 9
    protected tw
    protected res
    protected rowCount 1
    protected crow 1
    protected ccol 3
    protected X ""
    protected Y ""
    public maxcols 10 {}
    public maxrows 100 {}
    public maxsearch 500
    public taxRate 0.07
}


#
# Invoice Class --
#     This class manages the Invoice window, including interaction
#     with the Invoice_Out tkTable class
#
itcl_class Invoice {
    constructor {config} {
	global gv_invoice fx_config_dir s myarr gv_address_button gv_auto_print
	wm title . "Estimate"
	wm withdraw .
	if {[catch "qddb_schema open Estimates" s] != 0} {
	    puts "Cannot open Schema for Estimates. Please contact someone."; exit 1
	}
	set fx_config_dir .invoice_config
	Fx:Init $s
	frame .mb
	pack .mb -side top -expand on -fill x
	menubutton .mb.invoice -text "Estimate" -underline 0 -menu .mb.invoice.m
	menu .mb.invoice.m -tearoff 0
	.mb.invoice.m add command -label "New" -command [list $this newInvoice]
	.mb.invoice.m add sep
	.mb.invoice.m add command -label "Process" -command [list $this process]
	.mb.invoice.m add command -label "Clear items" -command [list $this clear]
	.mb.invoice.m add sep
 	.mb.invoice.m add command -label "Delete item" \
		-command [list $this deleteItem]
	.mb.invoice.m add sep
	.mb.invoice.m add command -label "Print..."  -command [list $this print]
	.mb.invoice.m add sep
	.mb.invoice.m add checkbutton -variable gv_auto_print \
		-label "Auto-print estimate"
	Fx_Menubar menubar -w .mb -schema $s -array gv_myattr \
		-auxbuttons .mb.invoice \
		-config_dir .invoice_config \
		-force_readonly_mode 1 \
		-beforechangemode [list invoice_out clearInvoice ] \
		-beforesearchmode [list $this clear ] \
		-afteraddmode     [list $this afterAddMode] \
		-afterchangemode  [list $this fillTable ] \
		-aftersearchmode  [list $this afterSearchMode ] \
		-afterreadonlymode [list $this afterReadOnlyMode ] \
		-afterpost_modes  [list $this afterpost_modes] \
		-afterpost_file   [list $this afterpost_file] \
		-restrict [glob ~]/.qba_config/Invoices

	#
	# We don't need the "Save" button, so remove it.
	#
	.mb.file.menu delete 0 0
	.mb.modes.menu entryconfigure 1 -label "Process Estimate Mode"
	.mb.modes.menu delete 2 3
	set gv_search_entry [menubar SearchForEntry]
	frame .f1 -relief groove -bd 4
	frame .f1.id -relief groove -bd 2
	frame .f1.name -relief groove -bd 2
	frame .f1.name.a
	frame .f1.name.b
	frame .f1.name.e
	frame .f1.name.c -relief groove -bd 4
	frame .f1.name.c.b -relief groove -bd 2
	frame .f1.name.c.b.f -relief groove -bd 0
	frame .f1.name.c.s -relief groove -bd 2
	frame .f1.name.c.s.f -relief groove -bd 0
	Fx_Entry invoice   -w .f1.id.invoice -attr InvoiceNumber \
	    -side left -mandatory 1 -searchfor_entry $gv_search_entry \
	    -setschema $s \
	    -restrict [glob ~]/.qba_config/Estimates
	Fx_Entry doi       -w .f1.id.doi -attr DateOfInvoice -side left \
		-mandatory 1
	Fx_Entry salesrep  -w .f1.id.salesrep  -attr SalesRep
	Fx_Entry company   -w .f1.name.a.company -attr Company        
	Fx_Entry first     -w .f1.name.b.first   -attr Name.First     
	Fx_Entry last      -w .f1.name.b.last    -attr Name.Last      
	Fx_Entry id        -w .f1.name.b.id      -attr Name.ID        
	Fx_Entry desc      -w .f1.name.e.desc    -attr Phone.Desc
	Fx_Entry area      -w .f1.name.e.area    -attr Phone.Area
	Fx_Entry number    -w .f1.name.e.number  -attr Phone.Number
	label .f1.name.c.b.f.l -text "Bill to:" -fg blue -anchor w
	button .f1.name.c.b.f.b0 -text "Addresses" -fg blue \
		-command [list $this viewAddresses billing]
	button .f1.name.c.b.f.b1 -text "<<" -fg blue \
		-command [list $this CopyShipToBill]
	button .f1.name.c.b.f.b2 -text ">>" -fg blue \
		-command [list $this CopyBillToShip]
	Fx_Entry bstreet    -w .f1.name.c.b.street  -attr AddressBill.Street 
	Fx_Entry bcity      -w .f1.name.c.b.city    -attr AddressBill.City   
	Fx_Entry bstate     -w .f1.name.c.b.state   -attr AddressBill.State  
	Fx_Entry bzip       -w .f1.name.c.b.zip     -attr AddressBill.Zip    
	label .f1.name.c.s.f.l -text "Ship to:" -fg blue -anchor w
	button .f1.name.c.s.f.b0 -text "Addresses" -fg blue \
		-command [list $this viewAddresses shipping]
	Fx_Entry sstreet    -w .f1.name.c.s.street  -attr AddressShip.Street 
	Fx_Entry scity      -w .f1.name.c.s.city    -attr AddressShip.City   
	Fx_Entry sstate     -w .f1.name.c.s.state   -attr AddressShip.State  
	Fx_Entry szip       -w .f1.name.c.s.zip     -attr AddressShip.Zip    
	pack forget .f1.id.invoice .f1.id.doi .f1.id.salesrep \
		.f1.name.a.company \
	    .f1.name.b.first .f1.name.b.last .f1.name.b.id \
	    .f1.name.e.desc .f1.name.e.area .f1.name.e.number \
	    .f1.name.c.b.street .f1.name.c.b.city .f1.name.c.b.state \
	    .f1.name.c.b.zip \
	    .f1.name.c.s.street .f1.name.c.s.city .f1.name.c.s.state \
	    .f1.name.c.s.zip
	pack .f1            -side top -fill x -expand yes
	pack .f1.id         -side top -fill x -expand yes
	pack .f1.name       -side top -fill x -expand yes
	pack .f1.name.a     -side top -fill x -expand yes
	pack .f1.name.b     -side top -fill x -expand yes
	pack .f1.name.e     -side top -fill x -expand yes
	pack .f1.name.e.desc .f1.name.e.area .f1.name.e.number -side left
	pack .f1.name.c     -side top -fill x -expand yes
	pack .f1.name.c.b .f1.name.c.s -side left -fill x -expand yes
	pack .f1.name.c.b.f   -side top -fill x -expand yes
	pack .f1.name.c.b.f.l -side left -fill x
	pack .f1.name.c.b.f.b2 -side right
	pack .f1.name.c.b.f.b1 -side right
	pack .f1.name.c.b.f.b0 -side right
	pack .f1.name.c.s.f -side top -fill x -expand yes
	pack .f1.name.c.s.f.l -side left -fill x
	pack .f1.name.c.s.f.b0 -side right
	pack .f1.id.invoice -side left -fill x -expand yes
	pack .f1.id.doi     -side left
	pack .f1.id.salesrep -side left -fill x -expand yes
	pack .f1.name.a.company -side top -fill x -expand yes
	pack .f1.name.b.first -side left
	pack .f1.name.b.id -side right
	pack .f1.name.b.last -side left -fill x -expand yes
	pack .f1.name.c.b.street -side top -fill x -expand yes
	pack .f1.name.c.b.city -side left -fill x -expand yes
	pack .f1.name.c.b.state -side left
	pack .f1.name.c.b.zip -side left
	pack .f1.name.c.s.street -side top -fill x -expand yes
	pack .f1.name.c.s.city -side left -fill x -expand yes
	pack .f1.name.c.s.state -side left
	pack .f1.name.c.s.zip -side left
	.f1.id.invoice.f_0.l      configure -text "Estimate(Number/Date):" \
		-width $lwidth
	.f1.id.doi.f_0.l          configure -bitmap @$bitmapdir/bluedot.xpm
	.f1.id.salesrep.f_0.l     configure -text "Sales Rep:" -width 10
	.f1.name.a.company.f_0.l  configure -text "Company:" -width $lwidth
	.f1.name.b.first.f_0.l    configure -text "First/Last/ID:" -width $lwidth
	.f1.name.b.last.f_0.l     configure -bitmap @$bitmapdir/bluedot.xpm
	.f1.name.b.id.f_0.l       configure -bitmap @$bitmapdir/bluedot.xpm
	.f1.name.e.desc.f_0.l     configure -text "Phone(Desc/Area/Number):" \
		-width $lwidth
	.f1.name.e.area.f_0.l     configure -bitmap @$bitmapdir/bluedot.xpm
	.f1.name.e.number.f_0.l   configure -bitmap @$bitmapdir/bluedot.xpm
	.f1.name.c.b.street.f_0.l   configure -text "" 
	.f1.name.c.b.city.f_0.l     configure -text "" 
	.f1.name.c.b.state.f_0.l    configure -bitmap @$bitmapdir/bluedot.xpm
	.f1.name.c.b.zip.f_0.l      configure -bitmap @$bitmapdir/bluedot.xpm
	.f1.name.c.s.street.f_0.l   configure -text "" 
	.f1.name.c.s.city.f_0.l     configure -text "" 
	.f1.name.c.s.state.f_0.l    configure -bitmap @$bitmapdir/bluedot.xpm
	.f1.name.c.s.zip.f_0.l      configure -bitmap @$bitmapdir/bluedot.xpm
	.f1.id.invoice.e configure -width 10
	.f1.id.doi.e configure -width 10
	.f1.name.b.id.e configure -width 8
	.f1.name.e.desc.e configure -width 20
	.f1.name.e.area.e configure -width 6
	.f1.name.e.number.e configure -width 10
	.f1.name.c.b.state.e configure -width 2
	.f1.name.c.b.zip.e configure -width 10
	.f1.name.c.s.state.e configure -width 2
	.f1.name.c.s.zip.e configure -width 10

	#
	# Create the Estimate spreadsheet
	# The estimate table contains the items on the estimate. Note, the
	# dummy variables are necessary so that the variables can be
	# set up for the view --- used later in fillTable.
	# -read_only is used to avoid tabbing into twilight zone.
	#
	InvoiceOut invoice_out
	Fx_Entry d1 -w .d1  -attr Items.Qty          -read_only 1
	Fx_Entry d2 -w .d2  -attr Items.Part         -read_only 1
	Fx_Entry d3 -w .d3  -attr Items.Desc         -read_only 1
	Fx_Entry d4 -w .d4  -attr Items.UnitPrice    -read_only 1
	Fx_Entry d5 -w .d5  -attr Items.Tax          -read_only 1
	Fx_Entry d6 -w .d6  -attr Items.Total        -read_only 1
	Fx_Entry d7 -w .d7  -attr Items.BackOrder    -read_only 1
	Fx_Entry d8 -w .d8  -attr Items.Ship         -read_only 1
	Fx_Entry d9 -w .d9  -attr Items.DiscountRate -read_only 1
	for {set i 1} {$i<10} {incr i} {pack forget .d$i}

	frame .f4 -bd 4 -relief groove
	frame .f4.totals
	frame .f4.ccinfo
	frame .f4.ccinfo.res
	frame .f4.ccinfo.mop
	frame .f4.ccinfo.cc
	frame .f4.ccinfo.dummy -bg black -relief groove -bd 6
	frame .f4.ccinfo.pmt
	Fx_Entry reseller  -w .f4.ccinfo.res.reseller  -attr Reseller
	Fx_Entry po        -w .f4.ccinfo.res.po        -attr PurchaseOrder
	Fx_Entry mop -w .f4.ccinfo.mop.mop -attr MethodOfPayment \
		-type Radiobutton \
	    -default_values "Visa Mastercard Check Cash Net10 Net30"
	Fx_Entry ccard     -w .f4.ccinfo.cc.ccard      -attr CreditCard
	Fx_Entry dateofexp -w .f4.ccinfo.cc.dateofexp  -attr DateOfExp
	label .f4.ccinfo.pmt.l    -text "Payment:" -width 30 -anchor e -fg blue
	radiobutton .f4.ccinfo.pmt.full -text "Full"    -variable gv_pmt \
		-value "Full" -command [list $this paymentClick]
	radiobutton .f4.ccinfo.pmt.part -text "Partial" -variable gv_pmt \
		-value "Partial" -command [list $this paymentClick]
	radiobutton .f4.ccinfo.pmt.none -text "None" -variable gv_pmt \
		-value "None" -command [list $this paymentClick]
	entry .f4.ccinfo.pmt.e -relief sunken -width 10 -textvariable gv_pmtamt\
		-state disabled
	Fx_Entry subtotal  -w .f4.totals.subtotal -attr Subtotal -read_only 1
	Fx_Entry tax       -w .f4.totals.tax -attr Tax -read_only 1
	Fx_Entry aftertax  -w .f4.totals.aftertax -attr AfterTax -read_only 1 
	Fx_Entry freight   -w .f4.totals.freight -attr Freight     
	Fx_Entry total     -w .f4.totals.total -attr TotalCharge -read_only 1
	#
	# The payment entry is controlled independently of Fx.  That is, it uses
	# Fx only to store and retrieve the values through gv_myattr.   This was
	# done to simplify adding Radio buttons to toggle the values.
	#
	Fx_Entry payment -w .f4.totals.pmt  -attr Payment -read_only 1
	pack forget .f4.ccinfo.cc.ccard .f4.ccinfo.cc.dateofexp \
		.f4.ccinfo.res.reseller \
		.f4.ccinfo.res.po .f4.ccinfo.mop .f4.totals.subtotal  \
		.f4.totals.aftertax .f4.totals.tax .f4.totals.freight \
		.f4.totals.total .f4.totals.pmt
	pack .f4 -side top -fill x -expand yes
	pack .f4.ccinfo -side left -fill both -expand yes
	pack .f4.totals -side right 
	pack .f4.ccinfo.res -side top -anchor w
	pack .f4.ccinfo.res.reseller  -side left
	pack .f4.ccinfo.res.po        -side left
	pack .f4.ccinfo.mop -side top -anchor w
	pack .f4.ccinfo.mop.mop       -side top
	pack .f4.ccinfo.cc  -side top -anchor w
	pack .f4.ccinfo.cc.ccard .f4.ccinfo.cc.dateofexp -side left
	pack .f4.ccinfo.dummy -side top -fill x -expand yes
	pack .f4.ccinfo.pmt -side top -fill x -expand yes -anchor w
	pack .f4.ccinfo.pmt.l .f4.ccinfo.pmt.full .f4.ccinfo.pmt.part \
		.f4.ccinfo.pmt.none \
		-side left -anchor w -fill x -expand yes
	pack .f4.ccinfo.pmt.e -side left -padx 1.0m
	pack .f4.totals.subtotal -side top -fill x -expand yes
	pack .f4.totals.tax -side top -fill x -expand yes
	pack .f4.totals.aftertax       -side top -fill x -expand yes
	pack .f4.totals.freight -side top -fill x -expand yes
	pack .f4.totals.total -side top -fill x -expand yes
	.f4.ccinfo.res.reseller.f_0.l configure -text "Reseller:" -width 15
	.f4.ccinfo.res.po.f_0.l      configure -text "Purchase Order:" -width 20
	.f4.ccinfo.res.po.e          configure -width 10
	.f4.ccinfo.mop.mop.f_0.l     configure -text "Payment Method:" -width 15
	.f4.ccinfo.cc.ccard.f_0.l      configure -text "Credit Card:" -width 15
	.f4.ccinfo.cc.ccard.e          configure -width 20
	.f4.ccinfo.cc.dateofexp.f_0.l configure -text "DOE:"         -width 5
	.f4.totals.subtotal.f_0.l      configure -text "Subtotal:"    -width 12
	.f4.totals.subtotal.e          configure -width 10
	.f4.totals.tax.f_0.l           configure -text "Tax:"         -width 12
	.f4.totals.tax.e               configure -width 10
	.f4.totals.aftertax.f_0.l      configure -text "After Tax:"   -width 12
	.f4.totals.aftertax.e          configure -width 10
	.f4.totals.freight.f_0.l       configure -text "Freight:"     -width 12
	.f4.totals.freight.e           configure -width 10
	.f4.totals.total.f_0.l         configure -text "** Total:"    -width 12
	.f4.totals.total.e             configure -width 10

	frame .f3 -relief groove -bd 4
	Fx_Entry comments  -w .f3.comments -attr Comments -type Text \
		-height 3 -side top
	pack .f3 -side top -fill x -expand yes

	# Set the drop area to the entire window; makes it easier for user.
	blt::drag&drop target . handler inventory [list invoice_out dragDropRow]
	blt::drag&drop target . handler customer  [list $this dragDropCustomer]

	# Default the auto_print to true
	::set gv_auto_print 1

	# Restore the window and do init stuff
	menubar configure -instances [Fx_Entry::GetInstances] \
		-frames [Fx_Frame::GetInstances]
	menubar AddModeProc
	newInvoice
	wm deiconify .
	focus .i.tbl
        bindings
	update
    }
    method config {config} {}
    method deleteItem {} { invoice_out deleteRow }
    method newInvoice {} { 
	global fx:current_focus
	::set fx:current_focus [list Company .f1.name.a.company.e]
	menubar AddModeProc
	.f4.ccinfo.pmt.none invoke
	update
    }
    method clear {} { 
	global gv_invoice gv_myattr gv_pmtamt
	invoice_out clearInvoice
	::set gv_myattr(Subtotal) [format "%10.2f" 0]
	::set gv_myattr(Tax) [format "%10.2f" 0]
	::set gv_myattr(AfterTax) [format "%10.2f" 0]
	::set gv_myattr(Freight) [format "%10.2f" 0]
	::set gv_myattr(TotalCharge) [format "%10.2f" 0]
	::set gv_pmtamt [format "%10.2f" 0]
    }
    method CopyShipToBill {} {
	global gv_myattr
	::set gv_myattr(AddressBill.Street) $gv_myattr(AddressShip.Street)
	::set gv_myattr(AddressBill.City) $gv_myattr(AddressShip.City)
	::set gv_myattr(AddressBill.State) $gv_myattr(AddressShip.State)
	::set gv_myattr(AddressBill.Zip) $gv_myattr(AddressShip.Zip)
    }
    method CopyBillToShip {} {
	global gv_myattr
	::set gv_myattr(AddressShip.Street) $gv_myattr(AddressBill.Street)
	::set gv_myattr(AddressShip.City) $gv_myattr(AddressBill.City)
	::set gv_myattr(AddressShip.State) $gv_myattr(AddressBill.State)
	::set gv_myattr(AddressShip.Zip) $gv_myattr(AddressBill.Zip)
    }
    method findAddressSearchResultsProc { i } {
	global gv_myattr gv_address_button
	set i [lindex $res $i]
	if {$gv_address_button == "shipping"} {
	    ::set gv_myattr(AddressShip.Street) [lindex $i 1]
	    ::set gv_myattr(AddressShip.City)   [lindex $i 2]
	    ::set gv_myattr(AddressShip.State)  [lindex $i 3]
	    ::set gv_myattr(AddressShip.Zip)    [lindex $i 4]
	} else {
	    ::set gv_myattr(AddressBill.Street) [lindex $i 1]
	    ::set gv_myattr(AddressBill.City)   [lindex $i 2]
	    ::set gv_myattr(AddressBill.State)  [lindex $i 3]
	    ::set gv_myattr(AddressBill.Zip)    [lindex $i 4]
	}
    }
    method viewAddresses { address } {
	global gv_myattr tmp_carr gv_address_button

	blt_busy hold .
	set gv_address_button $address
	set print_attrs {\
		Company.Address.Desc \
		Company.Address.Street \
		Company.Address.City \
		Company.Address.State \
		Company.Address.Zip \
	    }

	if {[catch "qddb_schema open Clients" c_s] != 0} {
	    puts "Cannot open Inventory Schema"; exit 1
	}
	set c_k [qddb_search $c_s -prunebyattr Company.Contact.ID \
		word $gv_myattr(Name.ID)]
	set c_k1 [qddb_keylist get $c_k]
	set c_t [isUniqueTuple $c_k1 $c_s]
	if { $c_t == "" } {
	    tk_dialog .dialog "Info" "Customer ID not found, or duplicate ID." \
		    info 0 Ok 
	    qddb_keylist delete $c_k
	    qddb_keylist delete $c_k1
	    qddb_schema close $c_s
	    blt_busy release .
	    return
	}
	::set fx:status_variable ""
	update idletasks
	set c_v [qddb_view define $c_t {
	    { Company.Address.Desc       tmp_carr(desc) }
	    { Company.Address.Street     tmp_carr(street) }
	    { Company.Address.City       tmp_carr(city) }
	    { Company.Address.State      tmp_carr(state) }
	    { Company.Address.Zip        tmp_carr(zip) }
	}]
	set num_addresses [qddb_instance maxnum $c_v Company.Address]
        set res {}
        for {set i 1} {$i <= [expr $num_addresses]} { incr i } {
	    qddb_instance switch $c_v Company.Address $i
	    lappend res [list $tmp_carr(desc) $tmp_carr(street) \
		    $tmp_carr(city) $tmp_carr(state) $tmp_carr(zip)]
	}

	toplevel .search_results
	wm title .search_results "Search Results"
	set widths2 { 10 15 15 5 11 }
	set headings { Description Street City State Zip }
	set alignment { left left left left left }
	set separators { {} {} {} {} {} }
	set f .search_results.f2
	pack [frame $f] -side bottom -fill x
	button $f.select -text Close -command {
	    search_results OnSelect 
	    search_results delete
	    destroy .search_results
	}
	pack $f.select -side left -padx 10m -pady 2m -ipadx 10m -ipady 2m
	Fx_MultiColumnListBox search_results -w .search_results.f \
		-widths $widths2 \
		-headings $headings -numcols [llength $headings] \
		-align $alignment \
		-exportselection off \
		-separators $separators \
		-single_select on \
		-height 10 \
		-onselect [list $this findAddressSearchResultsProc]
	search_results AppendRows $res
	search_results SortContents {Company.Address.State \
		Company.Address.City Company.Address.Street}
	search_results Format
	search_results Display
	tkwait window .search_results.f
	update idletasks                                    
	blt_busy release .
    }
    method tryAgain {} {
	global gv_myattr fx:status_variable
	::set gv_myattr(InvoiceNumber) "######"
	::set fx:status_variable "Ready to try again..."
	blt_busy release .
    }
    method process {} { 
	global gv_myattr gv_invoice arr carr parr s c_s gv_estimatenumber
	global fx:status_variable fx:current_focus gv_pmtamt gv_tmp_total
	global ISPUTIL gv_auto_print
	blt_busy hold .
	::set fx:current_focus [list Company .f1.name.a.company.e]
	set t [qddb_tuple new $s]
	set v [qddb_view define $t {
	    { InvoiceNumber        arr(InvoiceNumber) }
	    { DateOfInvoice        arr(DateOfInvoice) }
	    { Clock                arr(Clock) }
	    { Company              arr(Company) }
	    { Name.First           arr(Name.First) }
	    { Name.Last            arr(Name.Last) }
	    { Name.ID              arr(Name.ID) }
	    { Phone.Desc           arr(Phone.Desc) }
	    { Phone.Area           arr(Phone.Area) }
	    { Phone.Number         arr(Phone.Number) }
	    { AddressBill.Street   arr(AddressBill.Street) }
	    { AddressBill.City     arr(AddressBill.City) }
	    { AddressBill.State    arr(AddressBill.State) }
	    { AddressBill.Zip      arr(AddressBill.Zip) }
	    { AddressShip.Street   arr(AddressShip.Street) }
	    { AddressShip.City     arr(AddressShip.City) }
	    { AddressShip.State    arr(AddressShip.State) }
	    { AddressShip.Zip      arr(AddressShip.Zip) }
	    { Items.ItemNumber     arr(Items.ItemNumber) }
	    { Items.BackOrder      arr(Items.BackOrder) }
	    { Items.Ship           arr(Items.Ship) }
	    { Items.Qty            arr(Items.Qty) }
	    { Items.Part           arr(Items.Part) }
	    { Items.Desc           arr(Items.Desc) }
	    { Items.UnitPrice      arr(Items.UnitPrice) }
	    { Items.DiscountRate   arr(Items.DiscountRate) }
	    { Items.Tax            arr(Items.Tax) }
	    { Items.Total          arr(Items.Total) }
	    { SalesRep             arr(SalesRep) }
	    { MethodOfPayment      arr(MethodOfPayment) }
	    { SSN                  arr(ssn) }
	    { CreditCard           arr(CreditCard) }
	    { DateOfExp            arr(DateOfExp) }
	    { PurchaseOrder        arr(PurchaseOrder) }
	    { Subtotal             arr(Subtotal) }
	    { Tax                  arr(Tax) }
	    { AfterTax             arr(AfterTax) }
	    { Freight              arr(Freight) }
	    { TotalCharge          arr(TotalCharge) }
	    { Payment              arr(Payment) }
	    { Reseller             arr(Reseller) }
	    { Comments             arr(Comments) }
	}]
	::set gv_estimatenumber [$this getEstimateNumber]
	::set arr(InvoiceNumber) $gv_estimatenumber
	::set arr(DateOfInvoice)       $gv_myattr(DateOfInvoice)
	::set arr(Clock)               [qddb_util formatdate "%X" now]
	::set arr(Company)             $gv_myattr(Company)
	::set arr(Name.First)          $gv_myattr(Name.First)
	::set arr(Name.Last)           $gv_myattr(Name.Last)
	::set arr(Name.ID)             $gv_myattr(Name.ID)
	::set arr(Phone.Desc)          $gv_myattr(Phone.Desc)
	::set arr(Phone.Area)          $gv_myattr(Phone.Area)
	::set arr(Phone.Number)        $gv_myattr(Phone.Number)
	::set arr(AddressBill.Street)  $gv_myattr(AddressBill.Street)
	::set arr(AddressBill.City)    $gv_myattr(AddressBill.City)
	::set arr(AddressBill.State)   $gv_myattr(AddressBill.State)
	::set arr(AddressBill.Zip)     $gv_myattr(AddressBill.Zip)
	::set arr(AddressShip.Street)  $gv_myattr(AddressShip.Street)
	::set arr(AddressShip.City)    $gv_myattr(AddressShip.City)
	::set arr(AddressShip.State)   $gv_myattr(AddressShip.State)
	::set arr(AddressShip.Zip)     $gv_myattr(AddressShip.Zip)
	::set arr(MethodOfPayment)     $gv_myattr(MethodOfPayment)
	::set arr(Reseller)            $gv_myattr(Reseller)
	::set arr(PurchaseOrder)       $gv_myattr(PurchaseOrder)
	::set arr(CreditCard)          $gv_myattr(CreditCard)
	::set arr(DateOfExp)           $gv_myattr(DateOfExp)
	::set arr(SalesRep)            $gv_myattr(SalesRep)
	::set arr(Subtotal)            $gv_myattr(Subtotal)
	::set arr(Tax)                 $gv_myattr(Tax)
	::set arr(AfterTax)            $gv_myattr(AfterTax)
	::set arr(Freight)             $gv_myattr(Freight)
	::set arr(TotalCharge)         $gv_myattr(TotalCharge)
	::set arr(Payment)             $gv_pmtamt
	::set arr(Comments)            $gv_myattr(Comments)
	for { set i 1 } { $i < [invoice_out get_rowCount] } { incr i } {
	    ::set arr(Items.ItemNumber) $i
	    ::set arr(Items.BackOrder)    $gv_invoice($i,1)
	    ::set arr(Items.Ship)         $gv_invoice($i,2)
	    ::set arr(Items.Qty)          $gv_invoice($i,3)
	    ::set arr(Items.Part)         $gv_invoice($i,4)
	    ::set arr(Items.Desc)         $gv_invoice($i,5)
	    ::set arr(Items.UnitPrice)    $gv_invoice($i,6)
	    ::set arr(Items.DiscountRate) $gv_invoice($i,7)
	    ::set arr(Items.Tax)          $gv_invoice($i,8)
	    ::set arr(Items.Total)        $gv_invoice($i,9)
	    qddb_instance switch $v Items [qddb_instance new $v Items]
	}
	if $gv_auto_print {
	    exec $ISPUTIL/bin/launch print_estimate $gv_estimatenumber | lpr -h &
	}
	qddb_tuple write $t
	qddb_tuple delete $t
	::set fx:status_variable \
		"Processed estimate $gv_estimatenumber, ready for next estimate..."
	newInvoice
	update idletasks
    }
    #
    # fillTable searches Invoices for the Invoice# populated from a search
    # Once found, the Item information is thrown into the invoice table
    #
    method fillTable {} {
	global gv_invoice arr gv_myattr gv_pmtamt gv_pmt
	wm title . "Estimate: $gv_myattr(Name.First) \ $gv_myattr(Name.Last)"
	set v [menubar info public view -value]
	set max [qddb_instance maxnum $v Items]
	for { set i 1 } { $i <= $max } { incr i } {
	    qddb_instance switch $v Items [expr $i] 
	    set gv_invoice($i,1)  $gv_myattr(Items.BackOrder)
	    set gv_invoice($i,2)  $gv_myattr(Items.Ship)
	    set gv_invoice($i,3)  $gv_myattr(Items.Qty)
            set gv_invoice($i,4)  $gv_myattr(Items.Part)
	    set gv_invoice($i,5)  $gv_myattr(Items.Desc)
	    set gv_invoice($i,6)  $gv_myattr(Items.UnitPrice)
	    set gv_invoice($i,7)  $gv_myattr(Items.DiscountRate)
	    set gv_invoice($i,8)  $gv_myattr(Items.Tax)
	    set gv_invoice($i,9)  $gv_myattr(Items.Total)
	}
	.f1.name.c.b.f.b1 configure -state disabled
	.f1.name.c.b.f.b2 configure -state disabled
	
	::set gv_pmtamt $gv_myattr(Payment)
	.f4.ccinfo.pmt.e configure    -state disabled
	.f4.ccinfo.pmt.full configure -state disabled
	.f4.ccinfo.pmt.part configure -state disabled
	.f4.ccinfo.pmt.none configure -state disabled
	set gv_pmt ""
    }
    method getEstimateNumber {} {
	global fx:status_variable gv_myattr gv_tmpattr
	set s [qddb_schema open Setup]
	set k [qddb_search $s -prunebyattr InvoiceNumber r .*]
	set t {}
	foreach i [qddb_keylist get $k] {
	    set t [qddb_tuple read $s $i]
	    if {[llength $t] > 0} {
		break
	    }
	}
	if {[string compare $t {}] == 0} {
	    ::set fx:status_variable \
		    "You must set up fields in the Setup screen first"
	    return
	}
	qddb_keylist delete $k
	while {[qddb_tuple lock $t] == 0} {
	    ::set fx:status_variable "Waiting for Setup screen to be closed...."
	    update idletasks
	    exec sleep 1
	    ::set fx:status_variable ""
	    update idletasks
	}
	set v [qddb_view define $t {
	    {EstimateNumber gv_tmpattr(estimatenum)}
	}]
        uplevel \#0 [list incr gv_tmpattr(estimatenum)]
	qddb_tuple write $t
	catch "qddb_tuple delete $t"
	qddb_schema close $s
	return $gv_tmpattr(estimatenum)
    }
    method dragDropErrorHandler {} {
    }
    method dragDropCustomer {} {
	global DragDrop gv_myattr fx:mode_variable
	if { ${fx:mode_variable} == "Read-only Mode" } {
	    tk_dialog .dialog "Info" \
		    "Estimate in Read-only mode, unable to update already processed invoice." \
		    info 0 Ok 
	    return 0
	}
	::set gv_myattr(Company)            [lindex $DragDrop(inventory) 0 ]
	::set gv_myattr(Name.First)         [lindex $DragDrop(inventory) 1 ]
	::set gv_myattr(Name.Last)          [lindex $DragDrop(inventory) 2 ]
	::set gv_myattr(Name.ID)            [lindex $DragDrop(inventory) 3 ]
	::set gv_myattr(Phone.Desc)         [lindex $DragDrop(inventory) 4 ]
	::set gv_myattr(Phone.Area)         [lindex $DragDrop(inventory) 5 ]
	::set gv_myattr(Phone.Number)       [lindex $DragDrop(inventory) 6 ]
	::set gv_myattr(AddressBill.Street) \
		[lindex [lindex $DragDrop(inventory) 7 ] 0] 
	::set gv_myattr(AddressBill.City)   \
		[lindex [lindex $DragDrop(inventory) 8 ] 0] 
	::set gv_myattr(AddressBill.State)  \
		[lindex [lindex $DragDrop(inventory) 9 ] 0] 
	::set gv_myattr(AddressBill.Zip)    \
		[lindex [lindex $DragDrop(inventory) 10 ] 0] 
	::set gv_myattr(AddressShip.Street) \
		[lindex [lindex $DragDrop(inventory) 11 ] 0] 
	::set gv_myattr(AddressShip.City)   \
		[lindex [lindex $DragDrop(inventory) 12 ] 0] 
	::set gv_myattr(AddressShip.State)  \
		[lindex [lindex $DragDrop(inventory) 13 ] 0] 
	::set gv_myattr(AddressShip.Zip)    \
		[lindex [lindex $DragDrop(inventory) 14 ] 0] 
	set type                            [lindex $DragDrop(inventory) 15 ]
	::set gv_myattr(Reseller)           [lindex $DragDrop(inventory) 16 ]
	::set gv_myattr(CreditCard)         [lindex $DragDrop(inventory) 17 ]
	::set gv_myattr(DateOfExp)          [lindex $DragDrop(inventory) 18 ]
	set manualCreditHold                [lindex $DragDrop(inventory) 19 ]
	::set gv_myattr(SalesRep)           [lindex $DragDrop(inventory) 20]
	#
	# choose Cash radiobutton and disable payment method to prevent
	# user selecting anything else -- if customer is COD/Cash only
	#
	if { $type == "COD/Cash only" } {
	    ::set gv_myattr(MethodOfPayment) "Cash"
	    .f4.ccinfo.mop.mop.e.t.r0 configure -state disabled
	    .f4.ccinfo.mop.mop.e.t.r1 configure -state disabled
	    .f4.ccinfo.mop.mop.e.t.r2 configure -state disabled
	    .f4.ccinfo.mop.mop.e.t.r4 configure -state disabled
	} else {
	    ::set gv_myattr(MethodOfPayment) $default_payment
	}
	invoice_out applyResellerTax
    }
    method afterSearchMode {} {
	global gv_pmtamt
	wm title . "Estimate: <Search Mode>"
	focus .f1.id.invoice.e      ;# set the cursor on the Estimate#
	.i.tbl setcell 0 0          ;# kluge to hide that damn blinking cursor
	invoice_out disableBindings ;# prevent invoice table from getting focus
	.mb.invoice.m entryconfigure 2 -state disabled 
	.mb.invoice.m entryconfigure 3 -state disabled
	.mb.invoice.m entryconfigure 5 -state disabled
	.mb.invoice.m entryconfigure 7 -state disabled
	.f1.name.c.b.f.b1 configure -state disabled
	.f1.name.c.b.f.b2 configure -state disabled
	.f4.ccinfo.pmt.e configure    -state disabled
	.f4.ccinfo.pmt.full configure -state disabled
	.f4.ccinfo.pmt.part configure -state disabled
	.f4.ccinfo.pmt.none configure -state disabled
	$this bindingsSearchMode
	update
    }
    method afterReadOnlyMode {} {
	global gv_myattr
	wm title . "Estimate: $gv_myattr(Name.First) \ $gv_myattr(Name.Last)"
	focus .f1.id.invoice.e      ;# set the cursor on the Estimate#
	.i.tbl setcell 0 0          ;# kluge to hide that damn blinking cursor
	invoice_out disableBindings ;# prevent invoice table from getting focus
	.mb.invoice.m entryconfigure 2 -state disabled 
	.mb.invoice.m entryconfigure 3 -state disabled
	.mb.invoice.m entryconfigure 5 -state disabled
	.mb.invoice.m entryconfigure 7 -state normal
	update
    }
    method afterAddMode {} {
	global fx:mode_variable fx:status_variable gv_invoice gv_myattr
	wm title . "Estimate: <Process Mode>"
	::set fx:mode_variable "Process Estimate Mode"
	::set fx:status_variable "Ready to process estimate"
	::set gv_myattr(InvoiceNumber) "######"
	::set gv_myattr(DateOfInvoice) [exec date +%d/%m/%y]
	$this clear
	update
	focus .i.tbl
	.mb.invoice.m entryconfigure 2 -state normal
	.mb.invoice.m entryconfigure 3 -state normal
	.mb.invoice.m entryconfigure 5 -state normal
	.mb.invoice.m entryconfigure 7 -state disabled
	.f1.name.c.b.f.b1 configure -state normal
	.f1.name.c.b.f.b2 configure -state normal

	::set gv_pmtamt [format "%10.2f" 0]
	.f4.ccinfo.pmt.e configure    -state normal
	.f4.ccinfo.pmt.full configure -state normal
	.f4.ccinfo.pmt.part configure -state normal
	.f4.ccinfo.pmt.none configure -state normal

	invoice_out bindings    ;# restore the mouse bindings
	$this bindingsProcessMode
	update
    }
    # 
    # Delete the Change and Read-only modes, since these are not allowed.  
    # And, stipple Process Invoice Mode accordingly
    #
    method afterpost_modes {} {
	global fx:mode_variable
	if { ${fx:mode_variable} == "Process Estimate Mode" } {
	    .mb.modes.menu entryconfigure 1 -state disabled
	} else {
	    .mb.modes.menu entryconfigure 1 -state normal
	}
    }
    method afterpost_file {} {
	.mb.file.menu entryconfigure 0 -state normal
    }
    method clientSearch { attrib field } {
	global gv_myattr search_attrs fx:status_variable
	blt_busy hold .
	set search_attrs Company.Contact.ID
	set print_attrs {\
		Company.Name \
		Company.Contact.First \
		Company.Contact.Last \
		Company.Contact.ID \
		Company.Contact.Phone.Desc \
		Company.Contact.Phone.Area \
		Company.Contact.Phone.Number \
		Company.Address.Street \
		Company.Address.City \
		Company.Address.State \
		Company.Address.Zip \
		ResellerNumber \
		CreditCard \
		DateOfExp \
		SalesRep \
	    }
	if {[winfo exists .search_results]} {
	    catch "search_results delete"
	    catch "destroy .search_results"
	    update
	}
	update idletasks
	if {[catch "qddb_schema open Clients" s] != 0} {
	    puts "Cannot open Clients Schema"; exit 1
	}
	set k [Fx_QddbSearchParser :: MultiSearch $s \
		[list $gv_myattr($attrib)] on $field]
	set klen [qddb_keylist length $k]
	if { $klen == 0 } {
	    ::set fx:status_variable "No matches found."
	    qddb_schema close $s
	    blt_busy forget .
	    return
	}
	if { $klen > $maxsearch } {
	    tk_dialog .dialog "Search found $klen items." \
		    "Please narrow your search." info 0 OK  
	    ::set fx:status_variable \
		    "Search found $klen items, please narrow your search."
	    qddb_schema close $s
	    ::set fx:status_variable "Ready to process invoice"
	    blt_busy forget .
	    return
	}
	set last_search [qddb_rows select -suppress on -attrs $field \
		-print $search_attrs $k]
	set myrows {}
	foreach i $last_search { lappend myrows [lindex $i 1] }
	set srt [qddb_rows sort -ascending {Company.Name Company.Contact.ID} \
		$print_attrs $myrows] 
	set res {}
	foreach i $srt { lappend res [qddb_rows getval $print_attrs $i] }
	if { [llength $res] == 1 } {
	    findClientSearchResultsProc 0
	    qddb_keylist delete $k
	    ::set fx:status_variable "Ready to process invoice"
	    blt_busy forget .
	    return
	}
	qddb_schema close $s

	toplevel .search_results
	wm title .search_results "Search Results"
	set widths2 { 15 10 15 6 }
	set headings { Company FirstName LastName ID }
	set alignment { left left left right }
	set separators { {} {} {} {} {} {} }
	set f .search_results.f2
	pack [frame $f] -side bottom -fill x
	button $f.select -text Close -command {
	    search_results OnSelect
	    search_results delete
	    destroy .search_results
	}
	pack $f.select -side left -padx 10m -pady 2m -ipadx 10m -ipady 2m
	Fx_MultiColumnListBox search_results -w .search_results.f \
		-widths $widths2 \
		-headings $headings -numcols [llength $headings] \
		-align $alignment \
		-exportselection off \
		-separators $separators \
		-single_select on \
		-height 10 -onselect [list $this findClientSearchResultsProc]
	search_results AppendRows $res
	search_results SortContents {Company Company.Contact.Last}
	search_results Format
	search_results Display

	tkwait window .search_results.f
	blt_busy release .
	update idletasks                                    
    }
    method findClientSearchResultsProc { i } {
	global gv_myattr
	set i [lindex $res $i]
	::set gv_myattr(Company)              [lindex $i 0]
	::set gv_myattr(Name.First)           [lindex $i 1]
	::set gv_myattr(Name.Last)            [lindex $i 2]
	::set gv_myattr(Name.ID)              [lindex $i 3]
	::set gv_myattr(Phone.Desc)           [lindex $i 4]
	::set gv_myattr(Phone.Area)           [lindex $i 5]
	::set gv_myattr(Phone.Number)         [lindex $i 6]
	::set gv_myattr(AddressShip.Street)   [lindex $i 7]
	::set gv_myattr(AddressShip.City)     [lindex $i 8]
	::set gv_myattr(AddressShip.State)    [lindex $i 9]
	::set gv_myattr(AddressShip.Zip)      [lindex $i 10]
	::set gv_myattr(AddressBill.Street)   [lindex $i 7]
	::set gv_myattr(AddressBill.City)     [lindex $i 8]
	::set gv_myattr(AddressBill.State)    [lindex $i 9]
	::set gv_myattr(AddressBill.Zip)      [lindex $i 10]
	::set gv_myattr(Reseller)             [lindex $i 11]
	::set gv_myattr(CreditCard)           [lindex $i 12]
	::set gv_myattr(DateOfExp)            [lindex $i 13]
	::set gv_myattr(SalesRep)             [lindex $i 14]
	::set fx:status_variable "Ready to process invoice"
    }
    method bindingsProcessMode {} {
	bind .f1.name.a.company.e <KeyPress-Return> \
		"$this clientSearch Company Company.Name; break"
	bind .f1.name.b.first.e   <KeyPress-Return> \
		"$this clientSearch Name.First Company.Contact.First; break"
	bind .f1.name.b.last.e    <KeyPress-Return> \
		"$this clientSearch Name.Last Company.Contact.Last; break"
	bind .f1.name.b.id.e      <KeyPress-Return> \
		"$this clientSearch Name.ID Company.Contact.ID; break"
    }
    method bindingsSearchMode {} {
	bind .f1.name.a.company.e <KeyPress-Return> ""
	bind .f1.name.b.first.e   <KeyPress-Return> ""
	bind .f1.name.b.last.e    <KeyPress-Return> ""
	bind .f1.name.b.id.e      <KeyPress-Return> ""
    }
    method bindings {} {
	bind .f4.totals.freight.e <FocusOut> [list $this handleFreight]
	bind .f4.ccinfo.res.reseller.e <FocusOut> \
		[list invoice_out applyResellerTax]
	bind .f4.ccinfo.pmt.e <FocusOut> [list $this verifyPayment]
	bind .f4.ccinfo.pmt.e <Tab> [list $this tabPayment]
	bind .f4.ccinfo.pmt.e <Shift-Tab> [list $this shiftTabPayment]
    }
    method handleFreight {} {
	global gv_myattr fx:mode_variable
	if { ${fx:mode_variable} == "Search Mode" } {
	    return
	}
	::set gv_myattr(Freight) [format "%10.2f" $gv_myattr(Freight)]
	invoice_out sumTotal
    }
    # 
    # When user clicks one of the payment radio buttons (Full, Partial, None)
    # this method is called to update the payment total entry.  The payment entry
    # is also updated within sumTotal if "Full" is the payment type.
    #
    method paymentClick {} {
	global gv_pmt gv_pmtamt gv_myattr
	if { $gv_pmt == "Full" } {
	    .f4.ccinfo.pmt.e configure -state disabled
	    ::set gv_pmtamt [format "%10.2f" $gv_myattr(TotalCharge)]
	} elseif { $gv_pmt == "None" } {
	    .f4.ccinfo.pmt.e configure -state disabled
	    ::set gv_pmtamt  [format "%10.2f" 0]
	} else {
	    .f4.ccinfo.pmt.e configure -state normal
	    ::set gv_pmtamt ""
	    focus .f4.ccinfo.pmt.e
	}
    }
    method verifyPayment {} {
	global gv_pmtamt fx:status_variable
	if { [catch [list expr double($gv_pmtamt)] ] != 0 } {
	    ::set fx:status_variable "Invalid Payment Amount"
	    focus .f4.ccinfo.pmt.e
	    return 0
	} else {
	    ::set gv_pmtamt [format "%10.2f" $gv_pmtamt]
	    ::set fx:status_variable "Ready to process invoice"
	    return 1
	}
    }
    method tabPayment {} {
	if [verifyPayment] {
	    focus .f4.totals.freight.e
	}
    }
    method shiftTabPayment {} {
	if [verifyPayment] {
	    focus .f4.ccinfo.cc.dateofexp.e
	}
    }
    method print_header {fd} {
	global gv_myattr gv_invoice gv_page
	global fx:status_variable

	incr gv_page
	puts $fd ""
	puts $fd [format "Estimate#: %s%40s %8s        Page: %3d" \
		[string trim $gv_myattr(InvoiceNumber) " "] \
		"Date: " \
		[qddb_util formatdate "%d/%m/%y" today] \
		 $gv_page]
	puts $fd "\n"
	puts $fd [format "      %-28s               %-28s" \
		"Bill To:" "Ship To:"]
	puts $fd [format "      %-28s               %-28s" \
		"$gv_myattr(Name.First) $gv_myattr(Name.Last)" \
		"$gv_myattr(Name.First) $gv_myattr(Name.Last)"]
	puts $fd [format "      %-28s               %-28s" \
		$gv_myattr(Company) $gv_myattr(Company)]
	puts $fd [format "      %-28s               %-28s" \
		$gv_myattr(AddressBill.Street) \
		$gv_myattr(AddressShip.Street)]
	puts $fd [format "      %-28s               %-28s" \
		"$gv_myattr(AddressBill.City), $gv_myattr(AddressBill.State) \
		$gv_myattr(AddressBill.Zip)" \
		"$gv_myattr(AddressShip.City), $gv_myattr(AddressShip.State) \
		$gv_myattr(AddressShip.Zip)"]
	puts $fd "\n"
	puts $fd [format "Date of Estimate:   %8s" $gv_myattr(DateOfInvoice)]
	puts $fd [format "Customer ID:   %5s" \
		[string range $gv_myattr(Name.ID) 0 5]]
	puts $fd [format "Sales Rep:         %3s" \
		[string range $gv_myattr(SalesRep) 0 2]]
	puts $fd [format "Method of Payment: %s" \
	    [string trim [string range $gv_myattr(MethodOfPayment) 0 9] " "]]
        puts $fd "\n\n Qty   Ship   B/O        Part#              Description       Price       Total"
    }
    method print {} { 
	global gv_myattr gv_invoice s gv_page
	global fx:status_variable fx_config

	set fn [TempNam]
	set fd [open $fn w]
	set v [menubar info public view -value]
	set max [qddb_instance maxnum $v Items]
	set gv_page 0
	print_header $fd
	set lines 0
	for {set i 1} {$i <= $max} {incr i} {
	    if {$i % 30 == 0} {
		puts $fd "\f"
		print_header $fd
		set lines 0
		incr lines
	    }
	    puts $fd [format "%5d %5d %5d %12s %24s    %8.2f    %8.2f" \
		    $gv_invoice($i,3) $gv_invoice($i,2) $gv_invoice($i,1) \
		    $gv_invoice($i,4) [string range $gv_invoice($i,5) 0 19] \
		    $gv_invoice($i,6) $gv_invoice($i,9)]
	}
	set extra ""
	for {set i $lines} {$i <= 30} {incr i} {
	    append extra "\n"
	}
	puts -nonewline $fd $extra
	puts $fd [format "\n%62s %10.2f" "Subtotal: " $gv_myattr(Subtotal)]
	puts $fd [format "%62s %10.2f" "Tax: " $gv_myattr(Tax)]
	puts $fd [format "%62s %10.2f" "Freight: " $gv_myattr(Freight)]
	puts $fd [format "%62s %10.2f" "Total Charge: " $gv_myattr(TotalCharge)]
	close $fd
	set fx_config(print_cmd) QBA_PRINTER_COMMAND
	set fx_config(default_print_cmd) QBA_DEFAULT_PRINT_COMMAND
	doPrint $fn
	catch "exec rm -f $fn"
    }
    method applyDiscount {} {
	invoice_out sumTotal
    }
    protected invoiceNumber
    protected res
    protected gv_address_button
    public title "Estimate" {}
    public default_payment "Visa" {} 
    public rootdir /var/lib/ISPutil/bin {}
    public bitmapdir /var/lib/ISPutil/bitmaps {}
    ##public fx_config_dir .invoice_config {}
    public lwidth 25 {}
    public maxsearch 500
}

Invoice my_invoice
