Browse Source

add prepos back into revision control

git-svn-id: file:///home/pjones/src/tmp/b2-svn/prepos/trunk@281 637e2b85-2e0a-0410-84ac-bd785500051d
master
pjones 13 years ago
parent
commit
c319f82c4e
83 changed files with 16688 additions and 0 deletions
  1. 12
    0
      Makefile
  2. 85
    0
      bin/about.pl
  3. 93
    0
      bin/backup.pl
  4. 36
    0
      bin/cal.pl
  5. 123
    0
      bin/closing-custom.pl
  6. 93
    0
      bin/closing-report.pl
  7. 62
    0
      bin/curses-test.pl
  8. 282
    0
      bin/customer-edit.pl
  9. 235
    0
      bin/customer-find.pl
  10. 4606
    0
      bin/html2ps
  11. 294
    0
      bin/inventory-edit.pl
  12. 292
    0
      bin/inventory-find.pl
  13. 764
    0
      bin/invoice-edit.pl
  14. 278
    0
      bin/invoice-find.pl
  15. 331
    0
      bin/invoice-finish.pl
  16. 378
    0
      bin/invoice-print.pl
  17. 126
    0
      bin/jivebox.pl
  18. 75
    0
      bin/misc-menu.pl
  19. 100
    0
      bin/misc-new.pl
  20. 103
    0
      bin/notes-edit.pl
  21. 83
    0
      bin/notes-pick.pl
  22. 58
    0
      bin/play-quiz.pl
  23. 93
    0
      bin/prepos
  24. 140
    0
      bin/prepos-customer.pl
  25. 79
    0
      bin/prepos-games.pl
  26. 134
    0
      bin/prepos-inventory.pl
  27. 78
    0
      bin/prepos-main.pl
  28. 67
    0
      bin/prepos-reports.pl
  29. 43
    0
      bin/prepos-tools.pl
  30. 104
    0
      bin/report-cost-sale.pl
  31. 161
    0
      bin/report-line.pl
  32. 532
    0
      bin/report-print.pl
  33. 141
    0
      bin/report-suspicious.pl
  34. 189
    0
      bin/report-vendor.pl
  35. 130
    0
      bin/restore.pl
  36. 97
    0
      bin/revert-eox.pl
  37. 61
    0
      bin/salesmen-menu.pl
  38. 8
    0
      bin/term.sh
  39. 186
    0
      bin/tools-status.pl
  40. 187
    0
      bin/vendor-find.pl
  41. BIN
      contrib/Math-BigInt-1.49.tar.gz
  42. 69
    0
      docs/CHANGES
  43. 6
    0
      docs/README
  44. 8
    0
      docs/TODO
  45. 2
    0
      docs/db
  46. 238
    0
      docs/html/eod.html
  47. 4
    0
      docs/labels
  48. 4
    0
      etc/crontab
  49. 6
    0
      etc/html2ps.cf
  50. 1
    0
      etc/last-eod
  51. 1
    0
      etc/last-eom
  52. BIN
      etc/logo.jpg
  53. 1
    0
      etc/tax
  54. 575
    0
      lib/Curses/Widgets.pm
  55. 323
    0
      lib/Curses/Widgets/ButtonSet.pm
  56. 464
    0
      lib/Curses/Widgets/Calendar.pm
  57. 343
    0
      lib/Curses/Widgets/ComboBox.pm
  58. 395
    0
      lib/Curses/Widgets/ListBox.pm
  59. 294
    0
      lib/Curses/Widgets/ProgressBar.pm
  60. 485
    0
      lib/Curses/Widgets/Table.pm
  61. 398
    0
      lib/Curses/Widgets/TextField.pm
  62. 545
    0
      lib/Curses/Widgets/TextMemo.pm
  63. 381
    0
      lib/Math/Currency.pm
  64. 174
    0
      lib/common.pl
  65. 29
    0
      lib/gp.pl
  66. 276
    0
      lib/prepos.pm
  67. 415
    0
      lib/ui.pl
  68. 133
    0
      lib/ui_table.pl
  69. 14
    0
      sql/customers.sql
  70. 12
    0
      sql/initial-data.sql
  71. 21
    0
      sql/inventory.sql
  72. 37
    0
      sql/invoice.sql
  73. 8
    0
      sql/notes.sql
  74. 7
    0
      sql/salesmen.sql
  75. 15
    0
      sql/test-inventory-data.sql
  76. 1
    0
      sql/version-1.1.0.sql
  77. 2
    0
      sql/version-1.2.0.sql
  78. 2
    0
      sql/version-1.6.0.sql
  79. 9
    0
      tools/dbsetup.sh
  80. 3
    0
      tools/load-test-data.sh
  81. 5
    0
      tools/syntax.sh
  82. 5
    0
      tools/test-refresh.sh
  83. 38
    0
      tools/test-table-driver.pl

+ 12
- 0
Makefile View File

@@ -0,0 +1,12 @@
DESTDIR = /home/prepos
SUBDIRS = bin lib etc tools sql

install:
@ for directory in ${SUBDIRS}; do \
echo "==> installing for $$directory"; \
mkdir -p ${DESTDIR}/$$directory; \
cp -r $$directory/* ${DESTDIR}/$$directory/; \
done
mkdir -p ${DESTDIR}/tmp
chown -R prepos ${DESTDIR}

+ 85
- 0
bin/about.pl View File

@@ -0,0 +1,85 @@
#! /usr/bin/perl -w

################################################################################
#
# prepos-main.pl (prepos about screen)
# Peter J Jones (pjones@pmade.org)
#
################################################################################
#
# Includes
#
################################################################################
use strict;
use lib qw(../lib /home/prepos/lib);
use Curses;
use prepos;
require "ui.pl";
require "common.pl";
################################################################################
#
# Constants
#
################################################################################
use constant DATE => 'Sun Dec 30 14:54:53 2001';
use constant ID => '$Id: about.pl,v 1.1.1.1 2003/08/04 05:02:45 pjones Exp $';
################################################################################
#
# Global Variables
#
################################################################################
use vars qw($VERSION);
$VERSION = '0.0.1';

################################################################################
#
# Code Start
#
################################################################################
my $version_string = '$Name: $';
$version_string =~ s/^\s*\$\w+:\s*//;
$version_string =~ s/\s*\$\s*$//g;

if (length $version_string) {
$version_string =~ s/^\w+-//;
$version_string =~ s/_/./g;
} else {
$version_string = '$Revision: 1.1.1.1 $';
$version_string =~ s/^\s*\$\w+:\s*//;
$version_string =~ s/\s*\$\s*$//g;
$version_string = "CVS ID: $version_string";
}

my $curses = ui_begin("About prepos");
ui_add_draw_callback(\&draw_callback);
ui_getkey("\n");

################################################################################
sub draw_callback {
my ($y, $x) = @_;
my $string = "[ENTER] Exit";
my $start_x = int(($x - length($string)) / 2);

$curses->attrset(COLOR_PAIR(select_color('blue', 'white')));
$curses->addstr(2, 0, ' ' x $start_x);
$curses->addstr($string);
$curses->addstr(' ' x ($x - (length($string) + $start_x)));
$curses->attrset(0);

$curses->attrset(COLOR_PAIR(select_color('green', 'black')));
$curses->addstr(5, 1, "prepos: A point of sale (POS) and inventory package");
$curses->addstr(6, 1, "Prototype Application for Cruzers Rod and Custom Products");
$curses->attrset(0);

$curses->attrset(COLOR_PAIR(select_color('red', 'black')));
$curses->addstr(8, 1, "Copyright 2001-2002 Peter J Jones <pjones\@pmade.org>");
$curses->addstr(9, 1, "All Rights Reserved");
$curses->attrset(0);

$curses->attrset(COLOR_PAIR(select_color('yellow', 'black')));
$curses->addstr(11, 1, "Version: $version_string");
$curses->attrset(0);

my $fortune = `/usr/games/fortune -s murphy`;
$curses->addstr(16, 0, $fortune);
}

