From 9ae101389f2fe652575c6ab314a5e95c2283b72e Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 25 Oct 2003 02:05:44 +0000 Subject: signups with snarf info! --- FS/FS/ClientAPI/Signup.pm | 16 ++++++++++++++++ FS/FS/acct_snarf.pm | 8 ++++---- FS/FS/svc_Common.pm | 18 ++++++++++++++++-- FS/FS/svc_acct.pm | 8 +++++++- fs_signup/FS-SignupClient/cgi/signup.cgi | 6 +++++- 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm index 92fc6361d..60639b132 100644 --- a/FS/FS/ClientAPI/Signup.pm +++ b/FS/FS/ClientAPI/Signup.pm @@ -10,6 +10,8 @@ use FS::part_pkg; use FS::svc_acct_pop; use FS::cust_main; use FS::cust_pkg; +use FS::svc_acct; +use FS::acct_snarf; use FS::Msgcat qw(gettext); use FS::ClientAPI; #hmm @@ -155,6 +157,20 @@ sub new_customer { qw( username _password sec_phrase popnum ), } ); + my @acct_snarf; + my $snarfnum = 1; + while ( length($packet->{"snarf_machine$snarfnum"}) ) { + my $acct_snarf = new FS::acct_snarf ( { + 'machine' => $packet->{"snarf_machine$snarfnum"}, + 'protocol' => $packet->{"snarf_protocol$snarfnum"}, + 'username' => $packet->{"snarf_username$snarfnum"}, + '_password' => $packet->{"snarf_password$snarfnum"}, + } ); + $snarfnum++; + push @acct_snarf, $acct_snarf; + } + $svc_acct->child_objects( \@acct_snarf ); + my $y = $svc_acct->setdefault; # arguably should be in new method return { 'error' => $y } if $y && !ref($y); diff --git a/FS/FS/acct_snarf.pm b/FS/FS/acct_snarf.pm index 7c51d6945..b4e88bfc9 100644 --- a/FS/FS/acct_snarf.pm +++ b/FS/FS/acct_snarf.pm @@ -90,9 +90,9 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid example. If there is -an error, returns the error, otherwise returns false. Called by the insert -and replace methods. +Checks all fields to make sure this is a valid external mail account. If +there is an error, returns the error, otherwise returns false. Called by the +insert and replace methods. =cut @@ -103,7 +103,7 @@ sub check { || $self->ut_number('svcnum') || $self->ut_foreign_key('svcnum', 'svc_acct', 'svcnum') || $self->ut_domain('machine') - || $self->alphan('protocol') + || $self->ut_alphan('protocol') || $self->ut_textn('username') ; return $error if $error; diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 524e55086..2fed5dd8a 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -80,7 +80,7 @@ sub check { $self->SUPER::check; } -=item insert [ JOBNUM_ARRAYREF ] +=item insert [ JOBNUM_ARRAYREF [ OBJECTS_ARRAYREF ] ] Adds this record to the database. If there is an error, returns the error, otherwise returns false. @@ -91,11 +91,16 @@ defined. An FS::cust_svc record will be created and inserted. If an arrayref is passed as parameter, the Bs of any export jobs will be added to the array. +If an arrayref of FS::tablename objects (for example, FS::acct_snarf objects) +is passed as the optional second parameter, they will have their svcnum fields +set and will be inserted after this record, but before any exports are run. + =cut sub insert { my $self = shift; local $FS::queue::jobnums = shift if @_; + my $objects = scalar(@_) ? shift : []; my $error; local $SIG{HUP} = 'IGNORE'; @@ -142,6 +147,15 @@ sub insert { return $error; } + foreach my $object ( @$objects ) { + $object->svcnum($self->svcnum); + $error = $object->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + } + #new-style exports! unless ( $noexport_hack ) { foreach my $part_export ( $self->cust_svc->part_svc->part_export ) { @@ -416,7 +430,7 @@ sub cancel { ''; } =head1 VERSION -$Id: svc_Common.pm,v 1.13 2003-08-05 00:20:47 khoff Exp $ +$Id: svc_Common.pm,v 1.14 2003-10-25 02:05:44 ivan Exp $ =head1 BUGS diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 3fb28c054..4ea7734d4 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -188,10 +188,16 @@ The additional field I can optionally be defined; if so it should contain an arrayref of group names. See L. (used in sqlradius export only) +The additional field I can optionally be defined; if so it +should contain an arrayref of FS::tablename objects. They will have their +svcnum fields set and will be inserted after this record, but before any +exports are run. + (TODOC: L and L) (TODOC: new exports!) + =cut sub insert { @@ -319,7 +325,7 @@ sub insert { #see? i told you it was more complicated my @jobnums; - $error = $self->SUPER::insert(\@jobnums); + $error = $self->SUPER::insert(\@jobnums, $self->child_objects || [] ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; diff --git a/fs_signup/FS-SignupClient/cgi/signup.cgi b/fs_signup/FS-SignupClient/cgi/signup.cgi index 70facb570..5ca93d289 100755 --- a/fs_signup/FS-SignupClient/cgi/signup.cgi +++ b/fs_signup/FS-SignupClient/cgi/signup.cgi @@ -1,6 +1,6 @@ #!/usr/bin/perl -Tw # -# $Id: signup.cgi,v 1.47 2003-10-24 19:28:49 ivan Exp $ +# $Id: signup.cgi,v 1.48 2003-10-25 02:05:44 ivan Exp $ use strict; use vars qw( @payby $cgi $locales $packages @@ -266,6 +266,7 @@ if ( defined $cgi->param('magic') ) { '_password' => $password, 'popnum' => $popnum, 'agentnum' => $agentnum, + map { $_ => $cgi->param($_) } grep { /^snarf_/ } $cgi->param } ); } @@ -273,6 +274,9 @@ if ( defined $cgi->param('magic') ) { if ( $error eq '_decline' ) { print_decline(); } elsif ( $error ) { + #fudge the snarf info + no strict 'refs'; + ${$_} = $cgi->param($_) foreach grep { /^snarf_/ } $cgi->param; print_form(); } else { print_okay(); -- cgit v1.2.1 From 2f38a129b2eab044dce82ff4391f7c1779bdab2f Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 26 Oct 2003 00:39:13 +0000 Subject: payment reports broken down by Visa/MC / Amex / Discover --- httemplate/search/cust_pay.cgi | 38 ++++++++++++++++++++++++++++------ httemplate/search/report_cust_pay.html | 9 +++++--- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/httemplate/search/cust_pay.cgi b/httemplate/search/cust_pay.cgi index e4dba01e9..9eab5f82e 100755 --- a/httemplate/search/cust_pay.cgi +++ b/httemplate/search/cust_pay.cgi @@ -5,27 +5,53 @@ my @cust_pay; if ( $cgi->param('magic') && $cgi->param('magic') eq '_date' ) { my %search; + my @search; + if ( $cgi->param('payby') ) { - $cgi->param('payby') =~ /^(CARD|CHEK|BILL)$/ + $cgi->param('payby') =~ /^(CARD|CHEK|BILL)(-(VisaMC|Amex|Discover))?$/ or die "illegal payby ". $cgi->param('payby'); $search{'payby'} = $1; + if ( $3 ) { + if ( $3 eq 'VisaMC' ) { + #avoid posix regexes for portability + push @search, " ( substring(payinfo from 1 for 1) = '4' ". + " OR substring(payinfo from 1 for 2) = '51' ". + " OR substring(payinfo from 1 for 2) = '52' ". + " OR substring(payinfo from 1 for 2) = '53' ". + " OR substring(payinfo from 1 for 2) = '54' ". + " OR substring(payinfo from 1 for 2) = '54' ". + " OR substring(payinfo from 1 for 2) = '55' ". + " ) "; + } elsif ( $3 eq 'Amex' ) { + push @search, " ( substring(payinfo from 1 for 2 ) = '34' ". + " OR substring(payinfo from 1 for 2 ) = '37' ". + " ) "; + } elsif ( $3 eq 'Discover' ) { + push @search, " substring(payinfo from 1 for 4 ) = '6011' "; + } else { + die "unknown card type $3"; + } + } } #false laziness with cust_pkg.cgi - my $range = ''; if ( $cgi->param('beginning') && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) { my $beginning = str2time($1); - $range = " WHERE _date >= $beginning "; + push @search, "_date >= $beginning "; } if ( $cgi->param('ending') && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { my $ending = str2time($1) + 86400; - $range .= ( $range ? ' AND ' : ' WHERE ' ). " _date <= $ending "; + push @search, " _date <= $ending "; + } + my $search; + if ( @search ) { + $search = ( scalar(keys %search) ? ' AND ' : ' WHERE ' ). + join(' AND ', @search); } - $range =~ s/^\s*WHERE/ AND/ if scalar(keys %search) ; - @cust_pay = qsearch('cust_pay', \%search, '', $range ); + @cust_pay = qsearch('cust_pay', \%search, '', $search ); $sortby = \*date_sort; diff --git a/httemplate/search/report_cust_pay.html b/httemplate/search/report_cust_pay.html index 9d9fffb16..b8581ba4b 100644 --- a/httemplate/search/report_cust_pay.html +++ b/httemplate/search/report_cust_pay.html @@ -15,9 +15,12 @@ Payments of type: -- cgit v1.2.1 From c81ce77af66d49a2aafb4bc6361844748fa00d7e Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 26 Oct 2003 17:30:13 +0000 Subject: default quickpay to exact search --- httemplate/search/cust_main-quickpay.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/httemplate/search/cust_main-quickpay.html b/httemplate/search/cust_main-quickpay.html index 25d9db36b..d48f1d08f 100755 --- a/httemplate/search/cust_main-quickpay.html +++ b/httemplate/search/cust_main-quickpay.html @@ -13,22 +13,22 @@ Search for last name: using search method:

Search for company: using search methods: -

Note: Fuzzy searching can take a while. Please be patient. +