+ 93
- 0
bin/backup.pl View File

@@ -0,0 +1,93 @@
#! /usr/bin/perl -w
################################################################################
#
# backup.pl (backup prepos to disk)
# Peter J Jones (pjones@pmade.org)
#
################################################################################
#
# Includes
#
################################################################################
use strict;
use Cwd qw(chdir cwd);
use lib qw(../lib /home/prepos/lib);
use prepos;
################################################################################
#
# Constants
#
################################################################################
use constant DATE => 'Thu Feb 7 19:12:15 2002';
use constant ID => '$Id: backup.pl,v 1.1.1.1 2003/08/04 05:02:45 pjones Exp $';
################################################################################
#
# Global Variables
#
################################################################################
use vars qw($VERSION);
$VERSION = '0.0.1';

my $backup_start = "../backup";
my $backup_dir = $backup_start;
################################################################################
#
# Code Start
#
################################################################################
my @now = localtime();

$now[5]+=1900;
$now[4]++;
$_ = $_ < 10 ? "0$_" : $_ foreach (@now[0..4]);

my $backup_end = "$now[5]-$now[4]-$now[3]-$now[2]-$now[1]";

$backup_dir .= "/$backup_end";
system("mkdir -p $backup_dir");

my $prepos = new prepos;

my $table_sql = qq( show tables );
my $table_sth = $prepos->{'__dbh__'}->prepare($table_sql) || die;
my $table_rv = $table_sth->execute() || die;
my @tables;

while (defined ($table_rv = $table_sth->fetchrow_arrayref())) {
push(@tables, $table_rv->[0]);
}

foreach my $table (@tables) {
open(OUT, ">$backup_dir/$table") || die;

my $sql = qq( select * from $table );
my $sth = $prepos->{'__dbh__'}->prepare($sql) || die;
my $rv = $sth->execute() || die;

while (defined ($rv = $sth->fetchrow_hashref())) {
encode_hash($rv);
}

$sth->finish();
close OUT;
}


chdir($backup_start);
system("tar cyf $backup_end.tar.bz2 $backup_end");
system("rm -rf $backup_end");
system("scp -q $backup_end.tar.bz2 cracp\@pmade.org:backup");

sub encode_hash {
my $hash = shift;
my @parts;
my ($k, $v);

foreach my $key (keys %$hash) {
$k = $key; $v = $hash->{$k} || '';
s/([\n&=\%])/sprintf('%%%02x', ord($1))/ge foreach ($k, $v);
push(@parts, "$k=$v");
}

print OUT join('&', @parts), "\n";
}

+ 36
- 0
bin/cal.pl View File

@@ -0,0 +1,36 @@
#! /usr/bin/perl -w
################################################################################
#
# cal.pl (display a calendar)
# Peter J Jones (pjones@pmade.org)
#
################################################################################
#
# Includes
#
################################################################################
use strict;
use lib qw(../lib /home/prepos/lib);
require "ui.pl";
################################################################################
#
# Constants
#
################################################################################
use constant DATE => 'Thu Feb 7 23:23:42 2002';
use constant ID => '$Id: cal.pl,v 1.1.1.1 2003/08/04 05:02:45 pjones Exp $';
################################################################################
#
# Global Variables
#
################################################################################
use vars qw($VERSION);
$VERSION = '0.0.1';

################################################################################
#
# Code Start
#
################################################################################
ui_begin("Calendar");
ui_get_date("");

+ 123
- 0
bin/closing-custom.pl View File

@@ -0,0 +1,123 @@
#! /usr/bin/perl -w
################################################################################
#
# closing-custom.pl (run a custome time frame closing report)
# Peter J Jones (pjones@pmade.org)
#
################################################################################
#
# Includes
#
################################################################################
use strict;
use lib qw(../lib /home/prepos/lib);
use Curses;
use Curses::Widgets;
use Date::Manip;
require "ui.pl";
################################################################################
#
# Constants
#
################################################################################
use constant ID => '$Id: closing-custom.pl,v 1.1.1.1 2003/08/04 05:02:56 pjones Exp $';
################################################################################
#
# Global Variables
#
################################################################################
my $curses = ui_begin("Custom Time Frame Closing Report");
my ($start_box, $end_box);
my $start_string = UnixDate("yesterday 01:00:01", '%m/%d/%Y %H:%M');
my $end_string = UnixDate("today", '%m/%d/%Y %H:%M');
my ($start_time, $end_time);
my $key;
################################################################################
#
# Code Start
#
################################################################################
$start_box = Curses::Widgets::TextField->new({
CAPTION => "Start Time: ",
LENGTH => 40,
MAXLENGTH => 255,
BORDER => 1,
X => 2,
Y => 8,
VALUE => $start_string,
CURSORPOS => length($start_string),
});

$end_box = Curses::Widgets::TextField->new({
CAPTION => "End Time: ",
LENGTH => 40,
MAXLENGTH => 255,
BORDER => 1,
X => 2,
Y => 12,
VALUE => $end_string,
CURSORPOS => length($end_string),
});

ui_widget_add($start_box);
ui_widget_add($end_box);
ui_widget_set_active($start_box);
ui_add_draw_callback(\&draw_callback);

while (1) {
$key = ui_getkey("\n", "\t", KEY_UP, KEY_DOWN, KEY_F0()+1, KEY_F0()+2);

if (ord($key) == 10 or ord($key) == 9 or $key eq KEY_UP or $key eq KEY_DOWN) {
my $widget = ui_widget_get_active();

if (set_date($widget) == 1) {
if ($widget == $start_box) { ui_widget_set_active($end_box); }
else { ui_widget_set_active($start_box); }
}
} elsif ($key == (KEY_F0()+1)) {
if (set_date($start_box) == 1 && set_date($end_box) == 1) {
ui_shell("$^X report-print.pl --start $start_time --stop $end_time --title 'Closing Report (Custom Time Frame)'");
exit;
}
} elsif ($key == (KEY_F0()+2)) {
exit;
}
}
################################################################################
sub set_date {
my $widget = shift;
my $value = $widget->getField('VALUE');
my $date = ParseDate($value);

if (!$date) {
$widget->setField('VALUE', "Invalid Date");
$widget->setField('CURSORPOS', length("Invalid Date"));
return 0;
} else {
my $secs = UnixDate($date, '%s');
my $full_date = localtime($secs);

$widget->setField('VALUE', $full_date);
$widget->setField('CURSORPOS', length($full_date));

if ($widget == $start_box) {
$start_time = $secs;
} else {
$end_time = $secs;
}

return 1;
}
}
################################################################################
sub draw_callback {
my ($y, $x) = @_;
my $string = "[F1] Run Report [F2] Exit";
my $start_x = int(($x - length($string)) / 2);

$curses->attrset(COLOR_PAIR(select_color('blue', 'white')));
$curses->addstr(2, 0, ' ' x $start_x);
$curses->addstr($string);
$curses->addstr(' ' x ($x - (length($string) + $start_x)));
$curses->attrset(0);
}

+ 93
- 0
bin/closing-report.pl View File

@@ -0,0 +1,93 @@
#! /usr/bin/perl -w
################################################################################
#
# closing-report.pl (prepos report generator for the closing reports)
# Peter J Jones (pjones@pmade.org)
#
################################################################################
#
# Includes
#
################################################################################
use strict;
use lib qw(../lib /home/prepos/lib);
use Curses;
use Curses::Widgets;
use Curses::Widgets::TextField;
use prepos;
require "ui.pl";
require "common.pl";
################################################################################
#
# Constants
#
################################################################################
use constant DATE => 'Sun Dec 30 14:54:53 2001';
use constant ID => '$Id: closing-report.pl,v 1.1.1.1 2003/08/04 05:02:45 pjones Exp $';
################################################################################
#
# Global Variables
#
################################################################################
use vars qw($VERSION);
$VERSION = '0.0.1';

my %clo;
my $eod_file = "../etc/last-eod";
my $eom_file = "../etc/last-eom";

my $title;
my $eod_title = "End of Day Report (EOD)";
my $eom_title = "End of Month Report (EOM)";

my @menu_items = (
['End of Day (EOD)' => \&do_eod],
['End of Month (EOM)' => \&do_eom],
['Custom Time Frame' => \&do_custom],
['Exit This Menu' => sub{exit}],
);
################################################################################
#
# Code Start
#
################################################################################
my $curses = ui_begin($title);
ui_command_menu_new(\@menu_items, 5, 15);
ui_command_menu_run();
################################################################################
sub do_eod { run_report(1); }
################################################################################
sub do_eom { run_report(0); }
################################################################################
sub do_custom { ui_shell("$^X closing-custom.pl"); exit; }
################################################################################
sub run_report {
my $eod = shift;
my $last_run_file = $eod == 1 ? $eod_file : $eom_file;
my $save_time = time();
my $stop_time = time() + 120;
my $start_time = 0;

# backup last time (they run this report on accident all the time)
system("cp -p $last_run_file $last_run_file.orig > /dev/null 2>&1");

# get start time from file
if (open(LTIME, "$last_run_file")) {
chomp($start_time = <LTIME>);
close LTIME;

$start_time =~ s/[^\d]//g;
}

# write next start time to file
if (open(LTIME, ">$last_run_file")) {
print LTIME "$save_time\n";
close LTIME;
}

my $report_title = $eod == 1 ? $eod_title : $eom_title;
ui_shell("$^X report-print.pl --start $start_time --stop $stop_time --title '$report_title'");
if ($eod == 1) { ui_shell("$^X report-suspicious.pl --start $start_time --stop $stop_time"); }

exit;
}

+ 62
- 0
bin/curses-test.pl View File

@@ -0,0 +1,62 @@
#! /usr/bin/perl
################################################################################
#
# curses-test.pl (help for writing curses programs)
# Peter J Jones (pjones@pmade.org)
#
################################################################################
#
# Includes
#
################################################################################
use strict;
use Curses;
################################################################################
#
# Constants
#
################################################################################
use constant DATE => 'Mon Dec 31 19:54:21 2001';
use constant ID => '$Id: curses-test.pl,v 1.1.1.1 2003/08/04 05:02:45 pjones Exp $';
################################################################################
#
# Global Variables
#
################################################################################
use vars qw($VERSION);
$VERSION = '0.0.1';

################################################################################
#
# Code Start
#
################################################################################
my $curses = new Curses;
my ($y, $x); $curses->getmaxyx($y, $x);

noecho();
halfdelay(9);
$curses->keypad(1);
curs_set(0);

keytest();

sub keytest {
$curses->addstr(0, 0, "KEYS: ");
$curses->addstr("[F0 " . sprintf("%d]", KEY_F0()));

my $key = '';
while (1) {
$key = $curses->getch();
next if ($key eq "-1");

$curses->addstr(1, 0, ' ' x $x);
$curses->addstr(1, 0, "KEY PRESSED: " . sprintf("%d/%x/%s", $key, ord($key), $key));

if (length($key) > 1) {
$curses->addstr(2, 0, "WIDE KEY: YES");
} else {
$curses->addstr(2, 0, "WIDE KEY: NO ");
}
}
}

+ 282
- 0
bin/customer-edit.pl View File

@@ -0,0 +1,282 @@
#! /usr/bin/perl
################################################################################
#
# customer-edit.pl (edit a customer)
# Peter J Jones (pjones@pmade.org)
#
################################################################################
#
# Includes
#
################################################################################
use strict;
use lib qw(../lib /home/prepos/lib);

use Getopt::Long;
use Curses;
require "ui.pl";
################################################################################
#
# Constants
#
################################################################################
use constant DATE => 'Fri Dec 14 22:26:17 2001';
use constant ID => '$Id: customer-edit.pl,v 1.1.1.1 2003/08/04 05:02:47 pjones Exp $';
################################################################################
#
# Global Variables
#
################################################################################
use vars qw($VERSION);
$VERSION = '0.0.1';

use vars qw(
$color_background
$color_active_border
$color_active_border_bg
$color_inactive_border
$color_caption
);

my %clo;
my @widgets;
my $current_widget = 0;

my %titles = (
'custname' => 'Customer Name',
'resale_num' => 'Resale Number',
'phone_num' => 'Phone Number',
'addr_1' => 'Address Line 1',
'addr_2' => 'Address Line 2',
'city_name' => 'City',
'state_name' => 'ST',
'zip_code' => 'Zip Code',
);

my %posmap;
my $okay = 0;
################################################################################
#
# Code Start
#
################################################################################
GetOptions(
\%clo,

'help|h!',
'title=s',
'start-field=s',
'out-file=s',

'custname=s',
'resale_num=s',
'phone_num=s',
'addr_1=s',
'addr_2=s',
'city_name=s',
'state_name=s',
'zip_code=s',
) or usage();
$clo{'help'} && usage();

sub usage {
print "Usage: $0 [options]\n", <<EOT;
-h, --help This message
--start-field x Start editing with field x
--title x Set window title to x

--custname x Set customer name to x
--resale_num x Set resale number to x
--phone_num x Set phone number to x
--addr_1 x Set address line 1 to x
--addr_2 x Set address line 2 to x
--city_name x Set city to x
--state_name x Set state to x
--zip_code x Set zip code to x
EOT
exit;
}

$clo{'title'} ||= 'Add Customer';
$clo{'start-field'} ||= 'custname';
$clo{'out-file'} ||= "../tmp/customer-edit.pl-$^T.$$";

foreach (qw(custname resale_num phone_num addr_1 addr_2 city_name state_name zip_code)) {
if (not defined $clo{$_}) {
$clo{$_} = '';
}
}

if (not exists $titles{$clo{'start-field'}}) {
$clo{'start-field'} = 'custname';
}

unless (open(OUT, ">$clo{'out-file'}")) {
print STDERR "$0: can't create file $clo{'out-file'}: $!\n";
exit 1;
}

my $curses = ui_begin($clo{'title'});

my $start_y = 2;
my $pos = 0;
my $width = 40;
my $start_x = 1;
foreach (qw(custname resale_num phone_num addr_1 addr_2 city_name state_name zip_code)) {
if ($_ eq 'city_name') {
$width = 35;
$start_x = 2;
} elsif ($_ eq 'state_name') {
$width = 3;
$start_x = 39;
} else {
$width = 40;
$start_x = 2;
}

push @widgets, Curses::Widgets::TextField->new({
CAPTION => "$titles{$_}",
LENGTH => $width,
MAXLENGTH => 255,
BORDER => 1,
X => $start_x,
Y => $start_y,
VALUE => $clo{$_},
CURSORPOS => length($clo{$_}),
});

if ($_ ne 'city_name') {
$start_y += 3;
}

$posmap{$_} = $pos;
$pos++;
}

my $button_set = Curses::Widgets::ButtonSet->new({
LENGTH => 10,
VALUE => 0,
BORDER => 1,
HORIZONTAL => 0,
PADDING => 1,
X => 50,
Y => 2,
LABELS => [ qw( OK CANCEL ) ],
});
foreach (@widgets, $button_set) {
$_->setField('BACKGROUND', $color_background);
$_->setField('CAPTIONCOL', $color_caption) unless $_ == $button_set;
}

ui_widget_add(@widgets, $button_set);

main_loop();