-- cgit v1.2.1 From e8b6246f82305ee4e315ef4920ea5c356e77fd96 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 3 Nov 2003 05:21:54 +0000 Subject: also show suspended and canceled counts on active package browse --- httemplate/browse/part_pkg.cgi | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index 2a167e8d6..044ba462e 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -12,7 +12,8 @@ my @part_pkg = qsearch('part_pkg', \%search ); my $total = scalar(@part_pkg); my $sortby; -my %num_active_cust_pkg; +my %num_active_cust_pkg = (); +my( $suspended_sth, $canceled_sth ) = ( '', '' ); if ( $cgi->param('active') ) { my $active_sth = dbh->prepare( 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'. @@ -27,6 +28,18 @@ if ( $cgi->param('active') ) { $sortby = sub { $num_active_cust_pkg{$b->pkgpart} <=> $num_active_cust_pkg{$a->pkgpart}; }; + + $suspended_sth = dbh->prepare( + 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'. + ' AND ( cancel IS NULL OR cancel = 0 )'. + ' AND susp IS NOT NULL AND susp > 0' + ) or die dbh->errstr; + + $canceled_sth = dbh->prepare( + 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'. + ' AND cancel IS NOT NULL AND cancel > 0' + ) or die dbh->errstr; + } else { $sortby = \*pkgpart_sort; } @@ -102,8 +115,19 @@ END print " "; print ''. $num_active_cust_pkg{$hashref->{'pkgpart'}}. - qq! active!; - # suspended/cancelled + qq! active
!; + + $suspended_sth->execute( $part_pkg->pkgpart ) or die $suspended_sth->errstr; + my $num_suspended = $suspended_sth->fetchrow_arrayref->[0]; + print ''. $num_suspended. + qq! suspended
!; + + $canceled_sth->execute( $part_pkg->pkgpart ) or die $canceled_sth->errstr; + my $num_canceled = $canceled_sth->fetchrow_arrayref->[0]; + print ''. $num_canceled. + qq! canceled!; + + print ''; } print < Date: Mon, 3 Nov 2003 05:25:29 +0000 Subject: tyops --- httemplate/browse/part_pkg.cgi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index 044ba462e..9dc8860fb 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -119,12 +119,12 @@ END $suspended_sth->execute( $part_pkg->pkgpart ) or die $suspended_sth->errstr; my $num_suspended = $suspended_sth->fetchrow_arrayref->[0]; - print ''. $num_suspended. + print ''. $num_suspended. qq! suspended
!; $canceled_sth->execute( $part_pkg->pkgpart ) or die $canceled_sth->errstr; my $num_canceled = $canceled_sth->fetchrow_arrayref->[0]; - print ''. $num_canceled. + print ''. $num_canceled. qq! canceled!; -- cgit v1.2.1 From 08d2adef1c4b4fff43184d763e1d369ece975573 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 3 Nov 2003 05:34:15 +0000 Subject: remove duplicate items from "Reports" section --- httemplate/index.html | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/httemplate/index.html b/httemplate/index.html index e3556c2d7..c03fd79d2 100644 --- a/httemplate/index.html +++ b/httemplate/index.html @@ -126,24 +126,6 @@

  • packages (by next bill date range) Package definitions (by number of active packages) -

    Invoices - - Payment Report (by type and/or date range) -

    Financial reports - Customers - Package definitions (by number of active packages) + Package definitions (by number of active packages)

    Customers
    • Search customers by order-taker -- cgit v1.2.1 From de38e11cb5a40c78ef5543f59f6ce968e7fd613b Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 3 Nov 2003 05:48:14 +0000 Subject: add suspended/canceled package browse --- httemplate/search/cust_pkg.cgi | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index f735fbf42..1d54a53f6 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -48,10 +48,21 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { } else { my $qual = ''; - if ( $cgi->param('magic') && $cgi->param('magic') eq 'active' ) { + if ( $cgi->param('magic') && + $cgi->param('magic') =~ /^(active|suspended|canceled)$/ + ) { - $qual = 'WHERE ( susp IS NULL OR susp = 0 )'. - ' AND ( cancel IS NULL OR cancel = 0)'; + if ( $cgi->param('magic') eq 'active' ) { + $qual = 'WHERE ( susp IS NULL OR susp = 0 )'. + ' AND ( cancel IS NULL OR cancel = 0)'; + } elsif ( $cgi->param('magic') eq 'suspended' ) { + $qual = 'WHERE susp IS NOT NULL AND susp > 0'. + ' AND ( cancel IS NULL OR cancel = 0)'; + } elsif ( $cgi->param('magic') eq 'canceled' ) { + $qual = 'WHERE cancel IS NOT NULL AND cancel > 0'; + } else { + die "guru meditation #420"; + } $sortby = \*pkgnum_sort; -- cgit v1.2.1 From d9c51bdc266798d01247e0ff72f6d64aefb91a2e Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 3 Nov 2003 05:57:28 +0000 Subject: add suspended/canceled browse, fix the old suspended browse --- httemplate/browse/part_pkg.cgi | 4 ++-- httemplate/index.html | 2 +- httemplate/search/cust_pkg.cgi | 10 ++-------- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index 9dc8860fb..efaa59e00 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -32,12 +32,12 @@ if ( $cgi->param('active') ) { $suspended_sth = dbh->prepare( 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'. ' AND ( cancel IS NULL OR cancel = 0 )'. - ' AND susp IS NOT NULL AND susp > 0' + ' AND susp IS NOT NULL AND susp != 0' ) or die dbh->errstr; $canceled_sth = dbh->prepare( 'SELECT COUNT(*) FROM cust_pkg WHERE pkgpart = ?'. - ' AND cancel IS NOT NULL AND cancel > 0' + ' AND cancel IS NOT NULL AND cancel != 0' ) or die dbh->errstr; } else { diff --git a/httemplate/index.html b/httemplate/index.html index cefbc4072..b95d2ac73 100644 --- a/httemplate/index.html +++ b/httemplate/index.html @@ -121,7 +121,7 @@ Packages diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index 1d54a53f6..3c3e17864 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -56,10 +56,10 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { $qual = 'WHERE ( susp IS NULL OR susp = 0 )'. ' AND ( cancel IS NULL OR cancel = 0)'; } elsif ( $cgi->param('magic') eq 'suspended' ) { - $qual = 'WHERE susp IS NOT NULL AND susp > 0'. + $qual = 'WHERE susp IS NOT NULL AND susp != 0'. ' AND ( cancel IS NULL OR cancel = 0)'; } elsif ( $cgi->param('magic') eq 'canceled' ) { - $qual = 'WHERE cancel IS NOT NULL AND cancel > 0'; + $qual = 'WHERE cancel IS NOT NULL AND cancel != 0'; } else { die "guru meditation #420"; } @@ -74,12 +74,6 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { $sortby=\*pkgnum_sort; - } elsif ( $query eq 'SUSP_pkgnum' ) { - - $sortby=\*pkgnum_sort; - - $qual = 'WHERE susp IS NOT NULL AND susp != 0'; - } elsif ( $query eq 'APKG_pkgnum' ) { $sortby=\*pkgnum_sort; -- cgit v1.2.1 From 36f038b92a2cc9c8f7bc52a2e11c00dbb7ed6ce0 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 3 Nov 2003 11:30:42 +0000 Subject: kludge around this so i can add service definitions for now --- httemplate/edit/part_svc.cgi | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index 18319b341..e033a7b09 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -131,23 +131,25 @@ my %defs = ( }, ); - foreach my $svcdb (grep dbdef->table($_), keys %defs ) { - my $self = "FS::$svcdb"->new; - $vfields{$svcdb} = {}; - foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them - my $pvf = $self->pvf($field); - my @list = $pvf->list; - if (scalar @list) { - $defs{$svcdb}->{$field} = { desc => $pvf->label, - type => 'select', - select_list => \@list }; - } else { - $defs{$svcdb}->{$field} = $pvf->label; - } #endif - $vfields{$svcdb}->{$field} = $pvf; - warn "\$vfields{$svcdb}->{$field} = $pvf"; - } #next $field - } #next $svcdb + #comment this out until it can be fixed, see bug#590 + # + #foreach my $svcdb (grep dbdef->table($_), keys %defs ) { + # my $self = "FS::$svcdb"->new; + # $vfields{$svcdb} = {}; + # foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them + # my $pvf = $self->pvf($field); + # my @list = $pvf->list; + # if (scalar @list) { + # $defs{$svcdb}->{$field} = { desc => $pvf->label, + # type => 'select', + # select_list => \@list }; + # } else { + # $defs{$svcdb}->{$field} = $pvf->label; + # } #endif + # $vfields{$svcdb}->{$field} = $pvf; + # warn "\$vfields{$svcdb}->{$field} = $pvf"; + # } #next $field + #} #next $svcdb my @dbs = $hashref->{svcdb} ? ( $hashref->{svcdb} ) -- cgit v1.2.1 From 47c39af359fc93c777714a5c50a6183747b66da9 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 3 Nov 2003 11:42:32 +0000 Subject: does this fix Bug#590?? --- FS/FS/svc_Common.pm | 8 ++------ httemplate/edit/part_svc.cgi | 36 +++++++++++++++++------------------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 2fed5dd8a..7bc155d9d 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -49,8 +49,8 @@ sub virtual_fields { if ($self->svcpart) { # Case 1 $svcpart = $self->svcpart; - } elsif (my $cust_svc = $self->cust_svc) { # Case 2 - $svcpart = $cust_svc->svcpart; + } elsif ( $self->svcnum ) { #Case 2 + $svcpart = $self->cust_svc->svcpart; } else { # Case 3 $svcpart = ''; } @@ -428,10 +428,6 @@ sub cancel { ''; } =back -=head1 VERSION - -$Id: svc_Common.pm,v 1.14 2003-10-25 02:05:44 ivan Exp $ - =head1 BUGS The setfixed method return value. diff --git a/httemplate/edit/part_svc.cgi b/httemplate/edit/part_svc.cgi index e033a7b09..18319b341 100755 --- a/httemplate/edit/part_svc.cgi +++ b/httemplate/edit/part_svc.cgi @@ -131,25 +131,23 @@ my %defs = ( }, ); - #comment this out until it can be fixed, see bug#590 - # - #foreach my $svcdb (grep dbdef->table($_), keys %defs ) { - # my $self = "FS::$svcdb"->new; - # $vfields{$svcdb} = {}; - # foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them - # my $pvf = $self->pvf($field); - # my @list = $pvf->list; - # if (scalar @list) { - # $defs{$svcdb}->{$field} = { desc => $pvf->label, - # type => 'select', - # select_list => \@list }; - # } else { - # $defs{$svcdb}->{$field} = $pvf->label; - # } #endif - # $vfields{$svcdb}->{$field} = $pvf; - # warn "\$vfields{$svcdb}->{$field} = $pvf"; - # } #next $field - #} #next $svcdb + foreach my $svcdb (grep dbdef->table($_), keys %defs ) { + my $self = "FS::$svcdb"->new; + $vfields{$svcdb} = {}; + foreach my $field ($self->virtual_fields) { # svc_Common::virtual_fields with a null svcpart returns all of them + my $pvf = $self->pvf($field); + my @list = $pvf->list; + if (scalar @list) { + $defs{$svcdb}->{$field} = { desc => $pvf->label, + type => 'select', + select_list => \@list }; + } else { + $defs{$svcdb}->{$field} = $pvf->label; + } #endif + $vfields{$svcdb}->{$field} = $pvf; + warn "\$vfields{$svcdb}->{$field} = $pvf"; + } #next $field + } #next $svcdb my @dbs = $hashref->{svcdb} ? ( $hashref->{svcdb} ) -- cgit v1.2.1 From 39e40f004fb217f825f695276d7a1b5a29a821d8 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 4 Nov 2003 10:55:42 +0000 Subject: treat serial columns as ints too! --- FS/FS/Record.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 60b25edcf..437264112 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -240,7 +240,8 @@ sub qsearch { } } elsif ( $op eq '!=' ) { if ( driver_name eq 'Pg' ) { - if ( $dbdef->table($table)->column($column)->type =~ /(int)/i ) { + my $type = $dbdef->table($table)->column($column)->type; + if ( $type =~ /(int|serial)/i ) { qq-( $column IS NOT NULL )-; } else { qq-( $column IS NOT NULL AND $column != '' )-; -- cgit v1.2.1 From 3f0c721ee27ead3504df80addd947057e4fc4c14 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 4 Nov 2003 10:57:05 +0000 Subject: finish treating serials as ints! --- FS/FS/Record.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 437264112..14dfca2cb 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -230,7 +230,8 @@ sub qsearch { if ( ! defined( $record->{$_} ) || $record->{$_} eq '' ) { if ( $op eq '=' ) { if ( driver_name eq 'Pg' ) { - if ( $dbdef->table($table)->column($column)->type =~ /(int)/i ) { + my $type = $dbdef->table($table)->column($column)->type; + if ( $type =~ /(int|serial)/i ) { qq-( $column IS NULL )-; } else { qq-( $column IS NULL OR $column = '' )-; @@ -310,7 +311,7 @@ sub qsearch { grep defined( $record->{$_} ) && $record->{$_} ne '', @real_fields ) { if ( $record->{$field} =~ /^\d+(\.\d+)?$/ - && $dbdef->table($table)->column($field)->type =~ /(int)/i + && $dbdef->table($table)->column($field)->type =~ /(int|serial)/i ) { $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_INTEGER } ); } else { -- cgit v1.2.1 From 8e70e767b1fc7384d11d38246868d17ea0ec560e Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 4 Nov 2003 17:30:15 +0000 Subject: make snarf info available to exports --- FS/FS/part_export/shellcommands.pm | 7 +++++++ FS/FS/svc_acct.pm | 18 +++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index da24e5ca7..f65638984 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -40,6 +40,13 @@ sub _export_command { { no strict 'refs'; ${$_} = $svc_acct->getfield($_) foreach $svc_acct->fields; + + my $count = 1; + foreach my $acct_snarf ( $svc_acct->acct_snarf ) { + ${"snarf_$_$count"} = shell_quote( $acct_snarf->get($_) ) + foreach qw( machine username _password ); + $count++; + } } my $cust_pkg = $svc_acct->cust_svc->cust_pkg; diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 4ea7734d4..e4acf7459 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -16,7 +16,7 @@ use Carp; use Fcntl qw(:flock); use FS::UID qw( datasrc ); use FS::Conf; -use FS::Record qw( qsearch qsearchs fields dbh ); +use FS::Record qw( qsearch qsearchs fields dbh dbdef ); use FS::svc_Common; use FS::cust_svc; use FS::part_svc; @@ -951,6 +951,22 @@ sub email { $self->username. '@'. $self->domain; } +=item acct_snarf + +Returns an array of FS::acct_snarf records associated with the account. +If the acct_snarf table does not exist or there are no associated records, +an empty list is returned + +=cut + +sub acct_snarf { + my $self = shift; + return () unless dbdef->table('acct_snarf'); + eval "use FS::acct_snarf;"; + die $@ if $@; + qsearch('acct_snarf', { 'svcnum' => $self->svcnum } ); +} + =item seconds_since TIMESTAMP Returns the number of seconds this account has been online since TIMESTAMP, -- cgit v1.2.1 From b375da4f34a65409d2e657c12e434c279c3199dc Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 4 Nov 2003 17:57:04 +0000 Subject: adding --- bin/create-fetchmailrc | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 bin/create-fetchmailrc diff --git a/bin/create-fetchmailrc b/bin/create-fetchmailrc new file mode 100644 index 000000000..7b28bfbc0 --- /dev/null +++ b/bin/create-fetchmailrc @@ -0,0 +1,46 @@ +#!/usr/bin/perl -w +# this quick hack helps you generate/maintain .fetchmailrc files from +# FS::acct_snarf data. it is run from a shellcommands export as: +# create-fetchmailrc $username $dir $snarf_machine1 $snarf_username1 $snarf__password1 $snarf_machine2 $snarf_username2 $snarf__password2 $snarf_machine3 $snarf_username3 $snarf__password3 $snarf_machine4 $snarf_username4 $snarf__password4 $snarf_machine5 $snarf_username5 $snarf__password5 $snarf_machine6 $snarf_username6 $snarf__password6 $snarf_machine7 $snarf_username7 $snarf__password7 $snarf_machine8 $snarf_username8 $snarf__password8 $snarf_machine9 $snarf_username9 $snarf__password9 $snarf_machine10 $snarf_username10 $snarf__password10 + +use strict; +use POSIX qw( setuid setsid ); + +my $header = <$filename") or die "can't open $filename: $!\n"; +chown 0600, $filename or die "can't chown 600 $filename: $!\n"; +print FETCHMAILRC $header; + +while ($ARGV[0]) { + my( $s_machine, $s_username, $s_password ) = splice( @ARGV, 0, 3 ); + print FETCHMAILRC < Date: Tue, 4 Nov 2003 18:01:18 +0000 Subject: don't overwrite otaker on cust_main! --- FS/FS/cust_main.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 1d2e9edcd..2c89bb065 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -872,7 +872,7 @@ sub check { $self->tax =~ /^(Y?)$/ or return "Illegal tax: ". $self->tax; $self->tax($1); - $self->otaker(getotaker); + $self->otaker(getotaker) unless $self->otaker; #warn "AFTER: \n". $self->_dump; -- cgit v1.2.1 From 36034232a364770860d6ac531d12e66bbbb70046 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 5 Nov 2003 10:26:46 +0000 Subject: allow letters in quota for use with edquota -p --- FS/FS/svc_acct.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index e4acf7459..4c943a710 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -786,7 +786,7 @@ sub check { or return "Illegal finger: ". $self->getfield('finger'); $self->setfield('finger', $1); - $recref->{quota} =~ /^(\d*)$/ or return "Illegal quota"; + $recref->{quota} =~ /^(\w*)$/ or return "Illegal quota"; $recref->{quota} = $1; unless ( $part_svc->part_svc_column('slipip')->columnflag eq 'F' ) { -- cgit v1.2.1 From 261b727c65d8a6cfb81ceaf8e1eb95878ce75fcf Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 5 Nov 2003 11:13:40 +0000 Subject: fixup --- bin/create-fetchmailrc | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/bin/create-fetchmailrc b/bin/create-fetchmailrc index 7b28bfbc0..e92971140 100644 --- a/bin/create-fetchmailrc +++ b/bin/create-fetchmailrc @@ -1,10 +1,10 @@ #!/usr/bin/perl -w # this quick hack helps you generate/maintain .fetchmailrc files from # FS::acct_snarf data. it is run from a shellcommands export as: -# create-fetchmailrc $username $dir $snarf_machine1 $snarf_username1 $snarf__password1 $snarf_machine2 $snarf_username2 $snarf__password2 $snarf_machine3 $snarf_username3 $snarf__password3 $snarf_machine4 $snarf_username4 $snarf__password4 $snarf_machine5 $snarf_username5 $snarf__password5 $snarf_machine6 $snarf_username6 $snarf__password6 $snarf_machine7 $snarf_username7 $snarf__password7 $snarf_machine8 $snarf_username8 $snarf__password8 $snarf_machine9 $snarf_username9 $snarf__password9 $snarf_machine10 $snarf_username10 $snarf__password10 +# create-fetchmailrc $username $dir $snarf_machine1 $snarf_username1 $snarf__password1 $snarf_machine2 $snarf_username2 $snarf__password2 ... use strict; -use POSIX qw( setuid setsid ); +use POSIX qw( setuid setgid ); my $header = <$filename") or die "can't open $filename: $!\n"; -chown 0600, $filename or die "can't chown 600 $filename: $!\n"; +chown $uid, $gid, $filename or die "can't chown $uid.$gid $filename: $!\n"; +chmod 0600, $filename or die "can't chmod 600 $filename: $!\n"; print FETCHMAILRC $header; while ($ARGV[0]) { @@ -33,14 +37,9 @@ END close FETCHMAILRC; -my $gid = scalar(getgrnam($username)) or die "can't find $username's gid\n"; -my $uid = scalar(getpwnam($username)) or die "can't find $username's uid\n"; - setgid($gid) or die "can't setgid $gid\n"; setuid($uid) or die "can't setuid $uid\n"; +$ENV{HOME} = $homedir; -exec(qw( fetchmail -a -K --antispam "550,451" -d 180 -f ), $filename); -die "can't execute fetchmail: $!"; - - +system(qq(fetchmail -a -K --antispam "550,451" -d 180 -f $filename)); -- cgit v1.2.1 From ef27ba4362bc3ffac006237b5a73c496a5ee51a0 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 6 Nov 2003 13:37:21 +0000 Subject: removing (rewritten as a proper html report) --- FS/bin/freeside-receivables-report | 217 ------------------------------------- 1 file changed, 217 deletions(-) delete mode 100755 FS/bin/freeside-receivables-report diff --git a/FS/bin/freeside-receivables-report b/FS/bin/freeside-receivables-report deleted file mode 100755 index f3ad2a1a6..000000000 --- a/FS/bin/freeside-receivables-report +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/perl -Tw - -use strict; -use Date::Parse; -use Time::Local; -use Getopt::Std; -use Text::Template; -use Net::SMTP; -use Mail::Header; -use Mail::Internet; -use FS::Conf; -use FS::UID qw(adminsuidsetup); -use FS::Record qw(qsearch); -use FS::cust_main; - - -&untaint_argv; #what it sounds like (eww) -use vars qw($opt_v $opt_p $opt_m $opt_e $opt_t $report_lines $report_template @buf $header); -getopts("vpmet:"); #switches - -#we're at now now (and later). -my($_date)= $^T; - -# Get the current month -my ($sec,$min,$hour,$mday,$mon,$year) = - (localtime($_date) )[0,1,2,3,4,5]; -$mon++; -$year += 1900; - -# Login to the database -my $user = shift or die &usage; -adminsuidsetup $user; - -# Get the needed configuration files -my $conf = new FS::Conf; -my $lpr = $conf->config('lpr'); -my $email = $conf->config('email'); -my $smtpmachine = $conf->config('smtpmachine'); -my $mail_sender = $conf->exists('invoice_from') ? $conf->config('invoice_from') : - 'postmaster'; -my @report_template = $conf->config('report_template') - or die "cannot load config file report_template"; -$report_lines = 0; - foreach ( grep /report_lines\(\d+\)/, @report_template ) { #kludgy :/ - /report_lines\((\d+)\)/; - $report_lines += $1; -} -die "no report_lines() functions in template?" unless $report_lines; -$report_template = new Text::Template ( - TYPE => 'ARRAY', - SOURCE => [ map "$_\n", @report_template ], -) or die "can't create new Text::Template object: $Text::Template::ERROR"; - - -my(@customers)=qsearch('cust_main',{}); -if (scalar(@customers) == 0) -{ - exit 1; -} - -# Open print and email pipes -# $lpr and opt_p for printing -# $email and opt_m for email - -if ($lpr && $opt_p) -{ - open(LPR, "|$lpr"); -} - -if ($email && $opt_m) -{ - $ENV{MAILADDRESS} = $mail_sender; - $header = new Mail::Header ( [ - "From: Account Processor", - "To: $email", - "Sender: $mail_sender", - "Reply-To: $mail_sender", - "Subject: Receivables", - ] ); -} - -my $total = 0; - - -# Now I can start looping -foreach my $customer (@customers) -{ - my $custnum = $customer->getfield('custnum'); - my $first = $customer->getfield('first'); - my $last = $customer->getfield('last'); - my $company = $customer->getfield('company'); - my $daytime = $customer->getfield('daytime'); - my $balance = $customer->balance; - - - if ($balance != 0) { - $total += $balance; - push @buf, sprintf(qq{%8d %-32.32s %12s %9.2f}, - $custnum, - $first . " " . $last . " " . $company, - $daytime, - $balance); - - } - -} - -push @buf, ('', sprintf(qq{%61s}, "========="), sprintf(qq{%61.2f}, $total)); - -sub FS::receivables_report::_template::report_lines { - my $lines = shift; - map { - scalar(@buf) ? shift @buf : '' ; - } - ( 1 .. $lines ); -} - -$FS::receivables_report::_template::title = " R E C E I V A B L E S "; -$FS::receivables_report::_template::title = $opt_t if $opt_t; -$FS::receivables_report::_template::page = 1; -$FS::receivables_report::_template::date = $_date; -$FS::receivables_report::_template::date = $_date; -$FS::receivables_report::_template::total_pages = - int( scalar(@buf) / $report_lines); -$FS::receivables_report::_template::total_pages++ if scalar(@buf) % $report_lines; - -my @report; -while (@buf) { - push @report, split("\n", - $report_template->fill_in( PACKAGE => 'FS::receivables_report::_template' ) - ); - $FS::receivables_report::_template::page++; -} - -if ($opt_v) { - print map "$_\n", @report; -} -if($lpr && $opt_p) -{ - print LPR map "$_\n", @report; - print LPR "\f" if $opt_e; - close LPR || die "Could not close printer: $lpr\n"; -} -if($email && $opt_m) -{ - my $message = new Mail::Internet ( - 'Header' => $header, - 'Body' => [ (@report) ], - ); - $!=0; - $message->smtpsend( Host => "$smtpmachine" ) - or die "can't send report to $email via $smtpmachine: $!"; -} - - -# subroutines - -sub untaint_argv { - foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV - $ARGV[$_] =~ /^([\w\-\/ \.]*)$/ || die "Illegal argument \"$ARGV[$_]\""; - $ARGV[$_]=$1; - } -} - -sub usage { - die "Usage:\n\n freeside-receivables-report [-v] [-p] [-e] user\n"; -} - -=head1 NAME - -freeside-receivables-report - Prints or emails outstanding receivables. - -=head1 SYNOPSIS - - freeside-receivables-report [-v] [-p] [-m] [-e] [-t "title"] user - -=head1 DESCRIPTION - -Prints or emails outstanding receivables - -B<-v>: Verbose - Prints records to STDOUT. - -B<-p>: Print to printer lpr as found in the conf directory. - -B<-m>: Mail output to user found in the Conf email file. - -B<-e>: Print a final form feed to the printer. - -B<-t>: supply a title for the top of each page. - -user: From the mapsecrets file - see config.html from the base documentation - -=head1 VERSION - -$Id: freeside-receivables-report,v 1.6 2002-09-09 22:57:34 ivan Exp $ - -=head1 BUGS - -Yes..... Use at your own risk. No guarantees or warrantees of any -kind apply to this program. Parts of this program are hacked from -other GNU licensed software created mainly by Ivan Kohler. - -This is released under the GNU Public License. See www.gnu.org -for more information regarding this license. - -=head1 SEE ALSO - -L, config.html from the base documentation - -=head1 AUTHOR - -Jeff Finucane - -based on print-batch by Joel Griffiths - -=cut - -- cgit v1.2.1 From 3a4b4ab1673766048a33c970f5926b2bf07b535c Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 6 Nov 2003 13:39:29 +0000 Subject: "current receivables" -> A/R Aging summary --- httemplate/index.html | 4 +- httemplate/search/report_receivables.cgi | 191 +++++++++++++++++++++++++++++-- 2 files changed, 183 insertions(+), 12 deletions(-) diff --git a/httemplate/index.html b/httemplate/index.html index b95d2ac73..d863f3f3e 100644 --- a/httemplate/index.html +++ b/httemplate/index.html @@ -68,9 +68,9 @@
    • all invoices (by invoice number) (by date) (by customer number)
    Payment report (by type and/or date range) -

    Financial reports +

    Accounts Receivable Aging Summary +

    (old) Financial reports (being rewritten)
      -
    • current receivables
    • tax reports
    • credit card receipts
    • credit memos diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index fdd3779a9..241ca581c 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -1,19 +1,190 @@ <% + my $sql = < extract(epoch from now())-2592000 + and cust_main.custnum = cust_bill.custnum + ) + ,0 + ) as owed_0_30, -print '
      ';
      -while() {
      -  print $_;
      -}
      -print '
      '; + coalesce( + ( select sum( charged + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) -print ''; + ) + from cust_bill + where cust_bill._date > extract(epoch from now())-5184000 + and cust_bill._date <= extract(epoch from now())-2592000 + and cust_main.custnum = cust_bill.custnum + ) + ,0 + ) as owed_30_60, -%> + coalesce( + ( select sum( charged + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + + ) + from cust_bill + where cust_bill._date > extract(epoch from now())-7776000 + and cust_bill._date <= extract(epoch from now())-5184000 + and cust_main.custnum = cust_bill.custnum + ) + ,0 + ) as owed_60_90, + + coalesce( + ( select sum( charged + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + + ) + from cust_bill + where cust_bill._date <= extract(epoch from now())-7776000 + and cust_main.custnum = cust_bill.custnum + ) + ,0 + ) as owed_90_plus, + + coalesce( + ( select sum( charged + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + + ) + from cust_bill + where cust_main.custnum = cust_bill.custnum + ) + ,0 + ) as owed_total + +from cust_main + +where 0 < + coalesce( + ( select sum( charged + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + - coalesce( + ( select sum(amount) from cust_bill_pay + where cust_bill.invnum = cust_bill_pay.invnum ) + ,0 + ) + + ) + from cust_bill + where cust_main.custnum = cust_bill.custnum + ) + ,0 + ) +order by company, last + +END + + + #order by! + + #the grep (and the sort ) should be pushed down to SQL + #my @cust_main = sort { $a->company cmp $b->company + # || $a->last cmp $b->last } + # grep { $_->balance } + # qsearch('cust_main', {} ); + + my $totals_table = table(). 'Total'. + '0-30'. + '30-60'. + '60-90'. + '90+'. + 'total'. + ''; + $totals_table = ''; + + my $sth = dbh->prepare($sql) or die dbh->errstr; + $sth->execute or die $sth->errstr; + +%> +<%= header('Accounts Receivable Aging Summary', menubar( 'Main Menu'=>$p, ) ) %> +<%= $totals_table %> +<%= table() %> + + Customer + 0-30 + 30-60 + 60-90 + 90+ + Total + +<% while ( my $row = $sth->fetchrow_hashref() ) { %> + + <%= $row->{'custnum'} %>: + <%= $row->{'company'} ? $row->{'company'}. ' (' : '' %> + <%= $row->{'last'}. ', '. $row->{'first'} %> + <%= $row->{'company'} ? ')' : '' %> + + $<%= sprintf("%.2f", $row->{'owed_0_30'} ) %> + $<%= sprintf("%.2f", $row->{'owed_30_60'} ) %> + $<%= sprintf("%.2f", $row->{'owed_60_90'} ) %> + $<%= sprintf("%.2f", $row->{'owed_90_plus'} ) %> + $<%= sprintf("%.2f", $row->{'owed_total'} ) %> + +<% } %> + +<%= $totals_table %> + + -- cgit v1.2.1 From 4d88d3dc9debe254dddf996a9df936c482681844 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 6 Nov 2003 13:40:31 +0000 Subject: removing bin/freeside-receivables-report --- FS/MANIFEST | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/MANIFEST b/FS/MANIFEST index ad013c848..934100af5 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -19,7 +19,6 @@ bin/freeside-email bin/freeside-expiration-alerter bin/freeside-queued bin/freeside-radgroup -bin/freeside-receivables-report bin/freeside-reexport bin/freeside-selfservice-server bin/freeside-setinvoice @@ -45,6 +44,7 @@ FS/UI/Gtk.pm FS/UI/agent.pm FS/UID.pm FS/Msgcat.pm +FS/acct_snarf.pm FS/agent.pm FS/agent_type.pm FS/cust_bill.pm -- cgit v1.2.1 From 0d83329036f8768c2bc974c3d8713a6089479610 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 6 Nov 2003 13:56:50 +0000 Subject: link to customer, don't show custnum --- httemplate/search/report_receivables.cgi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index 241ca581c..9938abdc6 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -172,10 +172,10 @@ END <% while ( my $row = $sth->fetchrow_hashref() ) { %> - <%= $row->{'custnum'} %>: + <%= $row->{'company'} ? $row->{'company'}. ' (' : '' %> <%= $row->{'last'}. ', '. $row->{'first'} %> - <%= $row->{'company'} ? ')' : '' %> + <%= $row->{'company'} ? ')' : '' %> $<%= sprintf("%.2f", $row->{'owed_0_30'} ) %> $<%= sprintf("%.2f", $row->{'owed_30_60'} ) %> -- cgit v1.2.1 From 1203c14b5b34bc848c3176899767a4fbc13f914a Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 6 Nov 2003 14:00:27 +0000 Subject: typo --- httemplate/search/report_receivables.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index 9938abdc6..f0274d026 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -172,7 +172,7 @@ END <% while ( my $row = $sth->fetchrow_hashref() ) { %> - + <%= $row->{'company'} ? $row->{'company'}. ' (' : '' %> <%= $row->{'last'}. ', '. $row->{'first'} %> <%= $row->{'company'} ? ')' : '' %> -- cgit v1.2.1 From 5a77da3b812de3d218edc3faba3aefc777e5ca71 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 6 Nov 2003 14:08:14 +0000 Subject: ack! count credits, not payments twice --- httemplate/search/report_receivables.cgi | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index f0274d026..1fd7d03c7 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -12,8 +12,8 @@ select *, ,0 ) - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) + ( select sum(amount) from cust_credit_bill + where cust_bill.invnum = cust_credit_bill.invnum ) ,0 ) @@ -33,8 +33,8 @@ select *, ,0 ) - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) + ( select sum(amount) from cust_credit_bill + where cust_bill.invnum = cust_credit_bill.invnum ) ,0 ) @@ -55,8 +55,8 @@ select *, ,0 ) - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) + ( select sum(amount) from cust_credit_bill + where cust_bill.invnum = cust_credit_bill.invnum ) ,0 ) @@ -77,8 +77,8 @@ select *, ,0 ) - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) + ( select sum(amount) from cust_credit_bill + where cust_bill.invnum = cust_credit_bill.invnum ) ,0 ) @@ -98,8 +98,8 @@ select *, ,0 ) - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) + ( select sum(amount) from cust_credit_bill + where cust_bill.invnum = cust_credit_bill.invnum ) ,0 ) @@ -121,8 +121,8 @@ where 0 < ,0 ) - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) + ( select sum(amount) from cust_credit_bill + where cust_bill.invnum = cust_credit_bill.invnum) ,0 ) -- cgit v1.2.1 From 9d3055def8da004cc1b7c7f9b1ce88da3eca3bba Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 6 Nov 2003 14:45:59 +0000 Subject: add totals & simplify expressions --- httemplate/search/report_receivables.cgi | 147 +++++++++---------------------- 1 file changed, 41 insertions(+), 106 deletions(-) diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index 1fd7d03c7..d0665164c 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -1,24 +1,25 @@ <% - my $sql = < extract(epoch from now())-2592000 and cust_main.custnum = cust_bill.custnum ) @@ -26,20 +27,7 @@ select *, ) as owed_0_30, coalesce( - ( select sum( charged - - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) - ,0 - ) - - coalesce( - ( select sum(amount) from cust_credit_bill - where cust_bill.invnum = cust_credit_bill.invnum ) - ,0 - ) - - ) - from cust_bill + ( select $charged from cust_bill where cust_bill._date > extract(epoch from now())-5184000 and cust_bill._date <= extract(epoch from now())-2592000 and cust_main.custnum = cust_bill.custnum @@ -48,20 +36,7 @@ select *, ) as owed_30_60, coalesce( - ( select sum( charged - - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) - ,0 - ) - - coalesce( - ( select sum(amount) from cust_credit_bill - where cust_bill.invnum = cust_credit_bill.invnum ) - ,0 - ) - - ) - from cust_bill + ( select $charged from cust_bill where cust_bill._date > extract(epoch from now())-7776000 and cust_bill._date <= extract(epoch from now())-5184000 and cust_main.custnum = cust_bill.custnum @@ -70,20 +45,7 @@ select *, ) as owed_60_90, coalesce( - ( select sum( charged - - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) - ,0 - ) - - coalesce( - ( select sum(amount) from cust_credit_bill - where cust_bill.invnum = cust_credit_bill.invnum ) - ,0 - ) - - ) - from cust_bill + ( select $charged from cust_bill where cust_bill._date <= extract(epoch from now())-7776000 and cust_main.custnum = cust_bill.custnum ) @@ -91,43 +53,19 @@ select *, ) as owed_90_plus, coalesce( - ( select sum( charged - - coalesce( - ( select sum(amount) from cust_bill_pay - where cust_bill.invnum = cust_bill_pay.invnum ) - ,0 - ) - - coalesce( - ( select sum(amount) from cust_credit_bill - where cust_bill.invnum = cust_credit_bill.invnum ) - ,0 - ) - - ) - from cust_bill + ( select $charged from cust_bill where cust_main.custnum = cust_bill.custnum ) ,0 ) as owed_total +END -from cust_main + my $sql = <company cmp $b->company - # || $a->last cmp $b->last } - # grep { $_->balance } - # qsearch('cust_main', {} ); - - my $totals_table = table(). 'Total'. - '0-30'. - '30-60'. - '60-90'. - '90+'. - 'total'. - ''; - $totals_table = ''; + my $total_sql = "select $owed_cols"; my $sth = dbh->prepare($sql) or die dbh->errstr; $sth->execute or die $sth->errstr; + my $total_sth = dbh->prepare($total_sql) or die dbh->errstr; + $total_sth->execute or die $total_sth->errstr; + %> <%= header('Accounts Receivable Aging Summary', menubar( 'Main Menu'=>$p, ) ) %> -<%= $totals_table %> <%= table() %> Customer @@ -184,7 +108,18 @@ END $<%= sprintf("%.2f", $row->{'owed_total'} ) %> <% } %> +<% my $row = $total_sth->fetchrow_hashref(); %> + +   + + + Total + $<%= sprintf("%.2f", $row->{'owed_0_30'} ) %> + $<%= sprintf("%.2f", $row->{'owed_30_60'} ) %> + $<%= sprintf("%.2f", $row->{'owed_60_90'} ) %> + $<%= sprintf("%.2f", $row->{'owed_90_plus'} ) %> + $<%= sprintf("%.2f", $row->{'owed_total'} ) %> + -<%= $totals_table %> -- cgit v1.2.1 From 99d11ad690b5a0e35e9c5388fc1ae5935df3f12b Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 7 Nov 2003 07:56:23 +0000 Subject: also show RADIUS usage information for sqlradius_withdomain exports --- httemplate/view/svc_acct.cgi | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/httemplate/view/svc_acct.cgi b/httemplate/view/svc_acct.cgi index 78c3a3809..58591fcb5 100755 --- a/httemplate/view/svc_acct.cgi +++ b/httemplate/view/svc_acct.cgi @@ -57,7 +57,9 @@ function areyousure(href) { <% #if ( $cust_pkg && $cust_pkg->part_pkg->plan eq 'sqlradacct_hour' ) { -if ( $part_svc->part_export('sqlradius') ) { +if ( $part_svc->part_export('sqlradius') + || $part_svc->part_export('sqlradius_withdomain') +) { my $last_bill; my %plandata; -- cgit v1.2.1 From 26c21ea9dbbca328c91ab578c7c7c0c21927244e Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 7 Nov 2003 08:36:31 +0000 Subject: finish fixing sqlradius_withdomain time calculations --- FS/FS/cust_svc.pm | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index 6adb571d3..139dd9db6 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -353,10 +353,13 @@ for records where B is not "svc_acct". sub seconds_since_sqlradacct { my($self, $start, $end) = @_; - my $username = $self->svc_x->username; + my $svc_x = $self->svc_x; - my @part_export = $self->part_svc->part_export('sqlradius') - or die "no sqlradius export configured for this service type"; + my @part_export = $self->part_svc->part_export('sqlradius'); + push @part_export, $self->part_svc->part_export('sqlradius_withdomain'); + die "no sqlradius or sqlradius_withdomain export configured for this". + "service type" + unless @part_export; #or return undef; my $seconds = 0; @@ -378,6 +381,15 @@ sub seconds_since_sqlradacct { $str2time = 'extract(epoch from '; } + my $username; + if ( $part_export->exporttype eq 'sqlradius' ) { + $username = $svc_x->username; + } elsif ( $part_export->exporttype eq 'sqlradius_withdomain' ) { + $username = $svc_x->email; + } else { + die 'unknown exporttype '. $part_export->exporttype; + } + my $query; #find closed sessions completely within the given range -- cgit v1.2.1 From 1943cb7bba003c921cd4bfa3e108524b697e1fb5 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 7 Nov 2003 08:39:04 +0000 Subject: also fix attribute_since_sqlradacct for data charging --- FS/FS/cust_svc.pm | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index 139dd9db6..af0abf7f1 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -468,10 +468,13 @@ for records where B is not "svc_acct". sub attribute_since_sqlradacct { my($self, $start, $end, $attrib) = @_; - my $username = $self->svc_x->username; + my $svc_x = $self->svc_x; - my @part_export = $self->part_svc->part_export('sqlradius') - or die "no sqlradius export configured for this service type"; + my @part_export = $self->part_svc->part_export('sqlradius'); + push @part_export, $self->part_svc->part_export('sqlradius_withdomain'); + die "no sqlradius or sqlradius_withdomain export configured for this". + "service type" + unless @part_export; #or return undef; my $sum = 0; @@ -494,6 +497,15 @@ sub attribute_since_sqlradacct { $str2time = 'extract(epoch from '; } + my $username; + if ( $part_export->exporttype eq 'sqlradius' ) { + $username = $svc_x->username; + } elsif ( $part_export->exporttype eq 'sqlradius_withdomain' ) { + $username = $svc_x->email; + } else { + die 'unknown exporttype '. $part_export->exporttype; + } + my $sth = $dbh->prepare("SELECT SUM($attrib) FROM radacct WHERE UserName = ? -- cgit v1.2.1 From 8bd4923ffb0661febc36ff84f3e9b3e825e16973 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 7 Nov 2003 10:53:35 +0000 Subject: update jscalendar --- httemplate/elements/calendar-en.js | 75 ++++- httemplate/elements/calendar-setup.js | 100 ++++-- httemplate/elements/calendar-win2k-2.css | 52 ++- httemplate/elements/calendar.js | 562 ++++++++++++++++++++++--------- httemplate/elements/calendar_stripped.js | 14 +- 5 files changed, 604 insertions(+), 199 deletions(-) diff --git a/httemplate/elements/calendar-en.js b/httemplate/elements/calendar-en.js index bc075bb74..e9291e1b7 100644 --- a/httemplate/elements/calendar-en.js +++ b/httemplate/elements/calendar-en.js @@ -1,4 +1,15 @@ // ** I18N + +// Calendar EN language +// Author: Mihai Bazon, +// Encoding: any +// Distributed under the same terms as the calendar itself. + +// For translators: please use UTF-8 if possible. We strongly believe that +// Unicode is the answer to a real internationalized world. Also please +// include your contact information in the header, as can be seen above. + +// full day names Calendar._DN = new Array ("Sunday", "Monday", @@ -8,6 +19,31 @@ Calendar._DN = new Array "Friday", "Saturday", "Sunday"); + +// Please note that the following array of short day names (and the same goes +// for short month names, _SMN) isn't absolutely necessary. We give it here +// for exemplification on how one can customize the short day names, but if +// they are simply the first N letters of the full name you can simply say: +// +// Calendar._SDN_len = N; // short day name length +// Calendar._SMN_len = N; // short month name length +// +// If N = 3 then this is not needed either since we assume a value of 3 if not +// present, to be compatible with translation files that were written before +// this feature. + +// short day names +Calendar._SDN = new Array +("Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun"); + +// full month names Calendar._MN = new Array ("January", "February", @@ -22,9 +58,41 @@ Calendar._MN = new Array "November", "December"); +// short month names +Calendar._SMN = new Array +("Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec"); + // tooltips Calendar._TT = {}; -Calendar._TT["TOGGLE"] = "Toggle first day of week"; +Calendar._TT["INFO"] = "About the calendar"; + +Calendar._TT["ABOUT"] = +"DHTML Date/Time Selector\n" + +"(c) dynarch.com 2002-2003\n" + // don't translate this this ;-) +"For latest version visit: http://dynarch.com/mishoo/calendar.epl\n" + +"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + +"\n\n" + +"Date selection:\n" + +"- Use the \xab, \xbb buttons to select year\n" + +"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" + +"- Hold mouse button on any of the above buttons for faster selection."; +Calendar._TT["ABOUT_TIME"] = "\n\n" + +"Time selection:\n" + +"- Click on any of the time parts to increase it\n" + +"- or Shift-click to decrease it\n" + +"- or click and drag for faster selection."; + Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)"; Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)"; Calendar._TT["GO_TODAY"] = "Go Today"; @@ -37,9 +105,10 @@ Calendar._TT["MON_FIRST"] = "Display Monday first"; Calendar._TT["SUN_FIRST"] = "Display Sunday first"; Calendar._TT["CLOSE"] = "Close"; Calendar._TT["TODAY"] = "Today"; +Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value"; // date formats -Calendar._TT["DEF_DATE_FORMAT"] = "y-mm-dd"; -Calendar._TT["TT_DATE_FORMAT"] = "D, M d"; +Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d"; +Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e"; Calendar._TT["WK"] = "wk"; diff --git a/httemplate/elements/calendar-setup.js b/httemplate/elements/calendar-setup.js index de6ff591f..0dc3caa00 100644 --- a/httemplate/elements/calendar-setup.js +++ b/httemplate/elements/calendar-setup.js @@ -1,20 +1,25 @@ -/* Copyright Mihai Bazon, 2002, 2003 | http://students.infoiasi.ro/~mishoo +/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ * --------------------------------------------------------------------------- * * The DHTML Calendar * * Details and latest version at: - * http://students.infoiasi.ro/~mishoo/site/calendar.epl + * http://dynarch.com/mishoo/calendar.epl * - * Feel free to use this script under the terms of the GNU Lesser General - * Public License, as long as you do not remove or alter this notice. + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html * * This file defines helper functions for setting up the calendar. They are * intended to help non-programmers get a working calendar on their site - * quickly. + * quickly. This script should not be seen as part of the calendar. It just + * shows you what one can do with the calendar, while in the same time + * providing a quick and simple method for setting it up. If you need + * exhaustive customization of the calendar creation process feel free to + * modify this code to suit your needs (this is recommended and much better + * than modifying calendar.js itself). */ -// $Id: calendar-setup.js,v 1.2 2003-09-30 08:23:15 ivan Exp $ +// $Id: calendar-setup.js,v 1.3 2003-11-07 10:53:35 ivan Exp $ /** * This function "patches" an input field (or other element) to use a calendar @@ -31,13 +36,19 @@ * ifFormat | date format that will be stored in the input field * daFormat | the date format that will be used to display the date in displayArea * singleClick | (true/false) wether the calendar is in single click mode or not (default: true) - * mondayFirst | (true/false) if true Monday is the first day of week, Sunday otherwise (default: false) + * mondayFirst | (true/false) if true Monday is the first day of week, Sunday otherwise (default: true) * align | alignment (default: "Bl"); if you don't know what's this see the calendar documentation * range | array with 2 elements. Default: [1900, 2999] -- the range of years available * weekNumbers | (true/false) if it's true (default) the calendar will display week numbers * flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID * flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar) * disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar + * onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay) + * onClose | function that gets called when the calendar is closed. [default] + * onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar. + * date | the date that the calendar will be initially displayed to + * showsTime | default: false; if true the calendar will include a time selector + * timeFormat | the time format; can be "12" or "24", default is "12" * * None of them is required, they all have default values. However, if you * pass none of "inputField", "displayArea" or "button" you'll get a warning @@ -46,20 +57,27 @@ Calendar.setup = function (params) { function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } }; - param_default("inputField", null); - param_default("displayArea", null); - param_default("button", null); - param_default("eventName", "click"); - param_default("ifFormat", "y/mm/dd"); - param_default("daFormat", "y/mm/dd"); - param_default("singleClick", true); - param_default("disableFunc", null); - param_default("mondayFirst", false); - param_default("align", "Bl"); - param_default("range", [1900, 2999]); - param_default("weekNumbers", true); - param_default("flat", null); - param_default("flatCallback", null); + param_default("inputField", null); + param_default("displayArea", null); + param_default("button", null); + param_default("eventName", "click"); + param_default("ifFormat", "%Y/%m/%d"); + param_default("daFormat", "%Y/%m/%d"); + param_default("singleClick", true); + param_default("disableFunc", null); + param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined + param_default("mondayFirst", true); + param_default("align", "Bl"); + param_default("range", [1900, 2999]); + param_default("weekNumbers", true); + param_default("flat", null); + param_default("flatCallback", null); + param_default("onSelect", null); + param_default("onClose", null); + param_default("onUpdate", null); + param_default("date", null); + param_default("showsTime", false); + param_default("timeFormat", "24"); var tmp = ["inputField", "displayArea", "button"]; for (var i in tmp) { @@ -90,6 +108,9 @@ Calendar.setup = function (params) { if (cal.params.singleClick && cal.dateClicked) { cal.callCloseHandler(); } + if (typeof cal.params.onUpdate == "function") { + cal.params.onUpdate(cal); + } }; if (params.flat != null) { @@ -98,11 +119,13 @@ Calendar.setup = function (params) { alert("Calendar.setup:\n Flat specified but can't find parent."); return false; } - var cal = new Calendar(params.mondayFirst, null, onSelect); + var cal = new Calendar(params.mondayFirst, params.date, params.onSelect || onSelect); + cal.showsTime = params.showsTime; + cal.time24 = (params.timeFormat == "24"); cal.params = params; cal.weekNumbers = params.weekNumbers; cal.setRange(params.range[0], params.range[1]); - cal.setDisabledHandler(params.disableFunc); + cal.setDateStatusHandler(params.dateStatusFunc); cal.create(params.flat); cal.show(); return false; @@ -113,23 +136,28 @@ Calendar.setup = function (params) { var dateEl = params.inputField || params.displayArea; var dateFmt = params.inputField ? params.ifFormat : params.daFormat; var mustCreate = false; + var cal = window.calendar; if (!window.calendar) { - window.calendar = new Calendar(params.mondayFirst, null, onSelect, function(cal) { cal.hide(); }); - window.calendar.weekNumbers = params.weekNumbers; + window.calendar = cal = new Calendar(params.mondayFirst, + params.date, + params.onSelect || onSelect, + params.onClose || function(cal) { cal.hide(); }); + cal.showsTime = params.showsTime; + cal.time24 = (params.timeFormat == "24"); + cal.weekNumbers = params.weekNumbers; mustCreate = true; } else { - window.calendar.hide(); - } - window.calendar.setRange(params.range[0], params.range[1]); - window.calendar.params = params; - window.calendar.setDisabledHandler(params.disableFunc); - window.calendar.setDateFormat(dateFmt); - if (mustCreate) { - window.calendar.create(); + cal.hide(); } - window.calendar.parseDate(dateEl.value || dateEl.innerHTML); - window.calendar.refresh(); - window.calendar.showAtElement(params.displayArea || params.inputField, params.align); + cal.setRange(params.range[0], params.range[1]); + cal.params = params; + cal.setDateStatusHandler(params.dateStatusFunc); + cal.setDateFormat(dateFmt); + if (mustCreate) + cal.create(); + cal.parseDate(dateEl.value || dateEl.innerHTML); + cal.refresh(); + cal.showAtElement(params.displayArea || params.inputField, params.align); return false; }; }; diff --git a/httemplate/elements/calendar-win2k-2.css b/httemplate/elements/calendar-win2k-2.css index 398e78efe..9727d1b9a 100644 --- a/httemplate/elements/calendar-win2k-2.css +++ b/httemplate/elements/calendar-win2k-2.css @@ -37,6 +37,10 @@ border-left: 1px solid #fff; } +.calendar .nav { + background: transparent url(menuarrow.gif) no-repeat 100% 100%; +} + .calendar thead .title { /* This holds the current "month, year" */ font-weight: bold; padding: 1px; @@ -69,7 +73,7 @@ border-bottom: 2px solid #000; border-left: 2px solid #fff; padding: 0px; - background: #e4d8e0; + background-color: #e4d8e0; } .calendar thead .active { /* Active (pressed) buttons in header */ @@ -78,7 +82,7 @@ border-right: 1px solid #fff; border-bottom: 1px solid #fff; border-left: 1px solid #000; - background: #c4b8c0; + background-color: #c4b8c0; } /* The body part -- contains all the days in month. */ @@ -197,11 +201,16 @@ padding: 1px; } -.combo .label { +.combo .label, +.combo .label-IEfix { text-align: center; padding: 1px; } +.combo .label-IEfix { + width: 4em; +} + .combo .active { background: #d4c8d0; padding: 0px; @@ -215,3 +224,40 @@ background: #408; color: #fea; } + +.calendar td.time { + border-top: 1px solid #000; + padding: 1px 0px; + text-align: center; + background-color: #f4f0e8; +} + +.calendar td.time .hour, +.calendar td.time .minute, +.calendar td.time .ampm { + padding: 0px 3px 0px 4px; + border: 1px solid #889; + font-weight: bold; + background-color: #fff; +} + +.calendar td.time .ampm { + text-align: center; +} + +.calendar td.time .colon { + padding: 0px 2px 0px 3px; + font-weight: bold; +} + +.calendar td.time span.hilite { + border-color: #000; + background-color: #766; + color: #fff; +} + +.calendar td.time span.active { + border-color: #f00; + background-color: #000; + color: #0f0; +} diff --git a/httemplate/elements/calendar.js b/httemplate/elements/calendar.js index 772931b30..3c028cc76 100644 --- a/httemplate/elements/calendar.js +++ b/httemplate/elements/calendar.js @@ -1,23 +1,23 @@ -/* Copyright Mihai Bazon, 2002, 2003 | http://students.infoiasi.ro/~mishoo - * --------------------------------------------------------------------------- +/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ + * ------------------------------------------------------------------ * - * The DHTML Calendar, version 0.9.3 "It's still alive & keeps rocking" + * The DHTML Calendar, version 0.9.5 "Your favorite time, bis" * * Details and latest version at: - * http://students.infoiasi.ro/~mishoo/site/calendar.epl + * http://dynarch.com/mishoo/calendar.epl * - * Feel free to use this script under the terms of the GNU Lesser General - * Public License, as long as you do not remove or alter this notice. + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html */ -// $Id: calendar.js,v 1.2 2003-09-30 08:23:15 ivan Exp $ +// $Id: calendar.js,v 1.3 2003-11-07 10:53:35 ivan Exp $ /** The Calendar object constructor. */ Calendar = function (mondayFirst, dateStr, onSelected, onClose) { // member variables this.activeDiv = null; this.currentDateEl = null; - this.checkDisabled = null; + this.getDateStatus = null; this.timeout = null; this.onSelected = onSelected || null; this.onClose = onClose || null; @@ -32,6 +32,8 @@ Calendar = function (mondayFirst, dateStr, onSelected, onClose) { this.mondayFirst = mondayFirst; this.dateStr = dateStr; this.ar_days = null; + this.showsTime = false; + this.time24 = true; // HTML elements this.table = null; this.element = null; @@ -48,19 +50,23 @@ Calendar = function (mondayFirst, dateStr, onSelected, onClose) { this.dateClicked = false; // one-time initializations - if (!Calendar._DN3) { + if (typeof Calendar._SDN == "undefined") { // table of short day names + if (typeof Calendar._SDN_len == "undefined") + Calendar._SDN_len = 3; var ar = new Array(); for (var i = 8; i > 0;) { - ar[--i] = Calendar._DN[i].substr(0, 3); + ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len); } - Calendar._DN3 = ar; + Calendar._SDN = ar; // table of short month names + if (typeof Calendar._SMN_len == "undefined") + Calendar._SMN_len = 3; ar = new Array(); for (var i = 12; i > 0;) { - ar[--i] = Calendar._MN[i].substr(0, 3); + ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len); } - Calendar._MN3 = ar; + Calendar._SMN = ar; } }; @@ -73,17 +79,23 @@ Calendar._C = null; Calendar.is_ie = ( /msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent) ); -// short day names array (initialized at first constructor call) -Calendar._DN3 = null; +/// detect Opera browser +Calendar.is_opera = /opera/i.test(navigator.userAgent); -// short month names array (initialized at first constructor call) -Calendar._MN3 = null; +/// detect KHTML-based browsers +Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent); // BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate // library, at some point. Calendar.getAbsolutePos = function(el) { - var r = { x: el.offsetLeft, y: el.offsetTop }; + var SL = 0, ST = 0; + var is_div = /^div$/i.test(el.tagName); + if (is_div && el.scrollLeft) + SL = el.scrollLeft; + if (is_div && el.scrollTop) + ST = el.scrollTop; + var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST }; if (el.offsetParent) { var tmp = Calendar.getAbsolutePos(el.offsetParent); r.x += tmp.x; @@ -147,9 +159,10 @@ Calendar.getTargetElement = function(ev) { }; Calendar.stopEvent = function(ev) { + ev || (ev = window.event); if (Calendar.is_ie) { - window.event.cancelBubble = true; - window.event.returnValue = false; + ev.cancelBubble = true; + ev.returnValue = false; } else { ev.preventDefault(); ev.stopPropagation(); @@ -162,7 +175,7 @@ Calendar.addEvent = function(el, evname, func) { el.attachEvent("on" + evname, func); } else if (el.addEventListener) { // Gecko / W3C el.addEventListener(evname, func, true); - } else { // Opera (or old browsers) + } else { el["on" + evname] = func; } }; @@ -172,7 +185,7 @@ Calendar.removeEvent = function(el, evname, func) { el.detachEvent("on" + evname, func); } else if (el.removeEventListener) { // Gecko / W3C el.removeEventListener(evname, func, true); - } else { // Opera (or old browsers) + } else { el["on" + evname] = null; } }; @@ -244,9 +257,13 @@ Calendar.showMonthsCombo = function () { var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()]; Calendar.addClass(mon, "active"); cal.activeMonth = mon; - mc.style.left = cd.offsetLeft + "px"; - mc.style.top = (cd.offsetTop + cd.offsetHeight) + "px"; - mc.style.display = "block"; + var s = mc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else + s.left = (cd.offsetLeft + cd.offsetWidth - mc.offsetWidth) + "px"; + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; }; Calendar.showYearsCombo = function (fwd) { @@ -280,9 +297,13 @@ Calendar.showYearsCombo = function (fwd) { Y += fwd ? 2 : -2; } if (show) { - yc.style.left = cd.offsetLeft + "px"; - yc.style.top = (cd.offsetTop + cd.offsetHeight) + "px"; - yc.style.display = "block"; + var s = yc.style; + s.display = "block"; + if (cd.navtype < 0) + s.left = cd.offsetLeft + "px"; + else + s.left = (cd.offsetLeft + cd.offsetWidth - yc.offsetWidth) + "px"; + s.top = (cd.offsetTop + cd.offsetHeight) + "px"; } }; @@ -301,9 +322,10 @@ Calendar.tableMouseUp = function(ev) { return false; } var target = Calendar.getTargetElement(ev); + ev || (ev = window.event); Calendar.removeClass(el, "active"); if (target == el || target.parentNode == el) { - Calendar.cellClick(el); + Calendar.cellClick(el, ev); } var mon = Calendar.findMonth(target); var date = null; @@ -348,10 +370,42 @@ Calendar.tableMouseOver = function (ev) { Calendar.addClass(el, "hilite active"); Calendar.addClass(el.parentNode, "rowhilite"); } else { - Calendar.removeClass(el, "active"); + if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2))) + Calendar.removeClass(el, "active"); Calendar.removeClass(el, "hilite"); Calendar.removeClass(el.parentNode, "rowhilite"); } + ev || (ev = window.event); + if (el.navtype == 50 && target != el) { + var pos = Calendar.getAbsolutePos(el); + var w = el.offsetWidth; + var x = ev.clientX; + var dx; + var decrease = true; + if (x > pos.x + w) { + dx = x - pos.x - w; + decrease = false; + } else + dx = pos.x - x; + + if (dx < 0) dx = 0; + var range = el._range; + var current = el._current; + var count = Math.floor(dx / 10) % range.length; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + while (count-- > 0) + if (decrease) { + if (!(--i in range)) + i = range.length - 1; + } else if (!(++i in range)) + i = 0; + var newval = range[i]; + el.firstChild.data = newval; + + cal.onUpdateTime(); + } var mon = Calendar.findMonth(target); if (mon) { if (mon.month != cal.date.getMonth()) { @@ -364,6 +418,9 @@ Calendar.tableMouseOver = function (ev) { Calendar.removeClass(cal.hilitedMonth, "hilite"); } } else { + if (cal.hilitedMonth) { + Calendar.removeClass(cal.hilitedMonth, "hilite"); + } var year = Calendar.findYear(target); if (year) { if (year.year != cal.date.getFullYear()) { @@ -375,6 +432,8 @@ Calendar.tableMouseOver = function (ev) { } else if (cal.hilitedYear) { Calendar.removeClass(cal.hilitedYear, "hilite"); } + } else if (cal.hilitedYear) { + Calendar.removeClass(cal.hilitedYear, "hilite"); } } return Calendar.stopEvent(ev); @@ -431,6 +490,8 @@ Calendar.dayMouseDown = function(ev) { cal.activeDiv = el; Calendar._C = cal; if (el.navtype != 300) with (Calendar) { + if (el.navtype == 50) + el._current = el.firstChild.data; addClass(el, "hilite active"); addEvent(document, "mouseover", tableMouseOver); addEvent(document, "mousemove", tableMouseOver); @@ -439,8 +500,10 @@ Calendar.dayMouseDown = function(ev) { cal._dragStart(ev); } if (el.navtype == -1 || el.navtype == 1) { + if (cal.timeout) clearTimeout(cal.timeout); cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250); } else if (el.navtype == -2 || el.navtype == 2) { + if (cal.timeout) clearTimeout(cal.timeout); cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250); } else { cal.timeout = null; @@ -449,7 +512,7 @@ Calendar.dayMouseDown = function(ev) { }; Calendar.dayMouseDblClick = function(ev) { - Calendar.cellClick(Calendar.getElement(ev)); + Calendar.cellClick(Calendar.getElement(ev), ev || window.event); if (Calendar.is_ie) { document.selection.empty(); } @@ -498,7 +561,7 @@ Calendar.dayMouseOut = function(ev) { * A generic "click" handler :) handles all types of buttons defined in this * calendar. */ -Calendar.cellClick = function(el) { +Calendar.cellClick = function(el, ev) { var cal = el.calendar; var closing = false; var newdate = false; @@ -525,7 +588,8 @@ Calendar.cellClick = function(el) { // unless "today" was clicked, we assume no date was clicked so // the selected handler will know not to close the calenar when // in single-click mode. - cal.dateClicked = (el.navtype == 0); + // cal.dateClicked = (el.navtype == 0); + cal.dateClicked = false; var year = date.getFullYear(); var mon = date.getMonth(); function setMonth(m) { @@ -537,6 +601,22 @@ Calendar.cellClick = function(el) { date.setMonth(m); }; switch (el.navtype) { + case 400: + Calendar.removeClass(el, "hilite"); + var text = Calendar._TT["ABOUT"]; + if (typeof text != "undefined") { + text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : ""; + } else { + // FIXME: this should be removed as soon as lang files get updated! + text = "Help and about box text is not translated into this language.\n" + + "If you know this language and you feel generous please update\n" + + "the corresponding file in \"lang\" subdir to match calendar-en.js\n" + + "and send it back to to get it into the distribution ;-)\n\n" + + "Thank you!\n" + + "http://dynarch.com/mishoo/calendar.epl\n"; + } + alert(text); + return; case -2: if (year > cal.minYear) { date.setFullYear(year - 1); @@ -566,9 +646,24 @@ Calendar.cellClick = function(el) { case 100: cal.setMondayFirst(!cal.mondayFirst); return; + case 50: + var range = el._range; + var current = el.firstChild.data; + for (var i = range.length; --i >= 0;) + if (range[i] == current) + break; + if (ev && ev.shiftKey) { + if (!(--i in range)) + i = range.length - 1; + } else if (!(++i in range)) + i = 0; + var newval = range[i]; + el.firstChild.data = newval; + cal.onUpdateTime(); + return; case 0: // TODAY will bring us here - if ((typeof cal.checkDisabled == "function") && cal.checkDisabled(date)) { + if ((typeof cal.getDateStatus == "function") && cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { // remember, "date" was previously set to new // Date() if TODAY was clicked; thus, it // contains today date. @@ -638,6 +733,8 @@ Calendar.prototype.create = function (_par) { cell = Calendar.createElement("td", row); cell.colSpan = cs; cell.className = "button"; + if (navtype != 0 && Math.abs(navtype) <= 2) + cell.className += " nav"; Calendar._add_evs(cell); cell.calendar = cal; cell.navtype = navtype; @@ -656,7 +753,7 @@ Calendar.prototype.create = function (_par) { (this.isPopup) && --title_length; (this.weekNumbers) && ++title_length; - hh("-", 1, 100).ttip = Calendar._TT["TOGGLE"]; + hh("?", 1, 400).ttip = Calendar._TT["INFO"]; this.title = hh("", title_length, 300); this.title.className = "title"; if (this.isPopup) { @@ -720,6 +817,96 @@ Calendar.prototype.create = function (_par) { } } + if (this.showsTime) { + row = Calendar.createElement("tr", tbody); + row.className = "time"; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + cell.innerHTML = " "; + + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = this.weekNumbers ? 4 : 3; + + (function(){ + function makeTimePart(className, init, range_start, range_end) { + var part = Calendar.createElement("span", cell); + part.className = className; + part.appendChild(document.createTextNode(init)); + part.calendar = cal; + part.ttip = Calendar._TT["TIME_PART"]; + part.navtype = 50; + part._range = []; + if (typeof range_start != "number") + part._range = range_start; + else { + for (var i = range_start; i <= range_end; ++i) { + var txt; + if (i < 10 && range_end >= 10) txt = '0' + i; + else txt = '' + i; + part._range[part._range.length] = txt; + } + } + Calendar._add_evs(part); + return part; + }; + var hrs = cal.date.getHours(); + var mins = cal.date.getMinutes(); + var t12 = !cal.time24; + var pm = (hrs > 12); + if (t12 && pm) hrs -= 12; + var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23); + var span = Calendar.createElement("span", cell); + span.appendChild(document.createTextNode(":")); + span.className = "colon"; + var M = makeTimePart("minute", mins, 0, 59); + var AP = null; + cell = Calendar.createElement("td", row); + cell.className = "time"; + cell.colSpan = 2; + if (t12) + AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]); + else + cell.innerHTML = " "; + + cal.onSetTime = function() { + var hrs = this.date.getHours(); + var mins = this.date.getMinutes(); + var pm = (hrs > 12); + if (pm && t12) hrs -= 12; + H.firstChild.data = (hrs < 10) ? ("0" + hrs) : hrs; + M.firstChild.data = (mins < 10) ? ("0" + mins) : mins; + if (t12) + AP.firstChild.data = pm ? "pm" : "am"; + }; + + cal.onUpdateTime = function() { + var date = this.date; + var h = parseInt(H.firstChild.data, 10); + if (t12) { + if (/pm/i.test(AP.firstChild.data) && h < 12) + h += 12; + else if (/am/i.test(AP.firstChild.data) && h == 12) + h = 0; + } + var d = date.getDate(); + var m = date.getMonth(); + var y = date.getFullYear(); + date.setHours(h); + date.setMinutes(parseInt(M.firstChild.data, 10)); + date.setFullYear(y); + date.setMonth(m); + date.setDate(d); + this.dateClicked = false; + this.callHandler(); + }; + })(); + } else { + this.onSetTime = this.onUpdateTime = function() {}; + } + var tfoot = Calendar.createElement("tfoot", table); row = Calendar.createElement("tr", tfoot); @@ -738,9 +925,9 @@ Calendar.prototype.create = function (_par) { div.className = "combo"; for (i = 0; i < Calendar._MN.length; ++i) { var mn = Calendar.createElement("div"); - mn.className = "label"; + mn.className = Calendar.is_ie ? "label-IEfix" : "label"; mn.month = i; - mn.appendChild(document.createTextNode(Calendar._MN3[i])); + mn.appendChild(document.createTextNode(Calendar._SMN[i])); div.appendChild(mn); } @@ -749,7 +936,7 @@ Calendar.prototype.create = function (_par) { div.className = "combo"; for (i = 12; i > 0; --i) { var yr = Calendar.createElement("div"); - yr.className = "label"; + yr.className = Calendar.is_ie ? "label-IEfix" : "label"; yr.appendChild(document.createTextNode("")); div.appendChild(yr); } @@ -874,7 +1061,7 @@ Calendar.prototype._init = function (mondayFirst, date) { } var iday = 1; var row = this.tbody.firstChild; - var MN = Calendar._MN3[month]; + var MN = Calendar._SMN[month]; var hasToday = ((today.getFullYear() == year) && (today.getMonth() == month)); var todayDate = today.getDate(); var week_number = date.getWeekNumber(); @@ -904,11 +1091,16 @@ Calendar.prototype._init = function (mondayFirst, date) { } cell.disabled = false; cell.firstChild.data = iday; - if (typeof this.checkDisabled == "function") { + if (typeof this.getDateStatus == "function") { date.setDate(iday); - if (this.checkDisabled(date)) { + var status = this.getDateStatus(date, year, month, iday); + if (status === true) { cell.className += " disabled"; cell.disabled = true; + } else { + if (/disabled/i.test(status)) + cell.disabled = true; + cell.className += " " + status; } } if (!cell.disabled) { @@ -935,6 +1127,7 @@ Calendar.prototype._init = function (mondayFirst, date) { } this.ar_days = ar_days; this.title.firstChild.data = Calendar._MN[month] + ", " + year; + this.onSetTime(); // PROFILE // this.tooltips.firstChild.data = "Generated in " + ((new Date()) - today) + " ms"; }; @@ -971,8 +1164,8 @@ Calendar.prototype.setMondayFirst = function (mondayFirst) { * object) and returns a boolean value. If the returned value is true then * the passed date will be marked as disabled. */ -Calendar.prototype.setDisabledHandler = function (unaryFunction) { - this.checkDisabled = unaryFunction; +Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) { + this.getDateStatus = unaryFunction; }; /** Customization of allowed year range for the calendar. */ @@ -1001,6 +1194,7 @@ Calendar.prototype.destroy = function () { var el = this.element.parentNode; el.removeChild(this.element); Calendar._C = null; + window.calendar = null; }; /** @@ -1082,37 +1276,44 @@ Calendar.prototype.showAt = function (x, y) { /** Shows the calendar near a given element. */ Calendar.prototype.showAtElement = function (el, opts) { + var self = this; var p = Calendar.getAbsolutePos(el); if (!opts || typeof opts != "string") { this.showAt(p.x, p.y + el.offsetHeight); return true; } - this.show(); - var w = this.element.offsetWidth; - var h = this.element.offsetHeight; - this.hide(); - var valign = opts.substr(0, 1); - var halign = "l"; - if (opts.length > 1) { - halign = opts.substr(1, 1); - } - // vertical alignment - switch (valign) { - case "T": p.y -= h; break; - case "B": p.y += el.offsetHeight; break; - case "C": p.y += (el.offsetHeight - h) / 2; break; - case "t": p.y += el.offsetHeight - h; break; - case "b": break; // already there - } - // horizontal alignment - switch (halign) { - case "L": p.x -= w; break; - case "R": p.x += el.offsetWidth; break; - case "C": p.x += (el.offsetWidth - w) / 2; break; - case "r": p.x += el.offsetWidth - w; break; - case "l": break; // already there - } - this.showAt(p.x, p.y); + this.element.style.display = "block"; + Calendar.continuation_for_the_fucking_khtml_browser = function() { + var w = self.element.offsetWidth; + var h = self.element.offsetHeight; + self.element.style.display = "none"; + var valign = opts.substr(0, 1); + var halign = "l"; + if (opts.length > 1) { + halign = opts.substr(1, 1); + } + // vertical alignment + switch (valign) { + case "T": p.y -= h; break; + case "B": p.y += el.offsetHeight; break; + case "C": p.y += (el.offsetHeight - h) / 2; break; + case "t": p.y += el.offsetHeight - h; break; + case "b": break; // already there + } + // horizontal alignment + switch (halign) { + case "L": p.x -= w; break; + case "R": p.x += el.offsetWidth; break; + case "C": p.x += (el.offsetWidth - w) / 2; break; + case "r": p.x += el.offsetWidth - w; break; + case "l": break; // already there + } + self.showAt(p.x, p.y); + }; + if (Calendar.is_khtml) + setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); + else + Calendar.continuation_for_the_fucking_khtml_browser(); }; /** Customizes the date format. */ @@ -1137,30 +1338,42 @@ Calendar.prototype.parseDate = function (str, fmt) { if (!fmt) { fmt = this.dateFormat; } - var b = fmt.split(/\W+/); + var b = []; + fmt.replace(/(%.)/g, function(str, par) { + return b[b.length] = par; + }); var i = 0, j = 0; + var hr = 0; + var min = 0; for (i = 0; i < a.length; ++i) { - if (b[i] == "D" || b[i] == "DD") { + if (b[i] == "%a" || b[i] == "%A") { continue; } - if (b[i] == "d" || b[i] == "dd") { + if (b[i] == "%d" || b[i] == "%e") { d = parseInt(a[i], 10); } - if (b[i] == "m" || b[i] == "mm") { + if (b[i] == "%m") { m = parseInt(a[i], 10) - 1; } - if ((b[i] == "y") || (b[i] == "yy")) { + if (b[i] == "%Y" || b[i] == "%y") { y = parseInt(a[i], 10); (y < 100) && (y += (y > 29) ? 1900 : 2000); } - if (b[i] == "M" || b[i] == "MM") { + if (b[i] == "%b" || b[i] == "%B") { for (j = 0; j < 12; ++j) { if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; } } + } else if (/%[HIkl]/.test(b[i])) { + hr = parseInt(a[i], 10); + } else if (/%[pP]/.test(b[i])) { + if (/pm/i.test(a[i]) && hr < 12) + hr += 12; + } else if (b[i] == "%M") { + min = parseInt(a[i], 10); } } if (y != 0 && m != -1 && d != 0) { - this.setDate(new Date(y, m, d)); + this.setDate(new Date(y, m, d, hr, min, 0)); return; } y = 0; m = -1; d = 0; @@ -1190,61 +1403,70 @@ Calendar.prototype.parseDate = function (str, fmt) { y = today.getFullYear(); } if (m != -1 && d != 0) { - this.setDate(new Date(y, m, d)); + this.setDate(new Date(y, m, d, hr, min, 0)); } }; Calendar.prototype.hideShowCovered = function () { - function getStyleProp(obj, style){ - var value = obj.style[style]; - if (!value) { - if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C - value = document.defaultView. - getComputedStyle(obj, "").getPropertyValue(style); - } else if (obj.currentStyle) { // IE - value = obj.currentStyle[style]; - } else { - value = obj.style[style]; + var self = this; + Calendar.continuation_for_the_fucking_khtml_browser = function() { + function getVisib(obj){ + var value = obj.style.visibility; + if (!value) { + if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C + if (!Calendar.is_khtml) + value = document.defaultView. + getComputedStyle(obj, "").getPropertyValue("visibility"); + else + value = ''; + } else if (obj.currentStyle) { // IE + value = obj.currentStyle.visibility; + } else + value = ''; } - } - return value; - }; - - var tags = new Array("applet", "iframe", "select"); - var el = this.element; + return value; + }; - var p = Calendar.getAbsolutePos(el); - var EX1 = p.x; - var EX2 = el.offsetWidth + EX1; - var EY1 = p.y; - var EY2 = el.offsetHeight + EY1; - - for (var k = tags.length; k > 0; ) { - var ar = document.getElementsByTagName(tags[--k]); - var cc = null; - - for (var i = ar.length; i > 0;) { - cc = ar[--i]; - - p = Calendar.getAbsolutePos(cc); - var CX1 = p.x; - var CX2 = cc.offsetWidth + CX1; - var CY1 = p.y; - var CY2 = cc.offsetHeight + CY1; - - if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { - if (!cc.__msh_save_visibility) { - cc.__msh_save_visibility = getStyleProp(cc, "visibility"); - } - cc.style.visibility = cc.__msh_save_visibility; - } else { - if (!cc.__msh_save_visibility) { - cc.__msh_save_visibility = getStyleProp(cc, "visibility"); + var tags = new Array("applet", "iframe", "select"); + var el = self.element; + + var p = Calendar.getAbsolutePos(el); + var EX1 = p.x; + var EX2 = el.offsetWidth + EX1; + var EY1 = p.y; + var EY2 = el.offsetHeight + EY1; + + for (var k = tags.length; k > 0; ) { + var ar = document.getElementsByTagName(tags[--k]); + var cc = null; + + for (var i = ar.length; i > 0;) { + cc = ar[--i]; + + p = Calendar.getAbsolutePos(cc); + var CX1 = p.x; + var CX2 = cc.offsetWidth + CX1; + var CY1 = p.y; + var CY2 = cc.offsetHeight + CY1; + + if (self.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = cc.__msh_save_visibility; + } else { + if (!cc.__msh_save_visibility) { + cc.__msh_save_visibility = getVisib(cc); + } + cc.style.visibility = "hidden"; } - cc.style.visibility = "hidden"; } } - } + }; + if (Calendar.is_khtml) + setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10); + else + Calendar.continuation_for_the_fucking_khtml_browser(); }; /** Internal function; it displays the bar with the names of the weekday. */ @@ -1264,7 +1486,7 @@ Calendar.prototype._displayWeekdays = function () { if (i == SUN || i == SAT) { Calendar.addClass(cell, "weekend"); } - cell.firstChild.data = Calendar._DN3[i + 1 - MON]; + cell.firstChild.data = Calendar._SDN[i + 1 - MON]; cell = cell.nextSibling; } }; @@ -1325,14 +1547,22 @@ Date.prototype.getMonthDays = function(month) { } }; -/** Returns the number of the week. The algorithm was "stolen" from PPK's - * website, hope it's correct :) http://www.xs4all.nl/~ppk/js/week.html */ +/** Returns the number of day in the year. */ +Date.prototype.getDayOfYear = function() { + var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); + var then = new Date(this.getFullYear(), 0, 1, 0, 0, 0); + var time = now - then; + return Math.floor(time / Date.DAY); +}; + +/** Returns the number of the week in year, as defined in ISO 8601. */ Date.prototype.getWeekNumber = function() { var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0); var then = new Date(this.getFullYear(), 0, 1, 0, 0, 0); var time = now - then; - var day = then.getDay(); - (day > 3) && (day -= 4) || (day += 3); + var day = then.getDay(); // 0 means Sunday + if (day == 0) day = 7; + (day > 4) && (day -= 4) || (day += 3); return Math.round(((time / Date.DAY) + day) / 7); }; @@ -1340,37 +1570,69 @@ Date.prototype.getWeekNumber = function() { Date.prototype.equalsTo = function(date) { return ((this.getFullYear() == date.getFullYear()) && (this.getMonth() == date.getMonth()) && - (this.getDate() == date.getDate())); + (this.getDate() == date.getDate()) && + (this.getHours() == date.getHours()) && + (this.getMinutes() == date.getMinutes())); }; /** Prints the date in a string according to the given format. */ -Date.prototype.print = function (frm) { - var str = new String(frm); +Date.prototype.print = function (str) { var m = this.getMonth(); var d = this.getDate(); var y = this.getFullYear(); var wn = this.getWeekNumber(); var w = this.getDay(); - var s = new Array(); - s["d"] = d; - s["dd"] = (d < 10) ? ("0" + d) : d; - s["m"] = 1+m; - s["mm"] = (m < 9) ? ("0" + (1+m)) : (1+m); - s["y"] = y; - s["yy"] = new String(y).substr(2, 2); - s["w"] = wn; - s["ww"] = (wn < 10) ? ("0" + wn) : wn; - with (Calendar) { - s["D"] = _DN3[w]; - s["DD"] = _DN[w]; - s["M"] = _MN3[m]; - s["MM"] = _MN[m]; - } - var re = /(.*)(\W|^)(d|dd|m|mm|y|yy|MM|M|DD|D|w|ww)(\W|$)(.*)/; - while (re.exec(str) != null) { - str = RegExp.$1 + RegExp.$2 + s[RegExp.$3] + RegExp.$4 + RegExp.$5; - } - return str; + var s = {}; + var hr = this.getHours(); + var pm = (hr >= 12); + var ir = (pm) ? (hr - 12) : hr; + var dy = this.getDayOfYear(); + if (ir == 0) + ir = 12; + var min = this.getMinutes(); + var sec = this.getSeconds(); + s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N] + s["%A"] = Calendar._DN[w]; // full weekday name + s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N] + s["%B"] = Calendar._MN[m]; // full month name + // FIXME: %c : preferred date and time representation for the current locale + s["%C"] = 1 + Math.floor(y / 100); // the century number + s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31) + s["%e"] = d; // the day of the month (range 1 to 31) + // FIXME: %D : american date style: %m/%d/%y + // FIXME: %E, %F, %G, %g, %h (man strftime) + s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format) + s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format) + s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366) + s["%k"] = hr; // hour, range 0 to 23 (24h format) + s["%l"] = ir; // hour, range 1 to 12 (12h format) + s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12 + s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59 + s["%n"] = "\n"; // a newline character + s["%p"] = pm ? "PM" : "AM"; + s["%P"] = pm ? "pm" : "am"; + // FIXME: %r : the time in am/pm notation %I:%M:%S %p + // FIXME: %R : the time in 24-hour notation %H:%M + s["%s"] = Math.floor(this.getTime() / 1000); + s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59 + s["%t"] = "\t"; // a tab character + // FIXME: %T : the time in 24-hour notation (%H:%M:%S) + s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn; + s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON) + s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN) + // FIXME: %x : preferred date representation for the current locale without the time + // FIXME: %X : preferred time representation for the current locale without the date + s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99) + s["%Y"] = y; // year with the century + s["%%"] = "%"; // a literal '%' character + var re = Date._msh_formatRegexp; + if (typeof re == "undefined") { + var tmp = ""; + for (var i in s) + tmp += tmp ? ("|" + i) : i; + Date._msh_formatRegexp = re = new RegExp("(" + tmp + ")", 'g'); + } + return str.replace(re, function(match, par) { return s[par]; }); }; // END: DATE OBJECT PATCHES diff --git a/httemplate/elements/calendar_stripped.js b/httemplate/elements/calendar_stripped.js index 38b9b9aad..029496a74 100644 --- a/httemplate/elements/calendar_stripped.js +++ b/httemplate/elements/calendar_stripped.js @@ -1,12 +1,12 @@ -/* Copyright Mihai Bazon, 2002, 2003 | http://students.infoiasi.ro/~mishoo - * --------------------------------------------------------------------------- +/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ + * ------------------------------------------------------------------ * - * The DHTML Calendar, version 0.9.3 "It's still alive & keeps rocking" + * The DHTML Calendar, version 0.9.5 "Your favorite time, bis" * * Details and latest version at: - * http://students.infoiasi.ro/~mishoo/site/calendar.epl + * http://dynarch.com/mishoo/calendar.epl * - * Feel free to use this script under the terms of the GNU Lesser General - * Public License, as long as you do not remove or alter this notice. + * This script is distributed under the GNU Lesser General Public License. + * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html */ -Calendar=function(mondayFirst,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.checkDisabled=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.mondayFirst=mondayFirst;this.dateStr=dateStr;this.ar_days=null;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(!Calendar._DN3){var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,3);}Calendar._DN3=ar;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,3);}Calendar._MN3=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar._DN3=null;Calendar._MN3=null;Calendar.getAbsolutePos=function(el){var r={x:el.offsetLeft,y:el.offsetTop};if(el.offsetParent){var tmp=Calendar.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.currentTarget;}};Calendar.getTargetElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.target;}};Calendar.stopEvent=function(ev){if(Calendar.is_ie){window.event.cancelBubble=true;window.event.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;mc.style.left=cd.offsetLeft+"px";mc.style.top=(cd.offsetTop+cd.offsetHeight)+"px";mc.style.display="block";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.firstChild.data=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?2:-2;}if(show){yc.style.left=cd.offsetLeft+"px";yc.style.top=(cd.offsetTop+cd.offsetHeight)+"px";yc.style.display="block";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseover",stopEvent);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){addClass(el,"hilite active");addEvent(document,"mouseover",tableMouseOver);addEvent(document,"mousemove",tableMouseOver);addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev));if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){var date=null;with(el.calendar.date){date=new Date(getFullYear(),getMonth(),el.caldate);}el.ttip=date.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.firstChild.data=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled){return false;}removeClass(el,"hilite");if(el.caldate){removeClass(el.parentNode,"rowhilite");}el.calendar.tooltips.firstChild.data=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}cal.date.setDate(el.caldate);date=cal.date;newdate=true;cal.dateClicked=true;}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=(el.navtype==0)?new Date():new Date(cal.date);cal.dateClicked=(el.navtype==0);var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year0;--i){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));if(!i){cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}}this.firstdayname=(this.weekNumbers)?row.firstChild.nextSibling:row.firstChild;this._displayWeekdays();var tbody=Calendar.createElement("tbody",table);this.tbody=tbody;for(i=6;i>0;--i){row=Calendar.createElement("tr",tbody);if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));cell.calendar=this;Calendar._add_evs(cell);}}var tfoot=Calendar.createElement("tfoot",table);row=Calendar.createElement("tr",tfoot);row.className="footrow";cell=hh(Calendar._TT["SEL_DATE"],this.weekNumbers?8:7,300);cell.className="ttip";if(this.isPopup){cell.ttip=Calendar._TT["DRAG_TO_MOVE"];cell.style.cursor="move";}this.tooltips=cell;div=Calendar.createElement("div",this.element);this.monthsCombo=div;div.className="combo";for(i=0;i0;--i){var yr=Calendar.createElement("div");yr.className="label";yr.appendChild(document.createTextNode(""));div.appendChild(yr);}this._init(this.mondayFirst,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){if(!window.calendar){return false;}(Calendar.is_ie)&&(ev=window.event);var cal=window.calendar;var act=(Calendar.is_ie||ev.type=="keypress");if(ev.ctrlKey){switch(ev.keyCode){case 37:act&&Calendar.cellClick(cal._nav_pm);break;case 38:act&&Calendar.cellClick(cal._nav_py);break;case 39:act&&Calendar.cellClick(cal._nav_nm);break;case 40:act&&Calendar.cellClick(cal._nav_ny);break;default:return false;}}else switch(ev.keyCode){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.hide();break;case 37:case 38:case 39:case 40:if(act){var date=cal.date.getDate()-1;var el=cal.currentDateEl;var ne=null;var prev=(ev.keyCode==37)||(ev.keyCode==38);switch(ev.keyCode){case 37:(--date>=0)&&(ne=cal.ar_days[date]);break;case 38:date-=7;(date>=0)&&(ne=cal.ar_days[date]);break;case 39:(++datethis.maxYear){year=this.maxYear;date.setFullYear(year);}this.mondayFirst=mondayFirst;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var wday=date.getDay();var MON=mondayFirst?1:0;var SAT=mondayFirst?5:6;var SUN=mondayFirst?6:0;if(mondayFirst){wday=(wday>0)?(wday-1):6;}var iday=1;var row=this.tbody.firstChild;var MN=Calendar._MN3[month];var hasToday=((today.getFullYear()==year)&&(today.getMonth()==month));var todayDate=today.getDate();var week_number=date.getWeekNumber();var ar_days=new Array();for(var i=0;i<6;++i){if(iday>no_days){row.className="emptyrow";row=row.nextSibling;continue;}var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.firstChild.data=week_number;cell=cell.nextSibling;}++week_number;row.className="daysrow";for(var j=0;j<7;++j){cell.className="day";if((!i&&jno_days){cell.innerHTML=" ";cell.disabled=true;cell=cell.nextSibling;continue;}cell.disabled=false;cell.firstChild.data=iday;if(typeof this.checkDisabled=="function"){date.setDate(iday);if(this.checkDisabled(date)){cell.className+=" disabled";cell.disabled=true;}}if(!cell.disabled){ar_days[ar_days.length]=cell;cell.caldate=iday;cell.ttip="_";if(iday==mday){cell.className+=" selected";this.currentDateEl=cell;}if(hasToday&&(iday==todayDate)){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(wday==SAT||wday==SUN){cell.className+=" weekend";}}++iday;((++wday)^ 7)||(wday=0);cell=cell.nextSibling;}row=row.nextSibling;}this.ar_days=ar_days;this.title.firstChild.data=Calendar._MN[month]+", "+year;};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.mondayFirst,date);}};Calendar.prototype.refresh=function(){this._init(this.mondayFirst,this.date);};Calendar.prototype.setMondayFirst=function(mondayFirst){this._init(mondayFirst,this.date);this._displayWeekdays();};Calendar.prototype.setDisabledHandler=function(unaryFunction){this.checkDisabled=unaryFunction;};Calendar.prototype.setRange=function(a,z){this.minYear=a;this.maxYear=z;};Calendar.prototype.callHandler=function(){if(this.onSelected){this.onSelected(this,this.date.print(this.dateFormat));}};Calendar.prototype.callCloseHandler=function(){if(this.onClose){this.onClose(this);}this.hideShowCovered();};Calendar.prototype.destroy=function(){var el=this.element.parentNode;el.removeChild(this.element);Calendar._C=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){if(!window.calendar){return false;}var el=Calendar.is_ie?Calendar.getElement(ev):Calendar.getTargetElement(ev);for(;el!=null&&el!=calendar.element;el=el.parentNode);if(el==null){window.calendar.callCloseHandler();return Calendar.stopEvent(ev);}};Calendar.prototype.show=function(){var rows=this.table.getElementsByTagName("tr");for(var i=rows.length;i>0;){var row=rows[--i];Calendar.removeClass(row,"rowhilite");var cells=row.getElementsByTagName("td");for(var j=cells.length;j>0;){var cell=cells[--j];Calendar.removeClass(cell,"hilite");Calendar.removeClass(cell,"active");}}this.element.style.display="block";this.hidden=false;if(this.isPopup){window.calendar=this;Calendar.addEvent(document,"keydown",Calendar._keyEvent);Calendar.addEvent(document,"keypress",Calendar._keyEvent);Calendar.addEvent(document,"mousedown",Calendar._checkCalendar);}this.hideShowCovered();};Calendar.prototype.hide=function(){if(this.isPopup){Calendar.removeEvent(document,"keydown",Calendar._keyEvent);Calendar.removeEvent(document,"keypress",Calendar._keyEvent);Calendar.removeEvent(document,"mousedown",Calendar._checkCalendar);}this.element.style.display="none";this.hidden=true;this.hideShowCovered();};Calendar.prototype.showAt=function(x,y){var s=this.element.style;s.left=x+"px";s.top=y+"px";this.show();};Calendar.prototype.showAtElement=function(el,opts){var p=Calendar.getAbsolutePos(el);if(!opts||typeof opts!="string"){this.showAt(p.x,p.y+el.offsetHeight);return true;}this.show();var w=this.element.offsetWidth;var h=this.element.offsetHeight;this.hide();var valign=opts.substr(0,1);var halign="l";if(opts.length>1){halign=opts.substr(1,1);}switch(valign){case "T":p.y-=h;break;case "B":p.y+=el.offsetHeight;break;case "C":p.y+=(el.offsetHeight-h)/2;break;case "t":p.y+=el.offsetHeight-h;break;case "b":break;}switch(halign){case "L":p.x-=w;break;case "R":p.x+=el.offsetWidth;break;case "C":p.x+=(el.offsetWidth-w)/2;break;case "r":p.x+=el.offsetWidth-w;break;case "l":break;}this.showAt(p.x,p.y);};Calendar.prototype.setDateFormat=function(str){this.dateFormat=str;};Calendar.prototype.setTtDateFormat=function(str){this.ttDateFormat=str;};Calendar.prototype.parseDate=function(str,fmt){var y=0;var m=-1;var d=0;var a=str.split(/\W+/);if(!fmt){fmt=this.dateFormat;}var b=fmt.split(/\W+/);var i=0,j=0;for(i=0;i29)?1900:2000);}if(b[i]=="M"||b[i]=="MM"){for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}}}if(y!=0&&m!=-1&&d!=0){this.setDate(new Date(y,m,d));return;}y=0;m=-1;d=0;for(i=0;i31&&y==0){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}else if(d==0){d=a[i];}}if(y==0){var today=new Date();y=today.getFullYear();}if(m!=-1&&d!=0){this.setDate(new Date(y,m,d));}};Calendar.prototype.hideShowCovered=function(){function getStyleProp(obj,style){var value=obj.style[style];if(!value){if(document.defaultView&&typeof(document.defaultView.getComputedStyle)=="function"){value=document.defaultView.getComputedStyle(obj,"").getPropertyValue(style);}else if(obj.currentStyle){value=obj.currentStyle[style];}else{value=obj.style[style];}}return value;};var tags=new Array("applet","iframe","select");var el=this.element;var p=Calendar.getAbsolutePos(el);var EX1=p.x;var EX2=el.offsetWidth+EX1;var EY1=p.y;var EY2=el.offsetHeight+EY1;for(var k=tags.length;k>0;){var ar=document.getElementsByTagName(tags[--k]);var cc=null;for(var i=ar.length;i>0;){cc=ar[--i];p=Calendar.getAbsolutePos(cc);var CX1=p.x;var CX2=cc.offsetWidth+CX1;var CY1=p.y;var CY2=cc.offsetHeight+CY1;if(this.hidden||(CX1>EX2)||(CX2EY2)||(CY23)&&(day-=4)||(day+=3);return Math.round(((time/Date.DAY)+day)/7);};Date.prototype.equalsTo=function(date){return((this.getFullYear()==date.getFullYear())&&(this.getMonth()==date.getMonth())&&(this.getDate()==date.getDate()));};Date.prototype.print=function(frm){var str=new String(frm);var m=this.getMonth();var d=this.getDate();var y=this.getFullYear();var wn=this.getWeekNumber();var w=this.getDay();var s=new Array();s["d"]=d;s["dd"]=(d<10)?("0"+d):d;s["m"]=1+m;s["mm"]=(m<9)?("0"+(1+m)):(1+m);s["y"]=y;s["yy"]=new String(y).substr(2,2);s["w"]=wn;s["ww"]=(wn<10)?("0"+wn):wn;with(Calendar){s["D"]=_DN3[w];s["DD"]=_DN[w];s["M"]=_MN3[m];s["MM"]=_MN[m];}var re=/(.*)(\W|^)(d|dd|m|mm|y|yy|MM|M|DD|D|w|ww)(\W|$)(.*)/;while(re.exec(str)!=null){str=RegExp.$1+RegExp.$2+s[RegExp.$3]+RegExp.$4+RegExp.$5;}return str;};window.calendar=null; \ No newline at end of file + Calendar=function(mondayFirst,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.mondayFirst=mondayFirst;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(typeof Calendar._SDN=="undefined"){if(typeof Calendar._SDN_len=="undefined")Calendar._SDN_len=3;var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,Calendar._SDN_len);}Calendar._SDN=ar;if(typeof Calendar._SMN_len=="undefined")Calendar._SMN_len=3;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,Calendar._SMN_len);}Calendar._SMN=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar.is_opera=/opera/i.test(navigator.userAgent);Calendar.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Calendar.getAbsolutePos=function(el){var SL=0,ST=0;var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)SL=el.scrollLeft;if(is_div&&el.scrollTop)ST=el.scrollTop;var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=Calendar.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.currentTarget;}};Calendar.getTargetElement=function(ev){if(Calendar.is_ie){return window.event.srcElement;}else{return ev.target;}};Calendar.stopEvent=function(ev){ev||(ev=window.event);if(Calendar.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;var s=mc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else s.left=(cd.offsetLeft+cd.offsetWidth-mc.offsetWidth)+"px";s.top=(cd.offsetTop+cd.offsetHeight)+"px";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.firstChild.data=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?2:-2;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else s.left=(cd.offsetLeft+cd.offsetWidth-yc.offsetWidth)+"px";s.top=(cd.offsetTop+cd.offsetHeight)+"px";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);ev||(ev=window.event);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el,ev);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{if(typeof el.navtype=="undefined"||(el.navtype!=50&&(el.navtype==0||Math.abs(el.navtype)>2)))Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}ev||(ev=window.event);if(el.navtype==50&&target!=el){var pos=Calendar.getAbsolutePos(el);var w=el.offsetWidth;var x=ev.clientX;var dx;var decrease=true;if(x>pos.x+w){dx=x-pos.x-w;decrease=false;}else dx=pos.x-x;if(dx<0)dx=0;var range=el._range;var current=el._current;var count=Math.floor(dx/10)%range.length;for(var i=range.length;--i>=0;)if(range[i]==current)break;while(count-->0)if(decrease){if(!(--i in range))i=range.length-1;}else if(!(++i in range))i=0;var newval=range[i];el.firstChild.data=newval;cal.onUpdateTime();}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseover",stopEvent);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){if(el.navtype==50)el._current=el.firstChild.data;addClass(el,"hilite active");addEvent(document,"mouseover",tableMouseOver);addEvent(document,"mousemove",tableMouseOver);addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev),ev||window.event);if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){var date=null;with(el.calendar.date){date=new Date(getFullYear(),getMonth(),el.caldate);}el.ttip=date.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.firstChild.data=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled){return false;}removeClass(el,"hilite");if(el.caldate){removeClass(el.parentNode,"rowhilite");}el.calendar.tooltips.firstChild.data=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el,ev){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}cal.date.setDate(el.caldate);date=cal.date;newdate=true;cal.dateClicked=true;}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=(el.navtype==0)?new Date():new Date(cal.date);cal.dateClicked=false;var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case 400:Calendar.removeClass(el,"hilite");var text=Calendar._TT["ABOUT"];if(typeof text!="undefined"){text+=cal.showsTime?Calendar._TT["ABOUT_TIME"]:"";}else{text="Help and about box text is not translated into this language.\n"+"If you know this language and you feel generous please update\n"+"the corresponding file in \"lang\" subdir to match calendar-en.js\n"+"and send it back to to get it into the distribution ;-)\n\n"+"Thank you!\n"+"http://dynarch.com/mishoo/calendar.epl\n";}alert(text);return;case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(!(--i in range))i=range.length-1;}else if(!(++i in range))i=0;var newval=range[i];el.firstChild.data=newval;cal.onUpdateTime();return;case 0:if((typeof cal.getDateStatus=="function")&&cal.getDateStatus(date,date.getFullYear(),date.getMonth(),date.getDate())){return false;}break;}if(!date.equalsTo(cal.date)){cal.setDate(date);newdate=true;}}if(newdate){cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");cal.callCloseHandler();}};Calendar.prototype.create=function(_par){var parent=null;if(!_par){parent=document.getElementsByTagName("body")[0];this.isPopup=true;}else{parent=_par;this.isPopup=false;}this.date=this.dateStr?new Date(this.dateStr):new Date();var table=Calendar.createElement("table");this.table=table;table.cellSpacing=0;table.cellPadding=0;table.calendar=this;Calendar.addEvent(table,"mousedown",Calendar.tableMouseDown);var div=Calendar.createElement("div");this.element=div;div.className="calendar";if(this.isPopup){div.style.position="absolute";div.style.display="none";}div.appendChild(table);var thead=Calendar.createElement("thead",table);var cell=null;var row=null;var cal=this;var hh=function(text,cs,navtype){cell=Calendar.createElement("td",row);cell.colSpan=cs;cell.className="button";if(navtype!=0&&Math.abs(navtype)<=2)cell.className+=" nav";Calendar._add_evs(cell);cell.calendar=cal;cell.navtype=navtype;if(text.substr(0,1)!="&"){cell.appendChild(document.createTextNode(text));}else{cell.innerHTML=text;}return cell;};row=Calendar.createElement("tr",thead);var title_length=6;(this.isPopup)&&--title_length;(this.weekNumbers)&&++title_length;hh("?",1,400).ttip=Calendar._TT["INFO"];this.title=hh("",title_length,300);this.title.className="title";if(this.isPopup){this.title.ttip=Calendar._TT["DRAG_TO_MOVE"];this.title.style.cursor="move";hh("×",1,200).ttip=Calendar._TT["CLOSE"];}row=Calendar.createElement("tr",thead);row.className="headrow";this._nav_py=hh("«",1,-2);this._nav_py.ttip=Calendar._TT["PREV_YEAR"];this._nav_pm=hh("‹",1,-1);this._nav_pm.ttip=Calendar._TT["PREV_MONTH"];this._nav_now=hh(Calendar._TT["TODAY"],this.weekNumbers?4:3,0);this._nav_now.ttip=Calendar._TT["GO_TODAY"];this._nav_nm=hh("›",1,1);this._nav_nm.ttip=Calendar._TT["NEXT_MONTH"];this._nav_ny=hh("»",1,2);this._nav_ny.ttip=Calendar._TT["NEXT_YEAR"];row=Calendar.createElement("tr",thead);row.className="daynames";if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.className="name wn";cell.appendChild(document.createTextNode(Calendar._TT["WK"]));}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));if(!i){cell.navtype=100;cell.calendar=this;Calendar._add_evs(cell);}}this.firstdayname=(this.weekNumbers)?row.firstChild.nextSibling:row.firstChild;this._displayWeekdays();var tbody=Calendar.createElement("tbody",table);this.tbody=tbody;for(i=6;i>0;--i){row=Calendar.createElement("tr",tbody);if(this.weekNumbers){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);cell.appendChild(document.createTextNode(""));cell.calendar=this;Calendar._add_evs(cell);}}if(this.showsTime){row=Calendar.createElement("tr",tbody);row.className="time";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;cell.innerHTML=" ";cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=this.weekNumbers?4:3;(function(){function makeTimePart(className,init,range_start,range_end){var part=Calendar.createElement("span",cell);part.className=className;part.appendChild(document.createTextNode(init));part.calendar=cal;part.ttip=Calendar._TT["TIME_PART"];part.navtype=50;part._range=[];if(typeof range_start!="number")part._range=range_start;else{for(var i=range_start;i<=range_end;++i){var txt;if(i<10&&range_end>=10)txt='0'+i;else txt=''+i;part._range[part._range.length]=txt;}}Calendar._add_evs(part);return part;};var hrs=cal.date.getHours();var mins=cal.date.getMinutes();var t12=!cal.time24;var pm=(hrs>12);if(t12&&pm)hrs-=12;var H=makeTimePart("hour",hrs,t12?1:0,t12?12:23);var span=Calendar.createElement("span",cell);span.appendChild(document.createTextNode(":"));span.className="colon";var M=makeTimePart("minute",mins,0,59);var AP=null;cell=Calendar.createElement("td",row);cell.className="time";cell.colSpan=2;if(t12)AP=makeTimePart("ampm",pm?"pm":"am",["am","pm"]);else cell.innerHTML=" ";cal.onSetTime=function(){var hrs=this.date.getHours();var mins=this.date.getMinutes();var pm=(hrs>12);if(pm&&t12)hrs-=12;H.firstChild.data=(hrs<10)?("0"+hrs):hrs;M.firstChild.data=(mins<10)?("0"+mins):mins;if(t12)AP.firstChild.data=pm?"pm":"am";};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.firstChild.data,10);if(t12){if(/pm/i.test(AP.firstChild.data)&&h<12)h+=12;else if(/am/i.test(AP.firstChild.data)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.firstChild.data,10));date.setFullYear(y);date.setMonth(m);date.setDate(d);this.dateClicked=false;this.callHandler();};})();}else{this.onSetTime=this.onUpdateTime=function(){};}var tfoot=Calendar.createElement("tfoot",table);row=Calendar.createElement("tr",tfoot);row.className="footrow";cell=hh(Calendar._TT["SEL_DATE"],this.weekNumbers?8:7,300);cell.className="ttip";if(this.isPopup){cell.ttip=Calendar._TT["DRAG_TO_MOVE"];cell.style.cursor="move";}this.tooltips=cell;div=Calendar.createElement("div",this.element);this.monthsCombo=div;div.className="combo";for(i=0;i0;--i){var yr=Calendar.createElement("div");yr.className=Calendar.is_ie?"label-IEfix":"label";yr.appendChild(document.createTextNode(""));div.appendChild(yr);}this._init(this.mondayFirst,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){if(!window.calendar){return false;}(Calendar.is_ie)&&(ev=window.event);var cal=window.calendar;var act=(Calendar.is_ie||ev.type=="keypress");if(ev.ctrlKey){switch(ev.keyCode){case 37:act&&Calendar.cellClick(cal._nav_pm);break;case 38:act&&Calendar.cellClick(cal._nav_py);break;case 39:act&&Calendar.cellClick(cal._nav_nm);break;case 40:act&&Calendar.cellClick(cal._nav_ny);break;default:return false;}}else switch(ev.keyCode){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.hide();break;case 37:case 38:case 39:case 40:if(act){var date=cal.date.getDate()-1;var el=cal.currentDateEl;var ne=null;var prev=(ev.keyCode==37)||(ev.keyCode==38);switch(ev.keyCode){case 37:(--date>=0)&&(ne=cal.ar_days[date]);break;case 38:date-=7;(date>=0)&&(ne=cal.ar_days[date]);break;case 39:(++datethis.maxYear){year=this.maxYear;date.setFullYear(year);}this.mondayFirst=mondayFirst;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var wday=date.getDay();var MON=mondayFirst?1:0;var SAT=mondayFirst?5:6;var SUN=mondayFirst?6:0;if(mondayFirst){wday=(wday>0)?(wday-1):6;}var iday=1;var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var hasToday=((today.getFullYear()==year)&&(today.getMonth()==month));var todayDate=today.getDate();var week_number=date.getWeekNumber();var ar_days=new Array();for(var i=0;i<6;++i){if(iday>no_days){row.className="emptyrow";row=row.nextSibling;continue;}var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.firstChild.data=week_number;cell=cell.nextSibling;}++week_number;row.className="daysrow";for(var j=0;j<7;++j){cell.className="day";if((!i&&jno_days){cell.innerHTML=" ";cell.disabled=true;cell=cell.nextSibling;continue;}cell.disabled=false;cell.firstChild.data=iday;if(typeof this.getDateStatus=="function"){date.setDate(iday);var status=this.getDateStatus(date,year,month,iday);if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){ar_days[ar_days.length]=cell;cell.caldate=iday;cell.ttip="_";if(iday==mday){cell.className+=" selected";this.currentDateEl=cell;}if(hasToday&&(iday==todayDate)){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(wday==SAT||wday==SUN){cell.className+=" weekend";}}++iday;((++wday)^ 7)||(wday=0);cell=cell.nextSibling;}row=row.nextSibling;}this.ar_days=ar_days;this.title.firstChild.data=Calendar._MN[month]+", "+year;this.onSetTime();};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.mondayFirst,date);}};Calendar.prototype.refresh=function(){this._init(this.mondayFirst,this.date);};Calendar.prototype.setMondayFirst=function(mondayFirst){this._init(mondayFirst,this.date);this._displayWeekdays();};Calendar.prototype.setDateStatusHandler=Calendar.prototype.setDisabledHandler=function(unaryFunction){this.getDateStatus=unaryFunction;};Calendar.prototype.setRange=function(a,z){this.minYear=a;this.maxYear=z;};Calendar.prototype.callHandler=function(){if(this.onSelected){this.onSelected(this,this.date.print(this.dateFormat));}};Calendar.prototype.callCloseHandler=function(){if(this.onClose){this.onClose(this);}this.hideShowCovered();};Calendar.prototype.destroy=function(){var el=this.element.parentNode;el.removeChild(this.element);Calendar._C=null;window.calendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){if(!window.calendar){return false;}var el=Calendar.is_ie?Calendar.getElement(ev):Calendar.getTargetElement(ev);for(;el!=null&&el!=calendar.element;el=el.parentNode);if(el==null){window.calendar.callCloseHandler();return Calendar.stopEvent(ev);}};Calendar.prototype.show=function(){var rows=this.table.getElementsByTagName("tr");for(var i=rows.length;i>0;){var row=rows[--i];Calendar.removeClass(row,"rowhilite");var cells=row.getElementsByTagName("td");for(var j=cells.length;j>0;){var cell=cells[--j];Calendar.removeClass(cell,"hilite");Calendar.removeClass(cell,"active");}}this.element.style.display="block";this.hidden=false;if(this.isPopup){window.calendar=this;Calendar.addEvent(document,"keydown",Calendar._keyEvent);Calendar.addEvent(document,"keypress",Calendar._keyEvent);Calendar.addEvent(document,"mousedown",Calendar._checkCalendar);}this.hideShowCovered();};Calendar.prototype.hide=function(){if(this.isPopup){Calendar.removeEvent(document,"keydown",Calendar._keyEvent);Calendar.removeEvent(document,"keypress",Calendar._keyEvent);Calendar.removeEvent(document,"mousedown",Calendar._checkCalendar);}this.element.style.display="none";this.hidden=true;this.hideShowCovered();};Calendar.prototype.showAt=function(x,y){var s=this.element.style;s.left=x+"px";s.top=y+"px";this.show();};Calendar.prototype.showAtElement=function(el,opts){var self=this;var p=Calendar.getAbsolutePos(el);if(!opts||typeof opts!="string"){this.showAt(p.x,p.y+el.offsetHeight);return true;}this.element.style.display="block";Calendar.continuation_for_the_fucking_khtml_browser=function(){var w=self.element.offsetWidth;var h=self.element.offsetHeight;self.element.style.display="none";var valign=opts.substr(0,1);var halign="l";if(opts.length>1){halign=opts.substr(1,1);}switch(valign){case "T":p.y-=h;break;case "B":p.y+=el.offsetHeight;break;case "C":p.y+=(el.offsetHeight-h)/2;break;case "t":p.y+=el.offsetHeight-h;break;case "b":break;}switch(halign){case "L":p.x-=w;break;case "R":p.x+=el.offsetWidth;break;case "C":p.x+=(el.offsetWidth-w)/2;break;case "r":p.x+=el.offsetWidth-w;break;case "l":break;}self.showAt(p.x,p.y);};if(Calendar.is_khtml)setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()",10);else Calendar.continuation_for_the_fucking_khtml_browser();};Calendar.prototype.setDateFormat=function(str){this.dateFormat=str;};Calendar.prototype.setTtDateFormat=function(str){this.ttDateFormat=str;};Calendar.prototype.parseDate=function(str,fmt){var y=0;var m=-1;var d=0;var a=str.split(/\W+/);if(!fmt){fmt=this.dateFormat;}var b=[];fmt.replace(/(%.)/g,function(str,par){return b[b.length]=par;});var i=0,j=0;var hr=0;var min=0;for(i=0;i29)?1900:2000);}if(b[i]=="%b"||b[i]=="%B"){for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}}else if(/%[HIkl]/.test(b[i])){hr=parseInt(a[i],10);}else if(/%[pP]/.test(b[i])){if(/pm/i.test(a[i])&&hr<12)hr+=12;}else if(b[i]=="%M"){min=parseInt(a[i],10);}}if(y!=0&&m!=-1&&d!=0){this.setDate(new Date(y,m,d,hr,min,0));return;}y=0;m=-1;d=0;for(i=0;i31&&y==0){y=parseInt(a[i],10);(y<100)&&(y+=(y>29)?1900:2000);}else if(d==0){d=a[i];}}if(y==0){var today=new Date();y=today.getFullYear();}if(m!=-1&&d!=0){this.setDate(new Date(y,m,d,hr,min,0));}};Calendar.prototype.hideShowCovered=function(){var self=this;Calendar.continuation_for_the_fucking_khtml_browser=function(){function getVisib(obj){var value=obj.style.visibility;if(!value){if(document.defaultView&&typeof(document.defaultView.getComputedStyle)=="function"){if(!Calendar.is_khtml)value=document.defaultView. getComputedStyle(obj,"").getPropertyValue("visibility");else value='';}else if(obj.currentStyle){value=obj.currentStyle.visibility;}else value='';}return value;};var tags=new Array("applet","iframe","select");var el=self.element;var p=Calendar.getAbsolutePos(el);var EX1=p.x;var EX2=el.offsetWidth+EX1;var EY1=p.y;var EY2=el.offsetHeight+EY1;for(var k=tags.length;k>0;){var ar=document.getElementsByTagName(tags[--k]);var cc=null;for(var i=ar.length;i>0;){cc=ar[--i];p=Calendar.getAbsolutePos(cc);var CX1=p.x;var CX2=cc.offsetWidth+CX1;var CY1=p.y;var CY2=cc.offsetHeight+CY1;if(self.hidden||(CX1>EX2)||(CX2EY2)||(CY24)&&(day-=4)||(day+=3);return Math.round(((time/Date.DAY)+day)/7);};Date.prototype.equalsTo=function(date){return((this.getFullYear()==date.getFullYear())&&(this.getMonth()==date.getMonth())&&(this.getDate()==date.getDate())&&(this.getHours()==date.getHours())&&(this.getMinutes()==date.getMinutes()));};Date.prototype.print=function(str){var m=this.getMonth();var d=this.getDate();var y=this.getFullYear();var wn=this.getWeekNumber();var w=this.getDay();var s={};var hr=this.getHours();var pm=(hr>=12);var ir=(pm)?(hr-12):hr;var dy=this.getDayOfYear();if(ir==0)ir=12;var min=this.getMinutes();var sec=this.getSeconds();s["%a"]=Calendar._SDN[w];s["%A"]=Calendar._DN[w];s["%b"]=Calendar._SMN[m];s["%B"]=Calendar._MN[m];s["%C"]=1+Math.floor(y/100);s["%d"]=(d<10)?("0"+d):d;s["%e"]=d;s["%H"]=(hr<10)?("0"+hr):hr;s["%I"]=(ir<10)?("0"+ir):ir;s["%j"]=(dy<100)?((dy<10)?("00"+dy):("0"+dy)):dy;s["%k"]=hr;s["%l"]=ir;s["%m"]=(m<9)?("0"+(1+m)):(1+m);s["%M"]=(min<10)?("0"+min):min;s["%n"]="\n";s["%p"]=pm?"PM":"AM";s["%P"]=pm?"pm":"am";s["%s"]=Math.floor(this.getTime()/1000);s["%S"]=(sec<10)?("0"+sec):sec;s["%t"]="\t";s["%U"]=s["%W"]=s["%V"]=(wn<10)?("0"+wn):wn;s["%u"]=w+1;s["%w"]=w;s["%y"]=(''+y).substr(2,2);s["%Y"]=y;s["%%"]="%";var re=Date._msh_formatRegexp;if(typeof re=="undefined"){var tmp="";for(var i in s)tmp+=tmp?("|"+i):i;Date._msh_formatRegexp=re=new RegExp("("+tmp+")",'g');}return str.replace(re,function(match,par){return s[par];});};window.calendar=null; \ No newline at end of file -- cgit v1.2.1 From 4800ea73170e55c621ab9b6c1625c429799cafbf Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 8 Nov 2003 12:59:17 +0000 Subject: pass paybatch field to realtime_bop so we can prevent double-charges --- FS/FS/ClientAPI/MyAccount.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 8d6f6e55a..6bb664dfe 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -203,7 +203,7 @@ sub process_payment { my $error = $cust_main->realtime_bop( 'CC', $p->{'amount'}, quiet=>1, 'paydate' => $p->{'year'}. '-'. $p->{'month'}. '-01', map { $_ => $p->{$_} } - qw( payname address1 address2 city state zip payinfo ) + qw( payname address1 address2 city state zip payinfo paybatch ) ); return { 'error' => $error } if $error; -- cgit v1.2.1 From 8bf0686b09cde9eb2aa453420e3a7bcc4ada9f88 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 8 Nov 2003 16:31:49 +0000 Subject: documentation for self-service functions! --- fs_selfservice/FS-SelfService/SelfService.pm | 559 +++++++++++++++++++++++++-- 1 file changed, 536 insertions(+), 23 deletions(-) diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm index b2532be71..be6dd3a8a 100644 --- a/fs_selfservice/FS-SelfService/SelfService.pm +++ b/fs_selfservice/FS-SelfService/SelfService.pm @@ -27,6 +27,9 @@ $socket .= '.'.$tag if defined $tag && length($tag); 'cancel' => 'MyAccount/cancel', 'payment_info' => 'MyAccount/payment_info', 'process_payment' => 'MyAccount/process_payment', + 'list_pkgs' => 'MyAccount/list_pkgs', + 'order_pkg' => 'MyAccount/order_pkg', + 'cancel_pkg' => 'MyAccount/cancel_pkg', 'signup_info' => 'Signup/signup_info', 'new_customer' => 'Signup/new_customer', ); @@ -42,29 +45,6 @@ $ENV{'BASH_ENV'} = ''; my $freeside_uid = scalar(getpwnam('freeside')); die "not running as the freeside user\n" if $> != $freeside_uid; -=head1 NAME - -FS::SelfService - Freeside self-service API - -=head1 SYNOPSIS - -=head1 DESCRIPTION - -Use this API to implement your own client "self-service" module. - -If you just want to customize the look of the existing "self-service" module, -see XXXX instead. - -=head1 FUNCTIONS - -=over 4 - -=item passwd - -Returns the empty value on success, or an error message on errors. - -=cut - foreach my $autoload ( keys %autoload ) { my $eval = @@ -105,6 +85,539 @@ sub simple_packet { $return; } +=head1 NAME + +FS::SelfService - Freeside self-service API + +=head1 SYNOPSIS + + # password and shell account changes + use FS::SelfService qw(passwd chfn chsh); + + # "my account" functionality + use FS::SelfService qw( login customer_info invoice cancel payment_info process_payment ); + + my $rv = login( { 'username' => $username, + 'domain' => $domain, + 'password' => $password, + } + ); + + if ( $rv->{'error'} ) { + #handle login error... + } else { + #successful login + my $session_id = $rv->{'session_id'}; + } + + my $customer_info = customer_info( { 'session_id' => $session_id } ); + + #payment_info and process_payment are available in 1.5+ only + my $payment_info = payment_info) { 'session_id' => $session_id } ); + + #!!! process_payment example + + #!!! list_pkgs example + + #!!! order_pkg example + + #!!! cancel_pkg example + + # signup functionality + use FS::SelfService qw( signup_info new_customer ); + + my $signup_info = signup_info; + + $rv = new_customer( { + 'first' => $first, + 'last' => $last, + 'company' => $company, + 'address1' => $address1, + 'address2' => $address2, + 'city' => $city, + 'state' => $state, + 'zip' => $zip, + 'country' => $country, + 'daytime' => $daytime, + 'night' => $night, + 'fax' => $fax, + 'payby' => $payby, + 'payinfo' => $payinfo, + 'paycvv' => $paycvv, + 'paydate' => $paydate, + 'payname' => $payname, + 'invoicing_list' => $invoicing_list, + 'referral_custnum' => $referral_custnum, + 'pkgpart' => $pkgpart, + 'username' => $username, + '_password' => $password, + 'popnum' => $popnum, + 'agentnum' => $agentnum, + } + ); + + my $error = $rv->{'error'}; + if ( $error eq '_decline' ) { + print_decline(); + } elsif ( $error ) { + reprint_signup(); + } else { + print_success(); + } + +=head1 DESCRIPTION + +Use this API to implement your own client "self-service" module. + +If you just want to customize the look of the existing "self-service" module, +see XXXX instead. + +=head1 PASSWORD, GECOS, SHELL CHANGING FUNCTIONS + +=over 4 + +=item passwd + +=item chfn + +=item chsh + +=back + +=head1 "MY ACCOUNT" FUNCTIONS + +=over 4 + +=item login HASHREF + +Creates a user session. Takes a hash reference as parameter with the +following keys: + +=over 4 + +=item username + +=item domain + +=item password + +=back + +Returns a hash reference with the following keys: + +=over 4 + +=item error + +Empty on success, or an error message on errors. + +=item session_id + +Session identifier for successful logins + +=back + +=item customer_info HASHREF + +Returns general customer information. + +Takes a hash reference as parameter with a single key: B + +Returns a hash reference with the following keys: + +=over 4 + +=item name + +Customer name + +=item balance + +Balance owed + +=item open + +Array reference of hash references of open inoices. Each hash reference has +the following keys: invnum, date, owed + +=item small_custview + +An HTML fragment containing shipping and billing addresses. + +=back + +=item invoice HASHREF + +Returns an invoice. Takes a hash reference as parameter with two keys: +session_id and invnum + +Returns a hash reference with the following keys: + +=over 4 + +=item error + +Empty on success, or an error message on errors + +=item invnum + +Invoice number + +=item invoice_text + +Invoice text + +=back + +=item cancel HASHREF + +Cancels this customer. + +Takes a hash reference as parameter with a single key: B + +Returns a hash reference with a single key, B, which is empty on +success or an error message on errors. + +=item payment_info HASHREF + +Returns information that may be useful in displaying a payment page. + +Takes a hash reference as parameter with a single key: B. + +Returns a hash reference with the following keys: + +=over 4 + +=item error + +Empty on success, or an error message on errors + +=item balance + +Balance owed + +=item payname + +Exact name on credit card (CARD/DCRD) + +=item address1 + +=item address2 + +=item city + +=item state + +=item zip + +=item payby + +Customer's current default payment type. + +=item card_type + +For CARD/DCRD payment types, the card type (Visa card, MasterCard, Discover card, American Express card, etc.) + +=item payinfo + +For CARD/DCRD payment types, the card number + +=item month + +For CARD/DCRD payment types, expiration month + +=item year + +For CARD/DCRD payment types, expiration year + +=item cust_main_county + +County/state/country data - array reference of hash references, each of which has the fields of a cust_main_county record (see L). Note these are not FS::cust_main_county objects, but hash references of columns and values. + +=item states + +Array reference of all states in the current default country. + +=item card_types + +Hash reference of card types; keys are card types, values are the exact strings +passed to the process_payment function + +=item paybatch + +Unique transaction identifier (prevents multiple charges), passed to the +process_payment function + +=back + +=item process_payment HASHREF + +Processes a payment and possible change of address or payment type. Takes a +hash reference as parameter with the following keys: + +=over 4 + +=item session_id + +=item save + +If true, address and card information entered will be saved for subsequent +transactions. + +=item auto + +If true, future credit card payments will be done automatically (sets payby to +CARD). If false, future credit card payments will be done on-demand (sets +payby to DCRD). This option only has meaning if B is set true. + +=item payname + +=item address1 + +=item address2 + +=item city + +=item state + +=item zip + +=item payinfo + +Card number + +=item month + +Card expiration month + +=item year + +Card expiration year + +=item paybatch + +Unique transaction identifier, returned from the payment_info function. +Prevents multiple charges. + +=back + +Returns a hash reference with a single key, B, empty on success, or an +error message on errors + +=item list_pkgs + +Returns package information for this customer. + +Takes a hash reference as parameter with a single key: B + +Returns a hash reference containing customer package information. The hash reference contains the following keys: + +=over 4 + +=item cust_pkg HASHREF + +Array reference of hash references, each of which has the fields of a cust_pkg record (see L). Note these are not FS::cust_pkg objects, but hash references of columns and values. + +=back + +=item order_pkg + +Orders a package for this customer. + +Takes a hash reference as parameter with the following keys: + +=over 4 + +=item session_id + +=item pkgpart + +=item svcpart + +optional svcpart, required only if the package definition does not contain +one svc_acct service definition with quantity 1 (it may contain others with +quantity >1) + +=item username + +=item _password + +=item sec_phrase + +=item popnum + +=back + +Returns a hash reference with a single key, B, empty on success, or an +error message on errors. The special error '_decline' is returned for +declined transactions. + +=item cancel_pkg + +Cancels a package for this customer. + +Takes a hash reference as parameter with the following keys: + +=over 4 + +=item session_id + +=item pkgpart + +=back + +Returns a hash reference with a single key, B, empty on success, or an +error message on errors. + +=back + +=head1 SIGNUP FUNCTIONS + +=over 4 + +=item signup_info + +Returns a hash reference containing information that may be useful in +displaying a signup page. The hash reference contains the following keys: + +=over 4 + +=item cust_main_county + +County/state/country data - array reference of hash references, each of which has the fields of a cust_main_county record (see L). Note these are not FS::cust_main_county objects, but hash references of columns and values. + +=item part_pkg + +Available packages - array reference of hash references, each of which has the fields of a part_pkg record (see L). Each hash reference also has an additional 'payby' field containing an array reference of acceptable payment types specific to this package (see below and L). Note these are not FS::part_pkg objects, but hash references of columns and values. Requires the 'signup_server-default_agentnum' configuration value to be set. + +=item agent + +Array reference of hash references, each of which has the fields of an agent record (see L). Note these are not FS::agent objects, but hash references of columns and values. + +=item agentnum2part_pkg + +Hash reference; keys are agentnums, values are array references of available packages for that agent, in the same format as the part_pkg arrayref above. + +=item svc_acct_pop + +Access numbers - array reference of hash references, each of which has the fields of an svc_acct_pop record (see L). Note these are not FS::svc_acct_pop objects, but hash references of columns and values. + +=item security_phrase + +True if the "security_phrase" feature is enabled + +=item payby + +Array reference of acceptable payment types for signup + +=over 4 + +=item CARD (credit card - automatic) + +=item DCRD (credit card - on-demand - version 1.5+ only) + +=item CHEK (electronic check - automatic) + +=item DCHK (electronic check - on-demand - version 1.5+ only) + +=item LECB (Phone bill billing) + +=item BILL (billing, not recommended for signups) + +=item COMP (free, definately not recommended for signups) + +=item PREPAY (special billing type: applies a credit (see FS::prepay_credit) and sets billing type to BILL) + +=back + +=item cvv_enabled + +True if CVV features are available (1.5+ or 1.4.2 with CVV schema patch) + +=item msgcat + +Hash reference of message catalog values, to support error message customization. Currently available keys are: passwords_dont_match, invalid_card, unknown_card_type, and not_a (as in "Not a Discover card"). Values are configured in the web interface under "View/Edit message catalog". + +=item statedefault + +Default state + +=item countrydefault + +Default country + +=back + +=item new_customer HASHREF + +Creates a new customer. Takes a hash reference as parameter with the +following keys: + +=over 4 + +=item first - first name (required) + +=item last - last name (required) + +=item ss (not typically collected; mostly used for ACH transactions) + +=item company + +=item address1 (required) + +=item address2 + +=item city (required) + +=item county + +=item state (required) + +=item zip (required) + +=item daytime - phone + +=item night - phone + +=item fax - phone + +=item payby - CARD, DCRD, CHEK, DCHK, LECB, BILL, COMP or PREPAY (see L (required) + +=item payinfo - Card number for CARD/DCRD, account_number@aba_number for CHEK/DCHK, prepaid "pin" for PREPAY, purchase order number for BILL + +=item paycvv - Credit card CVV2 number (1.5+ or 1.4.2 with CVV schema patch) + +=item paydate - Expiration date for CARD/DCRD + +=item payname - Exact name on credit card for CARD/DCRD, bank name for CHEK/DCHK + +=item invoicing_list - comma-separated list of email addresses for email invoices. The special value 'POST' is used to designate postal invoicing (it may be specified alone or in addition to email addresses), + +=item referral_custnum - referring customer number + +=item pkgpart - pkgpart of initial package + +=item username + +=item _password + +=item sec_phrase - security phrase + +=item popnum - access number (index, not the literal number) + +=item agentnum - agent number + +=back + +Returns a hash reference with the following keys: + +=over 4 + +=item error Empty on success, or an error message on errors. The special error '_decline' is returned for declined transactions; other error messages should be suitable for display to the user (and are customizable in under Sysadmin | View/Edit message catalog) + +=back + + =back =head1 BUGS -- cgit v1.2.1 From 7d64a6acdc011b65b495e9ee4288c86be28d6897 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 8 Nov 2003 16:36:48 +0000 Subject: add order_pkg and cancel_pkg functions to self-service --- FS/FS/ClientAPI/MyAccount.pm | 116 +++++++++++++++++++++++++++++++++++++++++++ FS/FS/cust_main.pm | 35 ++++++++++--- 2 files changed, 144 insertions(+), 7 deletions(-) diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 6bb664dfe..c9a749424 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -14,6 +14,7 @@ use FS::svc_domain; use FS::cust_main; use FS::cust_bill; use FS::cust_main_county; +use FS::cust_pkg; use FS::ClientAPI; #hmm FS::ClientAPI->register_handlers( @@ -23,6 +24,9 @@ FS::ClientAPI->register_handlers( 'MyAccount/cancel' => \&cancel, 'MyAccount/payment_info' => \&payment_info, 'MyAccount/process_payment' => \&process_payment, + 'MyAccount/list_pkgs' => \&list_pkgs, + 'MyAccount/order_pkg' => \&order_pkg, + 'MyAccount/cancel_pkg' => \&cancel_pkg, ); #store in db? @@ -253,5 +257,117 @@ sub cancel { } +sub list_pkgs { + my $p = shift; + my $session = $cache->get($p->{'session_id'}) + or return { 'error' => "Can't resume session" }; #better error message + + my $custnum = $session->{'custnum'}; + + my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) + or return { 'error' => "unknown custnum $custnum" }; + + return { 'cust_pkg' => [ map { $_->hashref } $cust_main->ncancelled_pkgs ] }; + +} + +sub order_pkg { + my $p = shift; + my $session = $cache->get($p->{'session_id'}) + or return { 'error' => "Can't resume session" }; #better error message + + my $custnum = $session->{'custnum'}; + + my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) + or return { 'error' => "unknown custnum $custnum" }; + + #false laziness w/ClientAPI/Signup.pm + + my $cust_pkg = new FS::cust_pkg ( { + 'custnum' => $custnum, + 'pkgpart' => $p->{'pkgpart'}, + } ); + my $error = $cust_pkg->check; + return { 'error' => $error } if $error; + + my $svc_acct = new FS::svc_acct ( { + 'svcpart' => $p->{'svcpart'} || $cust_pkg->part_pkg->svcpart('svc_acct'), + map { $_ => $p->{$_} } + qw( username _password sec_phrase popnum ), + } ); + + my @acct_snarf; + my $snarfnum = 1; + while ( length($p->{"snarf_machine$snarfnum"}) ) { + my $acct_snarf = new FS::acct_snarf ( { + 'machine' => $p->{"snarf_machine$snarfnum"}, + 'protocol' => $p->{"snarf_protocol$snarfnum"}, + 'username' => $p->{"snarf_username$snarfnum"}, + '_password' => $p->{"snarf_password$snarfnum"}, + } ); + $snarfnum++; + push @acct_snarf, $acct_snarf; + } + $svc_acct->child_objects( \@acct_snarf ); + + my $y = $svc_acct->setdefault; # arguably should be in new method + return { 'error' => $y } if $y && !ref($y); + + $error = $svc_acct->check; + return { 'error' => $error } if $error; + + use Tie::RefHash; + tie my %hash, 'Tie::RefHash'; + %hash = ( $cust_pkg => [ $svc_acct ] ); + #msgcat + $error = $cust_main->order_pkgs( \%hash, '', 'noexport' => 1 ); + return { 'error' => $error } if $error; + + my $conf = new FS::Conf; + if ( $conf->exists('signup_server-realtime') ) { + + my $old_balance = $cust_main->balance; + + my $bill_error = $cust_main->bill; + $cust_main->apply_payments; + $cust_main->apply_credits; + $bill_error = $cust_main->collect; + + if ( $cust_main->balance > $old_balance ) { + $cust_pkg->cancel('quiet'=>1); + return { 'error' => '_decline' }; + } else { + $cust_pkg->reexport; + } + + } else { + $cust_pkg->reexport; + } + + return { error => '' }; + +} + +sub cancel_pkg { + my $p = shift; + my $session = $cache->get($p->{'session_id'}) + or return { 'error' => "Can't resume session" }; #better error message + + my $custnum = $session->{'custnum'}; + + my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ) + or return { 'error' => "unknown custnum $custnum" }; + + my $pkgnum = $session->{'pkgnum'}; + + my $cust_pkg = qsearchs('cust_pkg', { 'custnum' => $custnum, + 'pkgnum' => $pkgnum, } ) + or return { 'error' => "unknown pkgnum $pkgnum" }; + + my $error = $cust_main->cancel( 'quiet'=>1 ); + return { 'error' => $error }; + +} + 1; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 2c89bb065..8511e1a45 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -294,8 +294,8 @@ sub insert { } # packages - local $FS::svc_Common::noexport_hack = 1 if $options{'noexport'}; - $error = $self->order_pkgs($cust_pkgs, \$seconds); + #local $FS::svc_Common::noexport_hack = 1 if $options{'noexport'}; + $error = $self->order_pkgs($cust_pkgs, \$seconds, %options); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; @@ -329,9 +329,27 @@ sub insert { } -=item order_pkgs +=item order_pkgs HASHREF, [ , OPTION => VALUE ... ] ] + +Like the insert method on an existing record, this method orders a package +and included services atomicaly. Pass a Tie::RefHash data structure to this +method containing FS::cust_pkg and FS::svc_I objects. There should +be a better explanation of this, but until then, here's an example: + + use Tie::RefHash; + tie %hash, 'Tie::RefHash'; #this part is important + %hash = ( + $cust_pkg => [ $svc_acct ], + ... + ); + $cust_main->order_pkgs( \%hash, 'noexport'=>1 ); + +Currently available options are: I -document me. like ->insert(%cust_pkg) on an existing record +If I is set true, no provisioning jobs (exports) are scheduled. +(You can schedule them later with the B method for each +cust_pkg object. Using the B method on the cust_main object is not +recommended, as existing services will also be reexported.) =cut @@ -339,6 +357,7 @@ sub order_pkgs { my $self = shift; my $cust_pkgs = shift; my $seconds = shift; + my %options = @_; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -351,6 +370,8 @@ sub order_pkgs { local $FS::UID::AutoCommit = 0; my $dbh = dbh; + local $FS::svc_Common::noexport_hack = 1 if $options{'noexport'}; + foreach my $cust_pkg ( keys %$cust_pkgs ) { $cust_pkg->custnum( $self->custnum ); my $error = $cust_pkg->insert; @@ -379,9 +400,9 @@ sub order_pkgs { =item reexport -document me. Re-schedules all exports by calling the B method -of all associated packages (see L). If there is an error, -returns the error; otherwise returns false. +Re-schedules all exports by calling the B method of all associated +packages (see L). If there is an error, returns the error; +otherwise returns false. =cut -- cgit v1.2.1 From 066c1665efce43307446207a33f876b0baefe1d6 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 10 Nov 2003 13:54:21 +0000 Subject: hmm forgot to check this in? --- httemplate/misc/process/meta-import.cgi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httemplate/misc/process/meta-import.cgi b/httemplate/misc/process/meta-import.cgi index 2939c8fb2..59d236f64 100644 --- a/httemplate/misc/process/meta-import.cgi +++ b/httemplate/misc/process/meta-import.cgi @@ -116,8 +116,8 @@ function SafeOnsubmit() { #hashmaker widget sub hashmaker { my($name, $from, $to, $labelfrom, $labelto) = @_; - $fromsize = scalar(@$from); - $tosize = scalar(@$to); + my $fromsize = scalar(@$from); + my $tosize = scalar(@$to); " - - <% for my $period ( values %past ) { $sth->execute($part_referral->refnum, $today-$period) diff --git a/httemplate/browse/svc_acct_pop.cgi b/httemplate/browse/svc_acct_pop.cgi index f77fe820b..44cda81ad 100755 --- a/httemplate/browse/svc_acct_pop.cgi +++ b/httemplate/browse/svc_acct_pop.cgi @@ -48,7 +48,9 @@ foreach my $svc_acct_pop ( sort { <%= $svc_acct_pop->loc %> <% } %> -- cgit v1.2.1 From b82db29351ceec512b527f415d91b7b462172a85 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 11 Nov 2003 08:01:23 +0000 Subject: remove spaces between parens and contact name --- httemplate/search/report_receivables.cgi | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index d0665164c..60abb12ab 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -97,9 +97,7 @@ END <% while ( my $row = $sth->fetchrow_hashref() ) { %> -- cgit v1.2.1 From 8cb3d9ff1a0d7e82b603e0ce15e4a62c182537c4 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 11 Nov 2003 08:35:43 +0000 Subject: really fix advertising source edit links --- httemplate/browse/part_referral.cgi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httemplate/browse/part_referral.cgi b/httemplate/browse/part_referral.cgi index 270740cb8..69bb95aa5 100755 --- a/httemplate/browse/part_referral.cgi +++ b/httemplate/browse/part_referral.cgi @@ -44,9 +44,9 @@ foreach my $part_referral ( sort { } qsearch('part_referral',{}) ) { %> - - <% for my $period ( values %past ) { $sth->execute($part_referral->refnum, $today-$period) -- cgit v1.2.1 From 024ff792bac5bff12b02f08dbdba8134f82fe1dc Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 11 Nov 2003 14:21:02 +0000 Subject: simple change to cust_svc creation to help imports with svcnums --- FS/FS/svc_Common.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 7bc155d9d..73e08f239 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -119,9 +119,11 @@ sub insert { my $svcnum = $self->svcnum; my $cust_svc; - unless ( $svcnum ) { + #unless ( $svcnum ) { + if ( ! $svcnum || ! qsearchs('cust_svc',{'svcnum'=>$self->svcnum} ) ) { $cust_svc = new FS::cust_svc ( { #hua?# 'svcnum' => $svcnum, + 'svcnum' => $self->svcnum, 'pkgnum' => $self->pkgnum, 'svcpart' => $self->svcpart, } ); -- cgit v1.2.1 From 267aa3cd085d1563915207b5bb31df8dc3ab2fb8 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 11 Nov 2003 14:39:08 +0000 Subject: fix up virtual field reprucussions --- FS/FS/svc_Common.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 73e08f239..fa7996d70 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -49,7 +49,9 @@ sub virtual_fields { if ($self->svcpart) { # Case 1 $svcpart = $self->svcpart; - } elsif ( $self->svcnum ) { #Case 2 + } elsif ( $self->svcnum + && qsearchs('cust_svc',{'svcnum'=>$self->svcnum} ) + ) { #Case 2 $svcpart = $self->cust_svc->svcpart; } else { # Case 3 $svcpart = ''; -- cgit v1.2.1 From 868b7d4ab4af175617ce656a95ebc82b3fd1576f Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 11 Nov 2003 15:03:54 +0000 Subject: also make setx behave when setting svcnum during an import --- FS/FS/svc_Common.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index fa7996d70..89da604ef 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -312,7 +312,7 @@ sub setx { #get part_svc my $svcpart; - if ( $self->svcnum ) { + if ( $self->svcnum && qsearchs('cust_svc', {'svcnum'=>$self->svcnum}) ) { my $cust_svc = $self->cust_svc; return "Unknown svcnum" unless $cust_svc; $svcpart = $cust_svc->svcpart; -- cgit v1.2.1 From b16fc4133f24d3844755fe45243e437c9c35769d Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 12 Nov 2003 11:22:53 +0000 Subject: better error msg --- FS/FS/cust_pkg.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index d9a6385e2..5700b654e 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -148,7 +148,7 @@ sub insert { return $error if $error; my $cust_main = $self->cust_main; - return "Unknown customer ". $self->custnum unless $cust_main; + return "Unknown custnum: ". $self->custnum unless $cust_main; unless ( $disable_agentcheck ) { my $agent = qsearchs( 'agent', { 'agentnum' => $cust_main->agentnum } ); -- cgit v1.2.1 From 9a6f36bb2cb5cd349be52874868553491de78f05 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 12 Nov 2003 12:30:25 +0000 Subject: allow provisioning of unaudited services with a svcnum for imports --- FS/FS/svc_Common.pm | 6 +++--- FS/FS/svc_acct.pm | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 89da604ef..cadb997da 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -120,9 +120,9 @@ sub insert { return $error if $error; my $svcnum = $self->svcnum; - my $cust_svc; + my $cust_svc = $svcnum ? qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) : ''; #unless ( $svcnum ) { - if ( ! $svcnum || ! qsearchs('cust_svc',{'svcnum'=>$self->svcnum} ) ) { + if ( !$svcnum or !$cust_svc ) { $cust_svc = new FS::cust_svc ( { #hua?# 'svcnum' => $svcnum, 'svcnum' => $self->svcnum, @@ -136,7 +136,7 @@ sub insert { } $svcnum = $self->svcnum($cust_svc->svcnum); } else { - $cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum}); + #$cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum}); unless ( $cust_svc ) { $dbh->rollback if $oldAutoCommit; return "no cust_svc record found for svcnum ". $self->svcnum; diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 4c943a710..fce443634 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -226,7 +226,7 @@ sub insert { # 'domsvc' => $self->domsvc, # } ); - if ( $self->svcnum ) { + if ( $self->svcnum && qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) ) { my $cust_svc = qsearchs('cust_svc',{'svcnum'=>$self->svcnum}); unless ( $cust_svc ) { $dbh->rollback if $oldAutoCommit; -- cgit v1.2.1 From b793203b33ec10b058b82a56fe89fe760b9a42c2 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 13 Nov 2003 11:23:00 +0000 Subject: fix path in questionable section --- install/debian/3.0/INSTALL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/debian/3.0/INSTALL b/install/debian/3.0/INSTALL index ad3ad4f2b..ef28a91fc 100644 --- a/install/debian/3.0/INSTALL +++ b/install/debian/3.0/INSTALL @@ -22,7 +22,7 @@ su postgres -c "createuser -P freeside" su freeside -c "createdb freeside" #? -cd ../.. +cd ../../.. make install-perl-modules make create-config freeside-adduser -c -h /usr/local/etc/freeside/htpasswd ivan -- cgit v1.2.1 From 948d16c8038c5976c71a32b00a1ff3f46f97262e Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 14 Nov 2003 02:52:13 +0000 Subject: sort these case-insensitive --- httemplate/search/report_receivables.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index 60abb12ab..587108e33 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -71,7 +71,7 @@ where 0 < ,0 ) -order by company, last +order by lower(company), lower(last) END -- cgit v1.2.1 From 17b3560111539da506f7f6bb575dd3776375466f Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 14 Nov 2003 08:43:24 +0000 Subject: hopefully recover better from lost ssh connections --- FS/bin/freeside-selfservice-server | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/FS/bin/freeside-selfservice-server b/FS/bin/freeside-selfservice-server index 05fa72b43..d2358e33c 100644 --- a/FS/bin/freeside-selfservice-server +++ b/FS/bin/freeside-selfservice-server @@ -74,7 +74,13 @@ while (1) { warn "receiving packet from client\n" if $Debug; - my $packet = fd_retrieve($reader); + my $packet = eval { fd_retrieve($reader); }; + if ( $@ ) { + warn "Storable error receiving packet from client". + " (assuming lost connection): $@\n" + if $Debug; + last; + } warn "packet received\n". join('', map { " $_=>$packet->{$_}\n" } keys %$packet ) if $Debug > 1; @@ -120,6 +126,9 @@ while (1) { } + warn "connection lost, reconnecting\n" if $Debug; + sleep 3; + } ### -- cgit v1.2.1 From 3631b278f61e0dd08026f7a21ac2d24964f1ea99 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 15 Nov 2003 07:18:39 +0000 Subject: add trailing newline to supress useless error messages in log --- fs_selfservice/FS-SelfService/freeside-selfservice-clientd | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fs_selfservice/FS-SelfService/freeside-selfservice-clientd b/fs_selfservice/FS-SelfService/freeside-selfservice-clientd index a8b1e713b..925bce6d2 100644 --- a/fs_selfservice/FS-SelfService/freeside-selfservice-clientd +++ b/fs_selfservice/FS-SelfService/freeside-selfservice-clientd @@ -145,22 +145,22 @@ while (1) { #handle some commands weirdly? $packet->{_token}=$$; - warn "[child-$$] locking write stream" if $Debug > 1; + warn "[child-$$] locking write stream\n" if $Debug > 1; lock_write; - warn "[child-$$] sending packet to remote server" if $Debug > 1; + warn "[child-$$] sending packet to remote server\n" if $Debug > 1; nstore_fd($packet, \*STDOUT) or die "FATAL: can't send response: $!"; - warn "[child-$$] flushing write stream" if $Debug > 1; + warn "[child-$$] flushing write stream\n" if $Debug > 1; STDOUT->flush or die "FATAL: can't flush: $!"; - warn "[child-$$] releasing write lock" if $Debug > 1; + warn "[child-$$] releasing write lock\n" if $Debug > 1; unlock_write; - warn "[child-$$] closing write stream" if $Debug > 1; + warn "[child-$$] closing write stream\n" if $Debug > 1; close STDOUT or die "FATAL: can't close write stream: $!"; #??! - warn "[child-$$] waiting for response from parent" if $Debug > 1; + warn "[child-$$] waiting for response from parent\n" if $Debug > 1; my $w = new IO::Select; $w->add(\*STDIN); until ( $w->can_read ) { -- cgit v1.2.1 From ded62e3a3ab9b930593d36be3f2b32bda2433f07 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 15 Nov 2003 07:28:26 +0000 Subject: kill off ssh process when re-opening connection --- FS/bin/freeside-selfservice-server | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/FS/bin/freeside-selfservice-server b/FS/bin/freeside-selfservice-server index d2358e33c..f9571fa1e 100644 --- a/FS/bin/freeside-selfservice-server +++ b/FS/bin/freeside-selfservice-server @@ -79,6 +79,11 @@ while (1) { warn "Storable error receiving packet from client". " (assuming lost connection): $@\n" if $Debug; + if ( $ssh_pid ) { + warn "sending TERM signal to ssh process $ssh_pid\n" if $Debug; + kill 'TERM', $ssh_pid; + $ssh_pid = 0; + } last; } warn "packet received\n". -- cgit v1.2.1 From f628692253d949e00918e0d4ebc0fab89b657055 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 18 Nov 2003 11:17:12 +0000 Subject: remove thread/PerlIO warning - standard in 5.8.x and working fine --- httemplate/docs/install.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/docs/install.html b/httemplate/docs/install.html index 1355a113c..266708b1e 100644 --- a/httemplate/docs/install.html +++ b/httemplate/docs/install.html @@ -6,7 +6,7 @@ Note: Install Freeside on a firewalled, private server, not a public (web, RADIUS, etc.) server.

      Before installing, you need:
        -
      • Perl Don't enable experimental features like threads or the PerlIO abstraction layer. +
      • Perl
      • Apache (mod_ssl or Apache-SSL highly recommended)
      • mod_perl (if compiling your own mod_perl, make sure you set the EVERYTHING=1 compile-time option)
      • SSH (OpenSSH is recommended. SSH Communications Security commercial SSH version 3 has been reported incompatible with Freeside.) -- cgit v1.2.1 From 6569ee0ceffd720ca55594dc0f5a454187ee3b75 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 18 Nov 2003 15:05:17 +0000 Subject: add communigate_pro_singledomain export --- FS/FS/part_export.pm | 25 +++++++++++++++++++++++ FS/FS/part_export/communigate_pro.pm | 15 +++++++++----- FS/FS/part_export/communigate_pro_singledomain.pm | 11 ++++++++++ FS/MANIFEST | 1 + 4 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 FS/FS/part_export/communigate_pro_singledomain.pm diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm index 3dfaaee9b..8d058842e 100644 --- a/FS/FS/part_export.pm +++ b/FS/FS/part_export.pm @@ -754,6 +754,24 @@ tie my %communigate_pro_options, 'Tie::IxHash', }, ; +tie my %communigate_pro_singledomain_options, 'Tie::IxHash', + 'port' => { label=>'Port number', default=>'106', }, + 'login' => { label=>'The administrator account name. The name can contain a domain part.', }, + 'password' => { label=>'The administrator account password.', }, + 'domain' => { label=>'Domain', }, + 'accountType' => { label=>'Type for newly-created accounts', + type=>'select', + options=>[qw( MultiMailbox TextMailbox MailDirMailbox )], + default=>'MultiMailbox', + }, + 'externalFlag' => { label=> 'Create accounts with an external (visible for legacy mailers) INBOX.', + type=>'checkbox', + }, + 'AccessModes' => { label=>'Access modes', + default=>'Mail POP IMAP PWD WebMail WebSite', + }, +; + tie my %bind_options, 'Tie::IxHash', #'machine' => { label=>'named machine' }, 'named_conf' => { label => 'named.conf location', @@ -977,6 +995,13 @@ tie my %forward_shellcommands_options, 'Tie::IxHash', 'notes' => 'Real time export to a mail server. The CommuniGate Pro Perl Interface must be installed as CGP::CLI.', }, + 'communigate_pro_singledomain' => { + 'desc' => 'Real-time export to a CommuniGate Pro mail server, one domain only', + 'options' => \%communigate_pro_singledomain_options, + 'nodomain' => 'Y', + 'notes' => 'Real time export to a mail server. This is an unusual export to CommuniGate Pro that forces all accounts into a single domain. As CommuniGate Pro supports multiple domains, unless you have a specific reason for using this export, you probably want to use the communigate_pro export instead. The CommuniGate Pro Perl Interface must be installed as CGP::CLI.', + }, + }, 'svc_domain' => { diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm index 9a4539a4a..aa038f08e 100644 --- a/FS/FS/part_export/communigate_pro.pm +++ b/FS/FS/part_export/communigate_pro.pm @@ -8,10 +8,15 @@ use FS::queue; sub rebless { shift; } +sub export_username { + my($self, $svc_acct) = (shift, shift); + $svc_acct->email; +} + sub _export_insert { my( $self, $svc_acct ) = (shift, shift); my @options = ( $svc_acct->svcnum, 'CreateAccount', - 'accountName' => $svc_acct->email, + 'accountName' => $self->export_username($svc_acct), 'accountType' => $self->option('accountType'), 'AccessModes' => $self->option('AccessModes'), 'RealName' => $svc_acct->finger, @@ -47,7 +52,7 @@ sub _export_replace { #my $jobnum = $err_or_queue->jobnum; $self->communigate_pro_queue( $new->svcnum, 'SetAccountPassword', - $new->email, $new->_password ) + $self->export_username($new), $new->_password ) if $new->_password ne $old->_password; } @@ -55,14 +60,14 @@ sub _export_replace { sub _export_delete { my( $self, $svc_acct ) = (shift, shift); $self->communigate_pro_queue( $svc_acct->svcnum, 'DeleteAccount', - $svc_acct->email, + $self->export_username($svc_acct), ); } sub _export_suspend { my( $self, $svc_acct ) = (shift, shift); $self->communigate_pro_queue( $svc_acct->svcnum, 'UpdateAccountSettings', - 'accountName' => $svc_acct->email, + 'accountName' => $self->export_username($svc_acct), 'AccessModes' => 'Mail', ); } @@ -70,7 +75,7 @@ sub _export_suspend { sub _export_unsuspend { my( $self, $svc_acct ) = (shift, shift); $self->communigate_pro_queue( $svc_acct->svcnum, 'UpdateAccountSettings', - 'accountName' => $svc_acct->email, + 'accountName' => $self->export_username($svc_acct), 'AccessModes' => $self->option('AccessModes'), ); } diff --git a/FS/FS/part_export/communigate_pro_singledomain.pm b/FS/FS/part_export/communigate_pro_singledomain.pm new file mode 100644 index 000000000..11574af9b --- /dev/null +++ b/FS/FS/part_export/communigate_pro_singledomain.pm @@ -0,0 +1,11 @@ +package FS::part_export::communigate_pro_singledomain; + +use vars qw(@ISA); +use FS::part_export::communigate_pro; + +@ISA = qw(FS::part_export::communigate_pro); + +sub export_username { + my($self, $svc_acct) = (shift, shift); + $svc_acct->username. '@'. $self->option('domain'); +} diff --git a/FS/MANIFEST b/FS/MANIFEST index 934100af5..877f3ce0d 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -72,6 +72,7 @@ FS/part_export/bind.pm FS/part_export/bind_slave.pm FS/part_export/bsdshell.pm FS/part_export/communigate_pro.pm +FS/part_export/communigate_pro_singledomain.pm FS/part_export/cp.pm FS/part_export/cyrus.pm FS/part_export/domain_shellcommands.pm -- cgit v1.2.1 From bbbcc4f26d9205fda6bdf735d1c3d7451acbc575 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 18 Nov 2003 15:14:30 +0000 Subject: fix communigate pro export descriptions --- FS/FS/part_export.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm index 8d058842e..17e5cf908 100644 --- a/FS/FS/part_export.pm +++ b/FS/FS/part_export.pm @@ -992,14 +992,14 @@ tie my %forward_shellcommands_options, 'Tie::IxHash', 'communigate_pro' => { 'desc' => 'Real-time export to a CommuniGate Pro mail server', 'options' => \%communigate_pro_options, - 'notes' => 'Real time export to a mail server. The CommuniGate Pro Perl Interface must be installed as CGP::CLI.', + 'notes' => 'Real time export to a CommuniGate Pro mail server. The CommuniGate Pro Perl Interface must be installed as CGP::CLI.', }, 'communigate_pro_singledomain' => { 'desc' => 'Real-time export to a CommuniGate Pro mail server, one domain only', 'options' => \%communigate_pro_singledomain_options, 'nodomain' => 'Y', - 'notes' => 'Real time export to a mail server. This is an unusual export to CommuniGate Pro that forces all accounts into a single domain. As CommuniGate Pro supports multiple domains, unless you have a specific reason for using this export, you probably want to use the communigate_pro export instead. The CommuniGate Pro Perl Interface must be installed as CGP::CLI.', + 'notes' => 'Real time export to a CommuniGate Pro mail server. This is an unusual export to CommuniGate Pro that forces all accounts into a single domain. As CommuniGate Pro supports multiple domains, unless you have a specific reason for using this export, you probably want to use the communigate_pro export instead. The CommuniGate Pro Perl Interface must be installed as CGP::CLI.', }, }, -- cgit v1.2.1 From b9513f54e2be0c35a52848feef2aedd2c2fd2e3a Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 19 Nov 2003 01:29:58 +0000 Subject: disable debugging by default --- FS/FS/cust_bill.pm | 367 +++++++++++++++++++++++++++++++++++++++++++++++++---- FS/FS/cust_main.pm | 2 +- 2 files changed, 345 insertions(+), 24 deletions(-) diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index c15dad433..b49dac31c 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -647,7 +647,7 @@ sub batch_card { ''; } -=item print_text [TIME]; +=item print_text [ TIME [ , TEMPLATE ] ] Returns an text invoice, as a list of lines. @@ -667,6 +667,7 @@ sub print_text { $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') ) unless $cust_main->payname && $cust_main->payby ne 'CHEK'; + my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance # my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits #my $balance_due = $self->owed + $pr_total - $cr_total; @@ -749,28 +750,6 @@ sub print_text { $money_char. sprintf("%10.2f",$self->charged + $pr_total) ]; push @buf,['','']; - #credits - foreach ( $self->cust_credited ) { - - #something more elaborate if $_->amount ne $_->cust_credit->credited ? - - my $reason = substr($_->cust_credit->reason,0,32); - $reason .= '...' if length($reason) < length($_->cust_credit->reason); - $reason = " ($reason) " if $reason; - push @buf,[ - "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")". - $reason, - $money_char. sprintf("%10.2f",$_->amount) - ]; - } - #foreach ( @cr_cust_credit ) { - # push @buf,[ - # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")", - # $money_char. sprintf("%10.2f",$_->credited) - # ]; - #} - - #get & print payments foreach ( $self->cust_bill_pay ) { #something more elaborate if $_->amount ne ->cust_pay->paid ? @@ -877,6 +856,348 @@ sub print_text { } +=item print_ps [ TIME [ , TEMPLATE ] ] + +Returns an postscript invoice, as a scalar. + +TIME an optional value used to control the printing of overdue messages. The +default is now. It isn't the date of the invoice; that's the `_date' field. +It is specified as a UNIX timestamp; see L. Also see +L and L for conversion functions. + +=cut + +#still some false laziness w/print_text +sub print_ps { + + my( $self, $today, $template ) = @_; + $today ||= time; + +# my $invnum = $self->invnum; + my $cust_main = $self->cust_main; + $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') ) + unless $cust_main->payname && $cust_main->payby ne 'CHEK'; + + my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance +# my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits + #my $balance_due = $self->owed + $pr_total - $cr_total; + my $balance_due = $self->owed + $pr_total; + + #my @collect = (); + #my($description,$amount); + @buf = (); + + #create the template + my $templatefile = 'invoice_template_latex'; + $templatefile .= "_$template" if $template; + my @invoice_template = $conf->config($templatefile) + or die "cannot load config file $templatefile"; + + my %invoice_data = ( + 'invnum' => $self->invnum, + 'date' => time2str('%b %o, %Y', $self->_date), + 'agent' => $cust_main->agent->agent, + 'payname' => $cust_main->payname, + 'company' => $cust_main->company, + 'address1' => $cust_main->address1, + 'address2' => $cust_main->address2, + 'city' => $cust_main->city, + 'state' => $cust_main->state, + 'zip' => $cust_main->zip, + 'country' => $cust_main->country, + 'footer' => <<'END', #should come from config value +Ivan Kohler\\ +1339 Hayes St.\\ +San Francisco, CA~~94117\\ +ivan@sisd.com~~~~+1 415 462 1624\\ +Freeside - open-source billing - http://www.sisd.com/freeside\\ +END + + 'quantity' => 1, + + ); + + #$invoice_data{'footer'} =~ s/\n+$//; + + my $countrydefault = $conf->config('countrydefault') || 'US'; + $invoice_data{'country'} = '' if $invoice_data{'country'} eq $countrydefault; + + $invoice_data{'po_line'} = + ( $cust_main->payby eq 'BILL' && $cust_main->payinfo ) + ? "Purchase Order #". $cust_main->payinfo + : '~'; + + my @line_item = (); + my @total_item = (); + my @filled_in = (); + while ( @invoice_template ) { + my $line = shift @invoice_template; + + if ( $line =~ /^%%Detail\s*$/ ) { + + while ( ( my $line_item_line = shift @invoice_template ) + !~ /^%%EndDetail\s*$/ ) { + push @line_item, $line_item_line; + } + #foreach my $line_item ( $self->_items ) { + foreach my $line_item ( $self->_items_pkg ) { + $invoice_data{'ref'} = $line_item->{'pkgnum'}; + $invoice_data{'description'} = $line_item->{'description'}; + if ( exists $line_item->{'ext_description'} ) { + $invoice_data{'description'} .= + "\\tabularnewline\n~~". + join("\\tabularnewline\n~~", @{$line_item->{'ext_description'}} ); + } + $invoice_data{'amount'} = $line_item->{'amount'}; + $invoice_data{'product_code'} = $line_item->{'pkgpart'} || 'N/A'; + push @filled_in, + map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } @line_item; + } + + } elsif ( $line =~ /^%%TotalDetails\s*$/ ) { + + while ( ( my $total_item_line = shift @invoice_template ) + !~ /^%%EndTotalDetails\s*$/ ) { + push @total_item, $total_item_line; + } + + my @total_fill = (); + + my $taxtotal = 0; + foreach my $tax ( $self->_items_tax ) { + $invoice_data{'total_item'} = $tax->{'description'}; + $taxtotal += ( $invoice_data{'total_amount'} = $tax->{'amount'} ); + push @total_fill, + map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } + @total_item; + } + + $invoice_data{'total_item'} = '\textbf{Total}'; + $invoice_data{'total_amount'} = + '\textbf{\dollar '. sprintf('%.2f', $self->owed ). '}'; + push @total_fill, + map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } + @total_item; + + if ( $taxtotal ) { + $invoice_data{'total_item'} = 'Sub-total'; + $invoice_data{'total_amount'} = + '\dollar '. sprintf('%.2f', $self->owed - $taxtotal ); + unshift @total_fill, + map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } + @total_item; + } + + push @filled_in, @total_fill; + + } else { + #$line =~ s/\$(\w+)/$invoice_data{$1}/eg; + $line =~ s/\$(\w+)/exists($invoice_data{$1}) ? $invoice_data{$1} : nounder($1)/eg; + push @filled_in, $line; + } + + } + + sub nounder { + my $var = $1; + $var =~ s/_/\-/g; + $var; + } + + my $dir = '/tmp'; #! /usr/local/etc/freeside/invoices.datasrc/ + my $unique = int(rand(2**31)); #UGH... use File::Temp or something + + chdir($dir); + my $file = $self->invnum. ".$unique"; + + open(TEX,">$file.tex") or die "can't open $file.tex: $!\n"; + print TEX join("\n", @filled_in ), "\n"; + close TEX; + + #error checking!! + system('pslatex', "$file.tex"); + system('pslatex', "$file.tex"); + #system('dvips', '-t', 'letter', "$file.dvi", "$file.ps"); + system('dvips', '-t', 'letter', "$file.dvi" ); + + open(POSTSCRIPT, "<$file.ps") or die "can't open $file.ps: $!\n"; + + #rm $file.dvi $file.log $file.aux + #unlink("$file.dvi", "$file.log", "$file.aux", "$file.ps"); + unlink("$file.dvi", "$file.log", "$file.aux"); + + my $ps = ''; + while () { + $ps .= $_; + } + + close POSTSCRIPT; + + return $ps; + +} + +#utility methods for print_* + +sub _items { + my $self = shift; + my @display = scalar(@_) + ? @_ + : qw( _items_pkg ); + #: qw( _items_previous _items_pkg _items_tax _items_credits _items_payments ); + my @b = (); + foreach my $display ( @display ) { + push @b, $self->$display(@_); + } + @b; +} + +sub _items_previous { + my $self = shift; + my $cust_main = $self->cust_main; + my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance + my @b = (); + foreach ( @pr_cust_bill ) { + push @b, [ + "Previous Balance, Invoice #". $_->invnum. + " (". time2str("%x",$_->_date). ")", + $money_char. sprintf("%10.2f",$_->owed) + ]; + } + @b; +} + +sub _items_pkg { + my $self = shift; + my @cust_bill_pkg = grep { $_->pkgnum } $self->cust_bill_pkg; + $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_); +} + +sub _items_tax { + my $self = shift; + my @cust_bill_pkg = grep { ! $_->pkgnum } $self->cust_bill_pkg; + $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_); +} + +sub _items_cust_bill_pkg { + my $self = shift; + my $cust_bill_pkg = shift; + + my @b = (); + foreach my $cust_bill_pkg ( @$cust_bill_pkg ) { + + if ( $cust_bill_pkg->pkgnum ) { + + my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } ); + my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } ); + my $pkg = $part_pkg->pkg; + + if ( $cust_bill_pkg->setup != 0 ) { + my @d = (); + @d = $cust_bill_pkg->details if $cust_bill_pkg->recur == 0; + push @b, { + 'description' => "$pkg Setup", + 'pkgpart' => $part_pkg->pkgpart, + 'pkgnum' => $cust_pkg->pkgnum, + 'amount' => sprintf("%10.2f", $cust_bill_pkg->setup), + 'ext_description' => [ ( map { $_->[0]. ": ". $_->[1] } + $cust_pkg->labels ), + @d, + ], + }; + } + + if ( $cust_bill_pkg->recur != 0 ) { + push @b, { + 'description' => "$pkg (" . + time2str('%x', $cust_bill_pkg->sdate). ' - '. + time2str('%x', $cust_bill_pkg->edate). ')', + 'pkgpart' => $part_pkg->pkgpart, + 'pkgnum' => $cust_pkg->pkgnum, + 'amount' => sprintf("%10.2f", $cust_bill_pkg->recur), + 'ext_description' => [ ( map { $_->[0]. ": ". $_->[1] } + $cust_pkg->labels ), + $cust_bill_pkg->details, + ], + }; + } + + } else { #pkgnum tax or one-shot line item (??) + + my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc') + ? ( $cust_bill_pkg->itemdesc || 'Tax' ) + : 'Tax'; + if ( $cust_bill_pkg->setup != 0 ) { + push @b, { + 'description' => $itemdesc, + 'amount' => sprintf("%10.2f", $cust_bill_pkg->setup), + }; + } + if ( $cust_bill_pkg->recur != 0 ) { + push @b, { + 'description' => "$itemdesc (". + time2str("%x", $cust_bill_pkg->sdate). ' - '. + time2str("%x", $cust_bill_pkg->edate). ')', + 'amount' => sprintf("%10.2f", $cust_bill_pkg->recur), + }; + } + + } + + } + + @b; + +} + +sub _items_credits { + my $self = shift; + + my @b; + #credits + foreach ( $self->cust_credited ) { + + #something more elaborate if $_->amount ne $_->cust_credit->credited ? + + my $reason = substr($_->cust_credit->reason,0,32); + $reason .= '...' if length($reason) < length($_->cust_credit->reason); + $reason = " ($reason) " if $reason; + push @b,[ + "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")". + $reason, + $money_char. sprintf("%10.2f",$_->amount) + ]; + } + #foreach ( @cr_cust_credit ) { + # push @buf,[ + # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")", + # $money_char. sprintf("%10.2f",$_->credited) + # ]; + #} + + @b; + +} + +sub _items_payments { + my $self = shift; + + my @b; + #get & print payments + foreach ( $self->cust_bill_pay ) { + + #something more elaborate if $_->amount ne ->cust_pay->paid ? + + push @b,[ + "Payment received ". time2str("%x",$_->cust_pay->_date ), + $money_char. sprintf("%10.2f",$_->amount ) + ]; + } + + @b; + +} + =back =head1 BUGS diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 8511e1a45..df7bf1abb 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -41,7 +41,7 @@ use FS::Msgcat qw(gettext); $realtime_bop_decline_quiet = 0; -$Debug = 1; +$Debug = 0; #$Debug = 1; $import = 0; -- cgit v1.2.1 From d0bc83c16f5c9b38dda97671de8a49894fb08b4d Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 19 Nov 2003 01:37:14 +0000 Subject: reversing accidental commit of work-in-progress --- FS/FS/cust_bill.pm | 367 ++++------------------------------------------------- 1 file changed, 23 insertions(+), 344 deletions(-) diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index b49dac31c..c15dad433 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -647,7 +647,7 @@ sub batch_card { ''; } -=item print_text [ TIME [ , TEMPLATE ] ] +=item print_text [TIME]; Returns an text invoice, as a list of lines. @@ -667,7 +667,6 @@ sub print_text { $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') ) unless $cust_main->payname && $cust_main->payby ne 'CHEK'; - my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance # my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits #my $balance_due = $self->owed + $pr_total - $cr_total; @@ -750,6 +749,28 @@ sub print_text { $money_char. sprintf("%10.2f",$self->charged + $pr_total) ]; push @buf,['','']; + #credits + foreach ( $self->cust_credited ) { + + #something more elaborate if $_->amount ne $_->cust_credit->credited ? + + my $reason = substr($_->cust_credit->reason,0,32); + $reason .= '...' if length($reason) < length($_->cust_credit->reason); + $reason = " ($reason) " if $reason; + push @buf,[ + "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")". + $reason, + $money_char. sprintf("%10.2f",$_->amount) + ]; + } + #foreach ( @cr_cust_credit ) { + # push @buf,[ + # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")", + # $money_char. sprintf("%10.2f",$_->credited) + # ]; + #} + + #get & print payments foreach ( $self->cust_bill_pay ) { #something more elaborate if $_->amount ne ->cust_pay->paid ? @@ -856,348 +877,6 @@ sub print_text { } -=item print_ps [ TIME [ , TEMPLATE ] ] - -Returns an postscript invoice, as a scalar. - -TIME an optional value used to control the printing of overdue messages. The -default is now. It isn't the date of the invoice; that's the `_date' field. -It is specified as a UNIX timestamp; see L. Also see -L and L for conversion functions. - -=cut - -#still some false laziness w/print_text -sub print_ps { - - my( $self, $today, $template ) = @_; - $today ||= time; - -# my $invnum = $self->invnum; - my $cust_main = $self->cust_main; - $cust_main->payname( $cust_main->first. ' '. $cust_main->getfield('last') ) - unless $cust_main->payname && $cust_main->payby ne 'CHEK'; - - my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance -# my( $cr_total, @cr_cust_credit ) = $self->cust_credit; #credits - #my $balance_due = $self->owed + $pr_total - $cr_total; - my $balance_due = $self->owed + $pr_total; - - #my @collect = (); - #my($description,$amount); - @buf = (); - - #create the template - my $templatefile = 'invoice_template_latex'; - $templatefile .= "_$template" if $template; - my @invoice_template = $conf->config($templatefile) - or die "cannot load config file $templatefile"; - - my %invoice_data = ( - 'invnum' => $self->invnum, - 'date' => time2str('%b %o, %Y', $self->_date), - 'agent' => $cust_main->agent->agent, - 'payname' => $cust_main->payname, - 'company' => $cust_main->company, - 'address1' => $cust_main->address1, - 'address2' => $cust_main->address2, - 'city' => $cust_main->city, - 'state' => $cust_main->state, - 'zip' => $cust_main->zip, - 'country' => $cust_main->country, - 'footer' => <<'END', #should come from config value -Ivan Kohler\\ -1339 Hayes St.\\ -San Francisco, CA~~94117\\ -ivan@sisd.com~~~~+1 415 462 1624\\ -Freeside - open-source billing - http://www.sisd.com/freeside\\ -END - - 'quantity' => 1, - - ); - - #$invoice_data{'footer'} =~ s/\n+$//; - - my $countrydefault = $conf->config('countrydefault') || 'US'; - $invoice_data{'country'} = '' if $invoice_data{'country'} eq $countrydefault; - - $invoice_data{'po_line'} = - ( $cust_main->payby eq 'BILL' && $cust_main->payinfo ) - ? "Purchase Order #". $cust_main->payinfo - : '~'; - - my @line_item = (); - my @total_item = (); - my @filled_in = (); - while ( @invoice_template ) { - my $line = shift @invoice_template; - - if ( $line =~ /^%%Detail\s*$/ ) { - - while ( ( my $line_item_line = shift @invoice_template ) - !~ /^%%EndDetail\s*$/ ) { - push @line_item, $line_item_line; - } - #foreach my $line_item ( $self->_items ) { - foreach my $line_item ( $self->_items_pkg ) { - $invoice_data{'ref'} = $line_item->{'pkgnum'}; - $invoice_data{'description'} = $line_item->{'description'}; - if ( exists $line_item->{'ext_description'} ) { - $invoice_data{'description'} .= - "\\tabularnewline\n~~". - join("\\tabularnewline\n~~", @{$line_item->{'ext_description'}} ); - } - $invoice_data{'amount'} = $line_item->{'amount'}; - $invoice_data{'product_code'} = $line_item->{'pkgpart'} || 'N/A'; - push @filled_in, - map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } @line_item; - } - - } elsif ( $line =~ /^%%TotalDetails\s*$/ ) { - - while ( ( my $total_item_line = shift @invoice_template ) - !~ /^%%EndTotalDetails\s*$/ ) { - push @total_item, $total_item_line; - } - - my @total_fill = (); - - my $taxtotal = 0; - foreach my $tax ( $self->_items_tax ) { - $invoice_data{'total_item'} = $tax->{'description'}; - $taxtotal += ( $invoice_data{'total_amount'} = $tax->{'amount'} ); - push @total_fill, - map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } - @total_item; - } - - $invoice_data{'total_item'} = '\textbf{Total}'; - $invoice_data{'total_amount'} = - '\textbf{\dollar '. sprintf('%.2f', $self->owed ). '}'; - push @total_fill, - map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } - @total_item; - - if ( $taxtotal ) { - $invoice_data{'total_item'} = 'Sub-total'; - $invoice_data{'total_amount'} = - '\dollar '. sprintf('%.2f', $self->owed - $taxtotal ); - unshift @total_fill, - map { my $b=$_; $b =~ s/\$(\w+)/$invoice_data{$1}/eg; $b } - @total_item; - } - - push @filled_in, @total_fill; - - } else { - #$line =~ s/\$(\w+)/$invoice_data{$1}/eg; - $line =~ s/\$(\w+)/exists($invoice_data{$1}) ? $invoice_data{$1} : nounder($1)/eg; - push @filled_in, $line; - } - - } - - sub nounder { - my $var = $1; - $var =~ s/_/\-/g; - $var; - } - - my $dir = '/tmp'; #! /usr/local/etc/freeside/invoices.datasrc/ - my $unique = int(rand(2**31)); #UGH... use File::Temp or something - - chdir($dir); - my $file = $self->invnum. ".$unique"; - - open(TEX,">$file.tex") or die "can't open $file.tex: $!\n"; - print TEX join("\n", @filled_in ), "\n"; - close TEX; - - #error checking!! - system('pslatex', "$file.tex"); - system('pslatex', "$file.tex"); - #system('dvips', '-t', 'letter', "$file.dvi", "$file.ps"); - system('dvips', '-t', 'letter', "$file.dvi" ); - - open(POSTSCRIPT, "<$file.ps") or die "can't open $file.ps: $!\n"; - - #rm $file.dvi $file.log $file.aux - #unlink("$file.dvi", "$file.log", "$file.aux", "$file.ps"); - unlink("$file.dvi", "$file.log", "$file.aux"); - - my $ps = ''; - while () { - $ps .= $_; - } - - close POSTSCRIPT; - - return $ps; - -} - -#utility methods for print_* - -sub _items { - my $self = shift; - my @display = scalar(@_) - ? @_ - : qw( _items_pkg ); - #: qw( _items_previous _items_pkg _items_tax _items_credits _items_payments ); - my @b = (); - foreach my $display ( @display ) { - push @b, $self->$display(@_); - } - @b; -} - -sub _items_previous { - my $self = shift; - my $cust_main = $self->cust_main; - my( $pr_total, @pr_cust_bill ) = $self->previous; #previous balance - my @b = (); - foreach ( @pr_cust_bill ) { - push @b, [ - "Previous Balance, Invoice #". $_->invnum. - " (". time2str("%x",$_->_date). ")", - $money_char. sprintf("%10.2f",$_->owed) - ]; - } - @b; -} - -sub _items_pkg { - my $self = shift; - my @cust_bill_pkg = grep { $_->pkgnum } $self->cust_bill_pkg; - $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_); -} - -sub _items_tax { - my $self = shift; - my @cust_bill_pkg = grep { ! $_->pkgnum } $self->cust_bill_pkg; - $self->_items_cust_bill_pkg(\@cust_bill_pkg, @_); -} - -sub _items_cust_bill_pkg { - my $self = shift; - my $cust_bill_pkg = shift; - - my @b = (); - foreach my $cust_bill_pkg ( @$cust_bill_pkg ) { - - if ( $cust_bill_pkg->pkgnum ) { - - my $cust_pkg = qsearchs('cust_pkg', { pkgnum =>$cust_bill_pkg->pkgnum } ); - my $part_pkg = qsearchs('part_pkg', { pkgpart=>$cust_pkg->pkgpart } ); - my $pkg = $part_pkg->pkg; - - if ( $cust_bill_pkg->setup != 0 ) { - my @d = (); - @d = $cust_bill_pkg->details if $cust_bill_pkg->recur == 0; - push @b, { - 'description' => "$pkg Setup", - 'pkgpart' => $part_pkg->pkgpart, - 'pkgnum' => $cust_pkg->pkgnum, - 'amount' => sprintf("%10.2f", $cust_bill_pkg->setup), - 'ext_description' => [ ( map { $_->[0]. ": ". $_->[1] } - $cust_pkg->labels ), - @d, - ], - }; - } - - if ( $cust_bill_pkg->recur != 0 ) { - push @b, { - 'description' => "$pkg (" . - time2str('%x', $cust_bill_pkg->sdate). ' - '. - time2str('%x', $cust_bill_pkg->edate). ')', - 'pkgpart' => $part_pkg->pkgpart, - 'pkgnum' => $cust_pkg->pkgnum, - 'amount' => sprintf("%10.2f", $cust_bill_pkg->recur), - 'ext_description' => [ ( map { $_->[0]. ": ". $_->[1] } - $cust_pkg->labels ), - $cust_bill_pkg->details, - ], - }; - } - - } else { #pkgnum tax or one-shot line item (??) - - my $itemdesc = defined $cust_bill_pkg->dbdef_table->column('itemdesc') - ? ( $cust_bill_pkg->itemdesc || 'Tax' ) - : 'Tax'; - if ( $cust_bill_pkg->setup != 0 ) { - push @b, { - 'description' => $itemdesc, - 'amount' => sprintf("%10.2f", $cust_bill_pkg->setup), - }; - } - if ( $cust_bill_pkg->recur != 0 ) { - push @b, { - 'description' => "$itemdesc (". - time2str("%x", $cust_bill_pkg->sdate). ' - '. - time2str("%x", $cust_bill_pkg->edate). ')', - 'amount' => sprintf("%10.2f", $cust_bill_pkg->recur), - }; - } - - } - - } - - @b; - -} - -sub _items_credits { - my $self = shift; - - my @b; - #credits - foreach ( $self->cust_credited ) { - - #something more elaborate if $_->amount ne $_->cust_credit->credited ? - - my $reason = substr($_->cust_credit->reason,0,32); - $reason .= '...' if length($reason) < length($_->cust_credit->reason); - $reason = " ($reason) " if $reason; - push @b,[ - "Credit #". $_->crednum. " (". time2str("%x",$_->cust_credit->_date) .")". - $reason, - $money_char. sprintf("%10.2f",$_->amount) - ]; - } - #foreach ( @cr_cust_credit ) { - # push @buf,[ - # "Credit #". $_->crednum. " (" . time2str("%x",$_->_date) .")", - # $money_char. sprintf("%10.2f",$_->credited) - # ]; - #} - - @b; - -} - -sub _items_payments { - my $self = shift; - - my @b; - #get & print payments - foreach ( $self->cust_bill_pay ) { - - #something more elaborate if $_->amount ne ->cust_pay->paid ? - - push @b,[ - "Payment received ". time2str("%x",$_->cust_pay->_date ), - $money_char. sprintf("%10.2f",$_->amount ) - ]; - } - - @b; - -} - =back =head1 BUGS -- cgit v1.2.1 From 4eec09b9bb69dea116c0f8b5fa81862125aa587c Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 19 Nov 2003 12:21:09 +0000 Subject: fix jscalendar date ifFormat --- httemplate/edit/REAL_cust_pkg.cgi | 4 ++-- httemplate/search/cust_pkg.html | 4 ++-- httemplate/search/report_cc.html | 4 ++-- httemplate/search/report_credit.html | 4 ++-- httemplate/search/report_cust_pay.html | 4 ++-- httemplate/search/report_tax.html | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/httemplate/edit/REAL_cust_pkg.cgi b/httemplate/edit/REAL_cust_pkg.cgi index 4156b850b..d9b7579f6 100755 --- a/httemplate/edit/REAL_cust_pkg.cgi +++ b/httemplate/edit/REAL_cust_pkg.cgi @@ -1,6 +1,6 @@ <% -# +# my $error =''; my $pkgnum = ''; @@ -119,7 +119,7 @@ print '
      $labelfrom$labelto
      ". qq!
      + <%= $part_referral->refnum %> + <%= $part_referral->referral %> <%= $num_accounts %> - active + <% if ( $num_accounts ) { %><% } %> + active + <% if ( $num_accounts ) { %><% } %>
      - <%= $row->{'company'} ? $row->{'company'}. ' (' : '' %> - <%= $row->{'last'}. ', '. $row->{'first'} %> - <%= $row->{'company'} ? ')' : '' %> + <%= $row->{'company'} ? $row->{'company'}. ' (' : '' %><%= $row->{'last'}. ', '. $row->{'first'} %><%= $row->{'company'} ? ')' : '' %> $<%= sprintf("%.2f", $row->{'owed_0_30'} ) %> $<%= sprintf("%.2f", $row->{'owed_30_60'} ) %>
      + <%= $part_referral->refnum %> + <%= $part_referral->referral %>
      Cancellation date', %> Calendar.setup({ inputField: "<%= $cal %>_text", - ifFormat: "mm/dd/y", + ifFormat: "%m/%d/%Y", button: "<%= $cal %>_button", align: "BR" }); diff --git a/httemplate/search/cust_pkg.html b/httemplate/search/cust_pkg.html index 3d80d51a9..0dad83a2a 100755 --- a/httemplate/search/cust_pkg.html +++ b/httemplate/search/cust_pkg.html @@ -18,7 +18,7 @@