if ($okay) {
foreach (keys %titles) {
my $value = $widgets[$posmap{$_}]->getField('VALUE');

$value =~ s/^\s+//;
$value =~ s/^\$//;
$value =~ s/^\s+//;
$value =~ s/\s+$//;

print OUT "$_ = $value\n";
}
} else {
print OUT "canceled\n";
}

sub main_loop {
my $key;
my $switch_right = 0;
$current_widget = $posmap{$clo{'start-field'}};

while (1) {
last if $current_widget >= @widgets;
$widgets[$current_widget]->setField('BORDERCOL', $color_active_border);
$widgets[$current_widget]->setField('BACKGROUND', $color_active_border_bg);

ui_widget_set_active($widgets[$current_widget]);
ui_draw_window();

while (1) {
$key = ui_getkey("\n", "\t", KEY_UP, KEY_DOWN, KEY_RIGHT);
last if (ord($key) == 10 or ord($key) == 9 or $key eq KEY_UP or $key eq KEY_DOWN);

if ($key eq KEY_RIGHT) {
my $pos = $widgets[$current_widget]->getField('CURSORPOS');
if ($pos >= length($widgets[$current_widget]->getField('VALUE'))) {
$switch_right = 1;
last;
} else {
$widgets[$current_widget]->_input($key);
next;
}
}
}

$widgets[$current_widget]->setField('BORDERCOL', $color_inactive_border);
$widgets[$current_widget]->setField('BACKGROUND', $color_background);
$widgets[$current_widget]->draw($curses, 0);

if ($switch_right) {
$switch_right = 0;
last if button_loop();
next;
}

if ($key eq KEY_UP) {
if ($current_widget != 0) {
--$current_widget;
}
} elsif ($current_widget == $#widgets) {
last if button_loop();
next;
} else {
++$current_widget;
}
}
}

sub button_loop {
my $key;
my $change_focus = 0;

$button_set->setField('BORDERCOL', $color_active_border);
$button_set->setField('BACKGROUND', $color_active_border_bg);
ui_widget_set_active($button_set);
ui_draw_window();

while (1) {
$key = ui_getkey("\n", "\t", KEY_LEFT);

if (ord($key) == 9 or $key eq KEY_LEFT) {
$change_focus = 1;
last;
}

$button_set->_input($key);
$button_set->draw($curses, 1);
last if (ord($key) == 10);
}

$button_set->setField('BORDERCOL', $color_inactive_border);
$button_set->setField('BACKGROUND', $color_background);
$button_set->draw($curses, 0);

return 0 if $change_focus;
$okay = !$button_set->getField('VALUE');
return 1 if not $okay;

my $cust_name = $widgets[$posmap{'custname'}]->getField('VALUE');
$cust_name =~ s/^\s+//; $cust_name =~ s/\s+$//;

if (not length $cust_name) {
ui_dialog_okay("You at least need to give a Customer Name.");
return 0;
}

return 1;
}

+ 235
- 0
bin/customer-find.pl View File

@@ -0,0 +1,235 @@
#! /usr/bin/perl -w
################################################################################
#
# customer-find.pl (interface for finding a customer)
# Peter J Jones (pjones@pmade.org)
#
################################################################################
#
# Includes
#
################################################################################
use strict;
use Getopt::Long;
use lib qw(../lib /home/prepos/lib);
use Curses;
use prepos;
require "ui.pl";
require "common.pl";
################################################################################
#
# Constants
#
################################################################################
use constant DATE => 'Mon Dec 31 14:26:54 2001';
use constant ID => '$Id: customer-find.pl,v 1.1.1.1 2003/08/04 05:02:47 pjones Exp $';
################################################################################
#
# Global Variables
#
################################################################################
use vars qw($VERSION);
$VERSION = '0.0.1';

my @listitems;
my $current_field = 1;
my %field_desc = (
1 => "Add New Customer",
2 => "Exit",
);

my %clo;
################################################################################
#
# Code Start
#
################################################################################
GetOptions(
\%clo,
'title=s',
);

$clo{'title'} ||= "Customer Search";

my $outfile = shift @ARGV || "customer-find.$^T.$$.out";
open(OUT, ">$outfile") || die $!;

my $prepos = new prepos;
my $curses = ui_begin($clo{'title'});
my ($y, $x); $curses->getmaxyx($y, $x);

my $search_field = Curses::Widgets::TextField->new({
CAPTION => "Customer Name",
CAPTIONCOL => "yellow",
LENGTH => $x - 6,
MAXLENGTH => 255,
BORDER => 1,
BORDERCOL => 'green',
X => 2,
Y => 4,
VALUE => '',
});
ui_widget_add($search_field);

my $listbox = Curses::Widgets::ListBox->new({
CAPTION => "Items Found",
CAPTIONCOL => "yellow",
LENGTH => $x - 6,
LINES => $y - 12,
VALUE => 0,
FOREGROUND => undef,
BACKGROUND => 'black',
BORDER => 1,
BORDERCOL => 'green',
X => 2,
Y => 8,
TOPELEMENT => 0,
LISTITEMS => [],
});
ui_widget_add($listbox);

ui_widget_set_active($search_field);
ui_add_draw_callback(\&draw_callback);

if (@ARGV) {
$search_field->setField('VALUE', $ARGV[0]);
$search_field->setField('CURSORPOS', length($ARGV[0]) + 1);
do_search($ARGV[0]);
}

my $key = '';
while (1) {
$key = ui_getkey("\n", "\t", KEY_UP, KEY_DOWN, KEY_ENTER, map {KEY_F0()+$_} keys %field_desc);

if ($key eq KEY_UP or $key eq KEY_DOWN or $key eq "\n" or $key eq "\t") {
my $widget = ui_widget_get_active();

if ($widget == $search_field) {
my $search_string = $search_field->getField('VALUE');
next unless do_search($search_string);
} else {
if ($key eq KEY_UP or $key eq KEY_DOWN) {
if ($key eq KEY_UP and $listbox->getField('CURSORPOS') == 0) {
ui_widget_set_active($search_field);
} else {
$listbox->_input($key);
}
} elsif ($key eq "\t") {
undef @listitems;
$listbox->setField('LISTITEMS', []);
ui_widget_set_active($search_field);
} else {
if (@listitems and defined $listbox->getField('CURSORPOS')) {
print OUT $listitems[$listbox->getField('CURSORPOS')][1]{'id'}, "\n";
close OUT;
exit 0;
}
}
}

next;
}

if (length($key) > 1 and $key == KEY_F0() + 2) {
print OUT "cancled\n";
exit;
}

if (length($key) > 1 and $key == KEY_F0() + 1) {
my $id = try_new_item();
print OUT "$id\n";
exit;
}
}
################################################################################
sub do_search {
my $search_string = shift;
clean_string($search_string);
return 0 unless length $search_string;

fill_listitems($search_string);

unless (@listitems) {
ui_dialog_okay("No customers matched your search.");
return 0;
}

ui_widget_set_active($listbox);
return 1;
}
################################################################################
sub fill_listitems {
my $name = $prepos->{'__dbh__'}->quote(shift);
$name =~ s/^'//; $name =~ s/'$//;

undef @listitems;
$listbox->setField('LISTITEMS', []);

my $sql = qq{select * from customer where custname like '\%$name\%'};
my $sth = $prepos->{'__dbh__'}->prepare($sql);

if (not $sth) {
print STDERR "error with sql $sql\n";
exit 1;
}

my $rv = $sth->execute();
if (not defined $rv) {
print STDERR "error with sql execution $sql\n";
exit 1;
}

return unless $rv > 0;

my $string;
while (defined ($rv = $sth->fetchrow_hashref())) {
$_ = substr($rv->{'custname'}, 0, 20);
if (length($_) < 20) { $_ .= ' ' x (20 - length($_)) }
$string = $_;

$_ = defined $rv->{'phone_num'} ? substr($rv->{'phone_num'}, 0, 13) : '';
if (length($_) < 13) { $_ .= ' ' x (13 - length($_)) }
$string .= " $_";

$_ = defined $rv->{'addr_1'} ? substr($rv->{'addr_1'}, 0, 15) : '';
if (length($_) < 15) { $_ .= ' ' x (15 - length($_)) }
$string .= " $_";

push(@listitems, [$string, {%$rv}]);
}

$listbox->setField('LISTITEMS', [map {$_->[0]} @listitems]);
$listbox->setField('TOPELEMENT', 0);
$listbox->setField('CURSORPOS', 0);
}
################################################################################
sub clean_string {
$_[0] =~ s/^\s+//;
$_[0] =~ s/\s+$//;
$_[0] =~ s/\$\s*//;
}
################################################################################
sub draw_callback {
my ($y, $x) = @_;
my $string;

foreach my $key (sort keys %field_desc) {
$string .= "[F$key] $field_desc{$key} ";
}

my $start_x = int(($x - length($string)) / 2);

$curses->attrset(COLOR_PAIR(select_color('blue', 'white')));
$curses->addstr(2, 0, ' ' x $start_x);
$curses->addstr($string);
$curses->addstr(' ' x ($x - (length($string) + $start_x)));
$curses->attrset(0);
}
################################################################################
sub try_new_item {
my $value = $search_field->getField('VALUE');
$value =~ s/^\s+//;
$value =~ s/\s+$//;

return new_customer();
}

+ 4606
- 0
bin/html2ps
File diff suppressed because it is too large
View File


+ 294
- 0
bin/inventory-edit.pl View File

@@ -0,0 +1,294 @@
#! /usr/bin/perl
################################################################################
#
# inv-add.pl (Curses interface for adding an inventory item)
# Peter J Jones (pjones@pmade.org)
#
################################################################################
#
# Includes
#
################################################################################
use strict;
use lib qw(../lib /home/prepos/lib);

use Getopt::Long;
use Curses;
require "ui.pl";
require "gp.pl";
################################################################################
#
# Constants
#
################################################################################
use constant DATE => 'Fri Dec 14 22:26:17 2001';
use constant ID => '$Id: inventory-edit.pl,v 1.1.1.1 2003/08/04 05:02:52 pjones Exp $';
################################################################################
#
# Global Variables
#
################################################################################
use vars qw($VERSION);
$VERSION = '0.0.1';

use vars qw(
$color_background
$color_active_border
$color_active_border_bg
$color_inactive_border
$color_caption
);

my %clo;
my @widgets;
my $current_widget = 0;

my %titles = (
'partnum' => 'Part Number',
'vendor' => 'Vendor',
'desc' => 'Description',
'cost' => 'Cost Price',
'sale' => 'Sale Price',
'count' => 'Quanity on Hand',
'min' => 'Minimum on Hand',
);

my %posmap;
################################################################################
#
# Code Start
#
################################################################################
GetOptions(
\%clo,

'help|h!',
'partnum=s',
'vendor=s',
'desc=s',
'cost=s',
'sale=s',
'count=s',
'min=s',
'title=s',
'start-field=s',
'out-file=s',
) or usage();
$clo{'help'} && usage();

sub usage {
print "Usage: $0 [options]\n", <<EOT;
--cost x Set cost to x
--count x Set on hand quanity to x
--desc x Set description to x
--min x Set min quanity to x
--out-file x Place output into the file x
--partnum x Set part number to x
--sale x Set sale to x
--start-field x Start editing with field x
--title x Set window title to x
--vendor x Set vendor to x
-h, --help This message
EOT
exit;
}

$clo{'title'} ||= 'Add to Inventory';
$clo{'start-field'} ||= 'partnum';
$clo{'out-file'} ||= 'inventory.out';

if (not defined $clo{'count'} or not length $clo{'count'}) {
$clo{'count'} = 1;
}

foreach my $x (qw(partnum vendor desc cost sale min)) {
if (not defined $clo{$x}) {
$clo{$x} = '';
}
}

if (not exists $titles{$clo{'start-field'}}) {
$clo{'start-field'} = 'partnum';
}

unless (open(OUT, ">$clo{'out-file'}")) {
print STDERR "$0: can't create file $clo{'out-file'}: $!\n";
exit 1;
}

my $curses = ui_begin($clo{'title'});

my $start_y = 2;
my $pos = 0;
foreach ('partnum', 'vendor', 'desc', 'cost', 'sale', 'count', 'min') {
push @widgets, Curses::Widgets::TextField->new({
CAPTION => "$titles{$_}",
LENGTH => 40,
MAXLENGTH => 255,
BORDER => 1,
X => 2,
Y => $start_y,
VALUE => $clo{$_},
CURSORPOS => length($clo{$_}),
INPUT_FILTER => \&input_filter,
});

$start_y += 3;
$posmap{$_} = $pos;
$pos++;
}

ui_widget_add(@widgets);
ui_add_draw_callback(\&draw_callback);

if (main_loop()) {
foreach (keys %titles) {
my $value = $widgets[$posmap{$_}]->getField('VALUE');

$value =~ s/^\s+//;
$value =~ s/^\$//;
$value =~ s/^\s+//;
$value =~ s/\s+$//;

if ($_ eq 'min' and $value =~ /^\s*$/) {
print OUT "$_ = NULL\n";
} else {
print OUT "$_ = $value\n";
}
}
} else {
print OUT "canceled\n";
}

sub main_loop {
my $key;
$current_widget = $posmap{$clo{'start-field'}};

while (1) {
last if $current_widget >= @widgets;
$widgets[$current_widget]->setField('BORDERCOL', $color_active_border);
$widgets[$current_widget]->setField('BACKGROUND', $color_active_border_bg);

ui_widget_set_active($widgets[$current_widget]);
ui_draw_window();

while (1) {
$key = ui_getkey("\n", "\t", KEY_UP, KEY_DOWN, KEY_F0()+1, KEY_F0()+2, KEY_F0()+3);
last if (ord($key) == 10 or ord($key) == 9 or $key eq KEY_UP or $key eq KEY_DOWN);

if (length($key) > 1) {
if ($key == KEY_F0()+1) {
if (all_fields_are_good()) {
return 1;
}
} elsif ($key == KEY_F0()+2) {
return 0;
} elsif ($key == KEY_F0()+3) {
replace_vendor();
}
}
}

$widgets[$current_widget]->setField('BORDERCOL', $color_inactive_border);
$widgets[$current_widget]->setField('BACKGROUND', $color_background);
$widgets[$current_widget]->draw($curses, 0);

if ($key eq KEY_UP) {
if ($current_widget != 0) {
--$current_widget;
}
} elsif ($current_widget == $#widgets) {
next;
} else {
++$current_widget;
}
}
}

sub all_fields_are_good {
# check fields to make sure they have something in them
foreach my $field (keys %titles) {
next if $field eq 'min';

if ($widgets[$posmap{$field}]->getField('VALUE') =~ /^\s*$/) {
ui_dialog_okay("$titles{$field} can't be blank, please fix this first.");
$current_widget = $posmap{$field};
return 0;
}
}

# check number fields
foreach my $field ('count') {
if ($widgets[$posmap{$field}]->getField('VALUE') !~ /^\s*\d+\s*$/) {
ui_dialog_okay("$titles{$field} must be a number, please check what you have typed.");
$current_widget = $posmap{$field};
return 0;
}
}

# check for partnum == misc
if ($widgets[$posmap{'partnum'}]->getField('VALUE') =~ /^\s*MISC\s*$/) {
ui_dialog_okay("MISC is a reserved part number. Please pick something else.");
$current_widget = $widgets[$posmap{'partnum'}];
return 0;
}

return 1;
}

sub input_filter {
my ($widget, $key) = @_;

if ($widget == $widgets[$posmap{'cost'}] or $widget == $widgets[$posmap{'sale'}]) {
my $value = $widget->getField('VALUE');
$value = '' unless defined $value;

return 0 if ($key eq '$' and length($value));
return 0 if ($key eq '.' and $value =~ /\,/);
return 0 if ($value =~ /\.\d\d$/);
return 0 if ($key =~ /^[^\$\d.]$/);
} elsif ($widget == $widgets[$posmap{'count'}] or $widget == $widgets[$posmap{'min'}]) {
return 0 if ($key =~ /^[^\d]$/);
}

return 1;
}
################################################################################
sub draw_callback {
my ($y, $x) = @_;

my @menu = ('[F1] Save', '[F2] Abort', '[F3] Find Vendor' );

my $start_x = int($x - 25);
my $start_y = 5;

foreach my $item (@menu) {
$curses->attrset(COLOR_PAIR(select_color('yellow', 'black')));
$curses->addstr($start_y, $start_x, $item);
$curses->attrset(0);
++$start_y;
}

$start_y += 2;
$curses->attrset(COLOR_PAIR(select_color('yellow', 'black')));
my $gp = calc_gp($widgets[$posmap{cost}]->getField('VALUE'), $widgets[$posmap{sale}]->getField('VALUE'));
$curses->addstr($start_y, $start_x, "GP $gp");
$curses->attrset(0);
}
################################################################################
sub replace_vendor {
my $file = "../tmp/inventory-edit.$^T.$$.tmp";
ui_shell("$^X vendor-find.pl '$file'");

unless (open(IN, $file)) {
return;
}

my $vendor = <IN>;
chomp $vendor;
close IN;
unlink $file;

$widgets[$posmap{'vendor'}]->setField('VALUE', $vendor);
$widgets[$posmap{'vendor'}]->setField('CURSORPOS', length($vendor));
}

+ 292
- 0
bin/inventory-find.pl View File

@@ -0,0 +1,292 @@
#! /usr/bin/perl -w
################################################################################
#
# inventory-find.pl (Find an inventory item by one of the fields)
# Peter J Jones (pjones@pmade.org)
#
################################################################################
#
# Includes
#
################################################################################
use strict;
use Getopt::Long;
use lib qw(../lib /home/prepos/lib);
use Curses;
use prepos;
require "ui.pl";
require "common.pl";
################################################################################
#
# Constants
#
################################################################################
use constant DATE => 'Mon Dec 31 14:26:54 2001';
use constant ID => '$Id: inventory-find.pl,v 1.1.1.1 2003/08/04 05:02:52 pjones Exp $';
################################################################################
#
# Global Variables
#
################################################################################
use vars qw($VERSION);
$VERSION = '0.0.1';

my @listitems;
my $current_field = 1;
my %field_desc = (
1 => "Part Number",
2 => "Description",
3 => "Vendor",
4 => "New Item",
5 => "Exit",
);

my %clo;
################################################################################
#
# Code Start
#
################################################################################
GetOptions(
\%clo,
'no-new!',
'list-new!',
);

if ($clo{'no-new'}) {
delete $field_desc{4};
};

my $outfile = shift @ARGV || "inventory-find.$^T.$$.out";
open(OUT, ">$outfile") || die $!;

my $prepos = new prepos;
my $curses = ui_begin("Inventory Search");
my ($y, $x); $curses->getmaxyx($y, $x);

my $search_field = Curses::Widgets::TextField->new({
CAPTION => "Part Number",
CAPTIONCOL => "yellow",
LENGTH => $x - 6,
MAXLENGTH => 255,
BORDER => 1,
BORDERCOL => 'green',
X => 2,
Y => 4,
VALUE => '',
});
ui_widget_add($search_field);

my $listbox = Curses::Widgets::ListBox->new({
CAPTION => "Items Found",
CAPTIONCOL => "yellow",
LENGTH => $x - 6,
LINES => $y - 12,
VALUE => 0,
FOREGROUND => undef,
BACKGROUND => 'black',
BORDER => 1,
BORDERCOL => 'green',
X => 2,
Y => 8,
TOPELEMENT => 0,
LISTITEMS => [],
});
ui_widget_add($listbox);

ui_widget_set_active($search_field);
ui_add_draw_callback(\&draw_callback);

if (@ARGV) {
$search_field->setField('VALUE', $ARGV[0]);
$search_field->setField('CURSORPOS', length($ARGV[0]) + 1);
do_search($ARGV[0]);
}

my $key = '';
while (1) {
$key = ui_getkey("\n", "\t", KEY_UP, KEY_DOWN, KEY_ENTER, map {KEY_F0()+$_} keys %field_desc);

if ($key eq KEY_UP or $key eq KEY_DOWN or $key eq "\n" or $key eq "\t") {
my $widget = ui_widget_get_active();

if ($widget == $search_field) {
my $search_string = $search_field->getField('VALUE');
next unless do_search($search_string);
} else {
if ($key eq KEY_UP or $key eq KEY_DOWN) {
if ($key eq KEY_UP and $listbox->getField('CURSORPOS') == 0) {
ui_widget_set_active($search_field);
} else {
$listbox->_input($key);
}
} elsif ($key eq "\t") {
undef @listitems;
$listbox->setField('LISTITEMS', []);
ui_widget_set_active($search_field);
} else {
if (@listitems and defined $listbox->getField('CURSORPOS')) {
print OUT $listitems[$listbox->getField('CURSORPOS')][1]{'id'}, "\n";
close OUT;
exit 0;
}
}
}

next;
}

if (length($key) > 1 and $key == KEY_F0() + 5) {
print OUT "cancled\n";
exit;
}

if (length($key) > 1 and $key == KEY_F0() + 4) {
next if $clo{'no-new'};
my $id = try_new_item();
if (defined $id) {
if ($clo{'list-new'}) {
fill_listitems(undef, $id);
if (@listitems) {
ui_widget_set_active($listbox);
}
} else {
print OUT "$id\n";
exit;
}
}
next;
}

foreach my $fkey (keys %field_desc) {
if (length($key) > 1 && $key == (KEY_F0() + $fkey)) {
$current_field = $fkey;
$search_field->setField('CAPTION', $field_desc{$fkey});
ui_widget_set_active($search_field);
}
}
}
################################################################################
sub do_search {
my $search_string = shift;
clean_string($search_string);
return 0 unless length $search_string;

fill_listitems($search_string);

unless (@listitems) {
ui_dialog_okay("No inventory items matched your search.");
return 0;
}

ui_widget_set_active($listbox);
return 1;
}
################################################################################
sub fill_listitems {
my $string = shift;
my $id = shift;
my $inventory_system = prepos::inventory->new($prepos);
my ($field, @items);

undef @listitems;
$listbox->setField('LISTITEMS', []);

if (not defined $id) {
if ($current_field == 1) {
$field = 'part_num';
} elsif ($current_field == 2) {
$field = 'description';
} elsif ($current_field == 3) {
$field = 'vendor';
} elsif ($current_field == 4) {
$field = 'cost';
} elsif ($current_field == 5) {
$field = 'sale';
} else {
$field = 'part_num';
}

@items = $inventory_system->fetch_field($field, $string);
} else {
@items = $inventory_system->fetch_field("id", $id);
}

return unless @items;
return unless defined $items[0];

foreach my $item (@items) {
$_ = substr($item->{'part_num'}, 0, 15);
if (length($_) < 15) {$_ .= ' ' x (15 - length($_))}
$string = $_;

$_ = substr($item->{'description'}, 0, 20);
if (length($_) < 20) {$_ .= ' ' x (20 - length($_))}
$string .= " $_";

$_ = substr($item->{'vendor'}, 0, 15);
if (length($_) < 15) {$_ .= ' ' x (15 - length($_))}
$string .= " $_";

$_ = substr(ui_num_to_money($item->{'sale'}), 0, 10);
if (length($_) < 10) {$_ .= ' ' x (10 - length($_))}
$string .= " $_";

$_ = substr("QTY:" . $item->{'count'}, 0, 7);
if (length($_) < 7) {$_ .= ' ' x (7 - length($_))}
$string .= " $_";

push(@listitems, [$string, {%$item}]);
}

$listbox->setField('LISTITEMS', [map {$_->[0]} @listitems]);
$listbox->setField('TOPELEMENT', 0);
$listbox->setField('CURSORPOS', 0);
}
################################################################################
sub clean_string {
$_[0] =~ s/^\s+//;
$_[0] =~ s/\s+$//;
$_[0] =~ s/\$\s*//;
}
################################################################################
sub draw_callback {
my ($y, $x) = @_;
my $string;

foreach my $key (sort keys %field_desc) {
$string .= "[F$key] $field_desc{$key} ";
}

my $start_x = int(($x - length($string)) / 2);

$curses->attrset(COLOR_PAIR(select_color('blue', 'white')));
$curses->addstr(2, 0, ' ' x $start_x);
$curses->addstr($string);
$curses->addstr(' ' x ($x - (length($string) + $start_x)));
$curses->attrset(0);
}
################################################################################
sub try_new_item {
my $value = $search_field->getField('VALUE');
$value =~ s/^\s+//;
$value =~ s/\s+$//;

if (length $value) {
my $field;

if ($current_field == 1) {
$field = 'partnum';
} elsif ($current_field == 2) {
$field = 'desc';
} elsif ($current_field == 3) {
$field = 'vendor';
} else {
$field = 'partnum';
}

return new_inventory_item($field, $value);
}

return new_inventory_item();
}

+ 764
- 0
bin/invoice-edit.pl View File

@@ -0,0 +1,764 @@
#! /usr/bin/perl -w
################################################################################
#
# prepos-main.pl (Main prepos menu)
# Peter J Jones (pjones@pmade.org)
#
################################################################################
#
# Includes
#
################################################################################
use strict;
use Cwd qw(chdir cwd);
use Getopt::Long;
use lib qw(../lib /home/prepos/lib);
require "ui.pl";
require "gp.pl";
use prepos;
use Curses;
use Curses::Widgets;
use Curses::Widgets::Table;
use Math::Currency;
################################################################################
#
# Constants
#
################################################################################
use constant DATE => 'Sun Dec 30 14:54:53 2001';
use constant ID => '$Id: invoice-edit.pl,v 1.1.1.1 2003/08/04 05:02:53 pjones Exp $';
################################################################################
#
# Global Variables
#
################################################################################
use vars qw($VERSION);
$VERSION = '0.0.1';

my %clo;

my %totals = (
'items' => 0,
'subtotal' => 0,
'taxtotal' => 0,
'amountdue' => 0,
);

my @labels = (
['items' => "Items:"],
['subtotal' => "Sub Total:"],
['taxtotal' => "Tax:"],
['amountdue' => "Amount Due:"],
['taxstate' => "Taxable State:"],
);

my %command_menu = (
1 => "Exit This Menu",
2 => "Toggle Item Taxable State",
3 => "Toggle Invoice Taxable State",
4 => "Add Miscelaneous Charge",
5 => "Assign Invoice to a Customer",
6 => "Remove Assigned Customer From Invoice",
7 => "Add Note to Invoice",
8 => "Remove Note from Invoice",
9 => "Cancel (abort) Invoice",
10 => "Finish (complete) Invoice",
);

my @table_labels = (
['QTY', 6],
['PART NUMBER', 15],
['DESCRIPTION', 0],
['GP', 6],
['PRICE', 9],
['TAXABLE', 7],
);

my $adjust_col = 2;
my $title = "New Invoice";
my $taxstate = 1;
my $current_tax_rate;
my @rows;
my $salesmen;
my $orig_date;
my $customer_id;
my $note_text;
my $no_customer_name = "No Customer Assigned";
my $customer_name = $no_customer_name;
################################################################################
#
# Code Start
#
################################################################################
GetOptions(
\%clo,

'load=i',
'reload=i',
);

if ($clo{'load'}) {
$title = "Invoice Viewer";
}

my $prepos = new prepos;
my $curses = ui_begin($title);

if ($clo{'load'}) {
$title = "Invoice Viewer";

delete $command_menu{2};
delete $command_menu{3};
delete $command_menu{4};
delete $command_menu{5};
delete $command_menu{6};
delete $command_menu{7};
delete $command_menu{8};

$command_menu{9} = "Exit Invoice Viewer";
$command_menu{10} = "Print This Invoice";

load_invoice($clo{'load'});
} else {
unless (open(TAX, "../etc/tax")) {
print "$0: can't open tax file: $!\n";
print "$0: something is broken\n";
sleep 4;
exit 1;
}

chomp($current_tax_rate = <TAX>);
close TAX;

$current_tax_rate =~ s/^\s+//;
$current_tax_rate =~ s/\s+$//;

if ($clo{'reload'}) {load_invoice($clo{'reload'});}
}

my ($y, $x); $curses->getmaxyx($y, $x);
my $menu = $curses->derwin($clo{'load'} ? 6 : 5, $x-2, 3, 1);

my $max_title = 0;
foreach my $title (@labels) {
my $l = length($title->[1]);
if ($l > $max_title) {$max_title = $l}
}

my $max_column = 0;
foreach my $column (@table_labels) {
$max_column += $column->[1] + 1;
}
$table_labels[$adjust_col][1] = $x - 4 - $max_column;

my $table_start = @labels + 3;
$table_start++ if $clo{'load'};
my $table = Curses::Widgets::Table->new({
'Y' => $table_start,
'X' => 1,
'COLS' => scalar @table_labels,
'LABELS' => [map {$_->[0]} @table_labels],
'WIDTHS' => [map {$_->[1]} @table_labels],
'LINES' => $y - $table_start - 1,
'LENGTH' => $x - 2,
'BORDER' => 1,
'RO_COLS' => [2, 3, 5],
'NO_BLANK_COLS' => [0, 1, 4],
'ACTIVE_BG' => 'yellow',
'ACTIVE_FG' => 'blue',
'SWITCH_CB' => \&switch_callback,
'BLANK_CHECK_CB'=> \&blank_check,
'INPUT_FILTER_CB'=> \&input_filter,
'ROWS' => \@rows,
});

ui_widget_add($table);
ui_widget_set_active($table) unless $clo{'load'};
ui_add_draw_callback(\&draw_menu);
ui_draw_window();

my $key;
while (1) {
$key = ui_getkey(map{KEY_F0()+$_} keys %command_menu);

if (length($key) > 1) {
my $menu_item;

if ($key == KEY_F0()+1) {
$menu_item = show_menu();
} else {
$menu_item = $key - KEY_F0();
}

next if $menu_item == 1; # cancel menu
exit if $menu_item == 9; # cancel invoice


if ($menu_item == 2) {
my $rows = $table->getField('ROWS');
my $row = $table->getField('ACTIVE_ROW');

if (defined $rows->[$row][6]) {
if ($rows->[$row][5] eq 'Yes') {
$rows->[$row][5] = 'No';
} else {
$rows->[$row][5] = 'Yes';
}
}
} elsif ($menu_item == 3) {
$taxstate = !$taxstate;
foreach my $row (@{$table->getField('ROWS')}) {
if (defined $row and @$row > 5) {
next if $row->[6]{'part_num'} eq 'MISC';
$row->[5] = $taxstate ? "Yes" : "No";
}
}
} elsif ($menu_item == 4) {
add_misc_charge();
} elsif ($menu_item == 5) {
assign_to_customer();
} elsif ($menu_item == 6) {
$customer_id = undef;
$customer_name = $no_customer_name;
} elsif ($menu_item == 7) {
assign_note();
} elsif ($menu_item == 8) {
$note_text = '';
} elsif ($menu_item == 10) {
if ($clo{'load'}) {
print_invoice($clo{'load'});
} else {
exit if finish_sale();
}
}
}
}
################################################################################
sub draw_menu {
my ($y, $x); $menu->getmaxyx($y, $x);

$menu->erase();
calc_totals();

for (my $i=0; $i<@labels; ++$i) {
$menu->move($i, 0);

if (length($labels[$i][1]) < $max_title) {
$menu->addstr(' ' x ($max_title - length($labels[$i][1])));
}

next if ($labels[$i][0] eq 'taxstate' && $clo{'load'});
$menu->addstr($labels[$i][1] . " ");

if ($i == 0) {
$menu->addstr($totals{$labels[$i][0]});
} elsif ($labels[$i][0] eq 'taxstate') {
$menu->addstr($taxstate ? "On" : "Off");
} else {
$menu->addstr(ui_num_to_money($totals{$labels[$i][0]}));
}
}

# add customer name
my $customer_string = "Customer: $customer_name";
$menu->addstr(0, $x - length($customer_string) - 1, $customer_string);

# add note status
if (length($note_text)) {
my $note_string = "Invoice Has Note";
$menu->addstr(1, $x - length($note_string) - 1, $note_string);
}

my $string = "[F1] Invoice Command Menu";
my $start_x = int(($x - length($string)) / 2);
$curses->attrset(COLOR_PAIR(select_color('blue', 'white')));
$curses->addstr(2, 0, ' ' x $start_x);
$curses->addstr($string);
$curses->addstr(' ' x (2+$x - (length($string) + $start_x)));
$curses->attrset(0);

if ($clo{'load'}) {
my $string = "[Date: $orig_date] [Sales Person: $salesmen]";
$menu->standout();
$menu->addstr($y-1, 1, ' ' x ($x-2));
$menu->addstr($y-1, int(($x - length($string))/2), $string);
$menu->standend();
}

$menu->refresh();
}
################################################################################
sub calc_totals {
%totals = (
'items' => 0,
'subtotal' => 0,
'taxtotal' => 0,
'amountdue' => 0,
);

my $amount_due = Math::Currency->new(0);
my $subtotal = Math::Currency->new(0);
my $tax_total = Math::Currency->new(0);

foreach my $item (@{$table->getField('ROWS')}) {
next unless @$item > 5;

my $sale = Math::Currency->new(money_to_num($item->[4] || 0));

$item->[6]{'sale'} = $sale;
$totals{'items'} += abs($item->[0]);
$subtotal += $sale * $item->[0];

if ($item->[5] eq 'Yes') {
$tax_total += $sale * $item->[0] * $current_tax_rate;
}
}

$amount_due = $subtotal + $tax_total;

$totals{'subtotal'} = $subtotal;
$totals{'taxtotal'} = $tax_total;
$totals{'amountdue'}= $amount_due;
}
################################################################################
sub switch_callback {
my ($row, $col) = @_;
my $rows = $table->getField('ROWS');

my ($nrow, $ncol);
$nrow = $table->getField('ACTIVE_ROW');
$ncol = $table->getField('ACTIVE_COL');

if ($ncol == 1
and defined $rows->[$row][6]
and $rows->[$row][1] =~ /^MISC$/
and not defined $rows->[$row][6]{'part_num'})
{
if ($col > 1) {
$table->__prev_col();
} else {
$table->__next_col();
}
}

if ($col == 1) {
if (defined $rows->[$row][6] && $rows->[$row][1] !~ /^\s*$/) {
return if ($rows->[$row][1] eq $rows->[$row][6]{'part_num'});
return if ($rows->[$row][1] =~ /^MISC$/ and not defined $rows->[$row][6]{'part_num'});
}

my $part_num = $rows->[$row][$col];
$part_num = '' unless defined $part_num;

if ($part_num =~ /^\s*MISC\s*$/i) {
return add_misc_charge();
}

my $idfile = "../tmp/invoice-tmp-$^T-$$.tmp";
my $inv = prepos::inventory->new($prepos);
my $id = undef;

ui_shell("$^X inventory-find.pl $idfile '$part_num'");

if (open(IDFILE, "$idfile")) {
chomp($id = <IDFILE>);
close(IDFILE);
}

unlink($idfile);

if ($id =~ /^\d+$/) {
my @items = $inv->fetch_field('id', $id, 1);
if (@items == 1) {
$rows->[$row][1] = $items[0]->{'part_num'};
$rows->[$row][2] = qq|$items[0]->{'description'} ($items[0]->{'vendor'})|;
$rows->[$row][3] = calc_gp($items[0]->{'cost'}, $items[0]->{'sale'});
$rows->[$row][4] = ui_num_to_money($items[0]->{'sale'});
$rows->[$row][5] = $taxstate ? "Yes" : "No";
$rows->[$row][6] = $items[0];

draw_menu();
return;
}
}

$rows->[$row][$_] = "" foreach (0..@{$rows->[$row]});
$table->setField('ACTIVE_COL', 0);
} elsif ($col == 4) {
if (@{$rows->[$row]} > 5) {
my $sale = Math::Currency->new(money_to_num($rows->[$row][$col]));

$rows->[$row][4] = ui_num_to_money($sale);
$rows->[$row][3] = calc_gp($rows->[$row][6]{'cost'}, $sale);
}
} elsif ($col == 0) {
my $value = $rows->[$row][0];
$value = '' unless defined $value;

$value =~ s/^\s+//;
$value =~ s/\s+$//;

if ($value eq '-') {
$value = 0;
}

$rows->[$row][0] = $value;

if ($value =~ /[^\d-]/) {
ui_dialog_okay("You are going to have to go back and fix the QTY field because it contains non-numeric data.");
$table->setField('ACTIVE_COL', 0);
$table->setField('ACTIVE_ROW', $row);
$rows->[$row][0] = '';
}
}

draw_menu();
}
#################################################################################
sub blank_check {
my ($row, $col, $direction) = @_;
my $rows = $table->getField('ROWS');

if ($col == 0 and $row >= $#{$rows} and $direction eq 'UP') {
return 1;
}

return 0;
}
#################################################################################
sub input_filter {
my ($row, $col, $key) = @_;
my $rows = $table->getField('ROWS');

if ($col == 0) {
my $value = $rows->[$row][$col];
$value = '' unless defined $value;

return 0 if ($key eq '-' and length($value));
return $key =~ /^[\d-]$/;
} elsif ($col == 4) {
my $value = $rows->[$row][$col];
return 0 if ($key eq '.' and $value =~ /\./);
return 0 if ($key eq '$' and length($value));
return 0 if ($value =~ /\.\d\d$/);
return $key =~ /^[\$\d.]/;
}

return 1;
}
#################################################################################
sub add_misc_charge {
my $misc_charge;
my $output_file = "../tmp/misc-charge.$^T.$$.tmp";

ui_shell("$^X misc-menu.pl '$output_file'");
open(OUT, $output_file) or return;
chomp($misc_charge = <OUT>);
close(OUT);
unlink($output_file);

return if $misc_charge =~ /^cancel/;

my @item = (1, 'MISC', $misc_charge, "N/A", "", "No", {
'part_num' => 'MISC',
'vendor' => 'internal',
'cost' => 0,
'sale' => 0,
'min' => 0,
'count' => 1,
'description' => $misc_charge,
});

my $rows = $table->getField('ROWS');

if (@$rows and @{$rows->[-1]} < 6) {
if (defined $rows->[-1][0] && length($rows->[-1][0])) {
$item[0] = $rows->[-1][0];
}

splice(@$rows, -1, 1);
}

push(@$rows, \@item);

$table->setField('ACTIVE_COL', 4);
$table->setField('ACTIVE_ROW', $#{$rows});
$table->normalize();
$table->draw($curses, 1);
}
#################################################################################