From cbbd0225b07269209c674733bcf70f1c1308e84a Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 25 Jan 2006 12:34:29 +0000 Subject: change texas-style tax exemptions to be against a specific line item rather than just general per-customer, for later tracking and tax reporting. fix 1969/1970 exemptions for one-off charges --- FS/FS/Record.pm | 5 + FS/FS/Schema.pm | 18 ++++ FS/FS/cust_bill.pm | 22 +++- FS/FS/cust_main.pm | 234 ++++++++++++++++++++++++------------------- FS/FS/cust_tax_exempt.pm | 21 +++- FS/FS/cust_tax_exempt_pkg.pm | 135 +++++++++++++++++++++++++ FS/FS/h_cust_bill.pm | 33 ++++++ FS/FS/h_cust_tax_exempt.pm | 40 ++++++++ FS/MANIFEST | 6 ++ FS/t/cust_tax_exempt_pkg.t | 5 + FS/t/h_cust_bill.t | 5 + FS/t/h_cust_tax_exempt.t | 5 + README.2.0.0 | 17 ++++ 13 files changed, 435 insertions(+), 111 deletions(-) create mode 100644 FS/FS/cust_tax_exempt_pkg.pm create mode 100644 FS/FS/h_cust_bill.pm create mode 100644 FS/FS/h_cust_tax_exempt.pm create mode 100644 FS/t/cust_tax_exempt_pkg.t create mode 100644 FS/t/h_cust_bill.t create mode 100644 FS/t/h_cust_tax_exempt.t create mode 100644 README.2.0.0 diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 887c8dcd4..19da3d181 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -977,6 +977,11 @@ sub replace { warn "[debug]$me $new ->replace $old\n" if $DEBUG; + if ( $new->can('replace_check') ) { + my $error = $new->replace_check($old); + return $error if $error; + } + return "Records not in same table!" unless $new->table eq $old->table; my $primary_key = $old->dbdef_table->primary_key; diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 451ef2d2e..d502a12be 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -976,6 +976,24 @@ sub tables_hashref { 'index' => [], }, + 'cust_tax_exempt_pkg' => { + 'columns' => [ + 'exemptpkgnum', 'serial', '', '', + #'custnum', 'int', '', '', + 'billpkgnum', 'int', '', '', + 'taxnum', 'int', '', '', + 'year', 'int', '', '', + 'month', 'int', '', '', + 'amount', @money_type, + ], + 'primary_key' => 'exemptpkgnum', + 'unique' => [], + 'index' => [ [ 'taxnum', 'year', 'month' ], + [ 'billpkgnum' ], + [ 'taxnum' ] + ], + }, + 'router' => { 'columns' => [ 'routernum', 'serial', '', '', diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 6e3b2b2f8..159c9e405 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -121,8 +121,14 @@ returns the error, otherwise returns false. =item delete -Currently unimplemented. I don't remove invoices because there would then be -no record you ever posted this invoice (which is bad, no?) +This method now works but you probably shouldn't use it. Instead, apply a +credit against the invoice. + +Using this method to delete invoices outright is really, really bad. There +would be no record you ever posted this invoice, and there are no check to +make sure charged = 0 or that there are no associated cust_bill_pkg records. + +Really, don't use it. =cut @@ -142,14 +148,20 @@ collect method of a customer object (see L). =cut -sub replace { +#replace can be inherited from Record.pm + +# replace_check is now the preferred way to #implement replace data checks +# (so $object->replace() works without an argument) + +sub replace_check { my( $new, $old ) = ( shift, shift ); return "Can't change custnum!" unless $old->custnum == $new->custnum; #return "Can't change _date!" unless $old->_date eq $new->_date; return "Can't change _date!" unless $old->_date == $new->_date; - return "Can't change charged!" unless $old->charged == $new->charged; + return "Can't change charged!" unless $old->charged == $new->charged + || $old->charged == 0; - $new->SUPER::replace($old); + ''; } =item check diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index a265e4177..b5ccf5a3f 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -43,6 +43,7 @@ use FS::part_pkg; use FS::part_bill_event; use FS::cust_bill_event; use FS::cust_tax_exempt; +use FS::cust_tax_exempt_pkg; use FS::type_pkgs; use FS::payment_gateway; use FS::agent_payment_gateway; @@ -1617,16 +1618,28 @@ sub bill { $self->select_for_update; #mutex + #create a new invoice + #(we'll remove it later if it doesn't actually need to be generated [contains + # no line items] and we're inside a transaciton so nothing else will see it) + my $cust_bill = new FS::cust_bill ( { + 'custnum' => $self->custnum, + '_date' => $time, + #'charged' => $charged, + 'charged' => 0, + } ); + $error = $cust_bill->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't create invoice for customer #". $self->custnum. ": $error"; + } + my $invnum = $cust_bill->invnum; + + ### # find the packages which are due for billing, find out how much they are # & generate invoice database. - - my( $total_setup, $total_recur ) = ( 0, 0 ); - #my( $taxable_setup, $taxable_recur ) = ( 0, 0 ); - my @cust_bill_pkg = (); - #my $tax = 0;## - #my $taxable_charged = 0;## - #my $charged = 0;## + ### + my( $total_setup, $total_recur ) = ( 0, 0 ); my %tax; foreach my $cust_pkg ( @@ -1649,7 +1662,10 @@ sub bill { my @details = (); + ### # bill setup + ### + my $setup = 0; if ( !$cust_pkg->setup || $options{'resetup'} ) { @@ -1664,7 +1680,10 @@ sub bill { $cust_pkg->setfield('setup', $time) unless $cust_pkg->setup; } - #bill recurring fee + ### + # bill recurring fee + ### + my $recur = 0; my $sdate; if ( $part_pkg->getfield('freq') ne '0' && @@ -1719,6 +1738,10 @@ sub bill { warn "\$recur is undefined" unless defined($recur); warn "\$cust_pkg->bill is undefined" unless defined($cust_pkg->bill); + ### + # If $cust_pkg has been modified, update it and create cust_bill_pkg records + ### + if ( $cust_pkg->modified ) { warn " package ". $cust_pkg->pkgnum. " modified; updating\n" @@ -1740,10 +1763,13 @@ sub bill { $dbh->rollback if $oldAutoCommit; return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum; } + if ( $setup != 0 || $recur != 0 ) { - warn " charges (setup=$setup, recur=$recur); queueing line items\n" + + warn " charges (setup=$setup, recur=$recur); adding line items\n" if $DEBUG > 1; my $cust_bill_pkg = new FS::cust_bill_pkg ({ + 'invnum' => $invnum, 'pkgnum' => $cust_pkg->pkgnum, 'setup' => $setup, 'recur' => $recur, @@ -1751,10 +1777,18 @@ sub bill { 'edate' => $cust_pkg->bill, 'details' => \@details, }); - push @cust_bill_pkg, $cust_bill_pkg; + $error = $cust_bill_pkg->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't create invoice line item for invoice #$invnum: $error"; + } $total_setup += $setup; $total_recur += $recur; + ### + # handle taxes + ### + unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) { my @taxes = qsearch( 'cust_main_county', { @@ -1803,7 +1837,8 @@ sub bill { next unless $taxable_charged; if ( $tax->exempt_amount && $tax->exempt_amount > 0 ) { - my ($mon,$year) = (localtime($sdate) )[4,5]; + #my ($mon,$year) = (localtime($sdate) )[4,5]; + my ($mon,$year) = (localtime( $sdate || $cust_bill->_date ) )[4,5]; $mon++; my $freq = $part_pkg->freq || 1; if ( $freq !~ /(\d+)$/ ) { @@ -1811,40 +1846,74 @@ sub bill { return "daily/weekly package definitions not (yet?)". " compatible with monthly tax exemptions"; } - my $taxable_per_month = sprintf("%.2f", $taxable_charged / $freq ); + my $taxable_per_month = + sprintf("%.2f", $taxable_charged / $freq ); + + #call the whole thing off if this customer has any old + #exemption records... + my @cust_tax_exempt = + qsearch( 'cust_tax_exempt' => { custnum=> $self->custnum } ); + if ( @cust_tax_exempt ) { + $dbh->rollback if $oldAutoCommit; + return + 'this customer still has old-style tax exemption records; '. + 'run bin/fs-migrate-cust_tax_exempt?'; + } + foreach my $which_month ( 1 .. $freq ) { - my %hash = ( - 'custnum' => $self->custnum, - 'taxnum' => $tax->taxnum, - 'year' => 1900+$year, - 'month' => $mon++, - ); - #until ( $mon < 12 ) { $mon -= 12; $year++; } - until ( $mon < 13 ) { $mon -= 12; $year++; } - my $cust_tax_exempt = - qsearchs('cust_tax_exempt', \%hash) - || new FS::cust_tax_exempt( { %hash, 'amount' => 0 } ); - my $remaining_exemption = sprintf("%.2f", - $tax->exempt_amount - $cust_tax_exempt->amount ); + + #maintain the new exemption table now + my $sql = " + SELECT SUM(amount) + FROM cust_tax_exempt_pkg + LEFT JOIN cust_bill_pkg USING ( billpkgnum ) + LEFT JOIN cust_bill USING ( invnum ) + WHERE custnum = ? + AND taxnum = ? + AND year = ? + AND month = ? + "; + my $sth = dbh->prepare($sql) or do { + $dbh->rollback if $oldAutoCommit; + return "fatal: can't lookup exising exemption: ". dbh->errstr; + }; + $sth->execute( + $self->custnum, + $tax->taxnum, + 1900+$year, + $mon, + ) or do { + $dbh->rollback if $oldAutoCommit; + return "fatal: can't lookup exising exemption: ". dbh->errstr; + }; + my $existing_exemption = $sth->fetchrow_arrayref->[0]; + + my $remaining_exemption = + $tax->exempt_amount - $existing_exemption; if ( $remaining_exemption > 0 ) { my $addl = $remaining_exemption > $taxable_per_month ? $taxable_per_month : $remaining_exemption; $taxable_charged -= $addl; - my $new_cust_tax_exempt = new FS::cust_tax_exempt ( { - $cust_tax_exempt->hash, - 'amount' => - sprintf("%.2f", $cust_tax_exempt->amount + $addl), + + my $cust_tax_exempt_pkg = new FS::cust_tax_exempt_pkg ( { + 'billpkgnum' => $cust_bill_pkg->billpkgnum, + 'taxnum' => $tax->taxnum, + 'year' => 1900+$year, + 'month' => $mon, + 'amount' => sprintf("%.2f", $addl ), } ); - $error = $new_cust_tax_exempt->exemptnum - ? $new_cust_tax_exempt->replace($cust_tax_exempt) - : $new_cust_tax_exempt->insert; + $error = $cust_tax_exempt_pkg->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "fatal: can't update cust_tax_exempt: $error"; + return "fatal: can't insert cust_tax_exempt_pkg: $error"; } - } # if $remaining_exemption > 0 + + #++ + $mon++; + #until ( $mon < 12 ) { $mon -= 12; $year++; } + until ( $mon < 13 ) { $mon -= 12; $year++; } } #foreach $which_month @@ -1866,86 +1935,41 @@ sub bill { } #foreach my $cust_pkg - my $charged = sprintf( "%.2f", $total_setup + $total_recur ); -# my $taxable_charged = sprintf( "%.2f", $taxable_setup + $taxable_recur ); - - unless ( @cust_bill_pkg ) { #don't create invoices with no line items + unless ( $cust_bill->cust_bill_pkg ) { + $cust_bill->delete; #don't create an invoice w/o line items $dbh->commit or die $dbh->errstr if $oldAutoCommit; return ''; - } - -# unless ( $self->tax =~ /Y/i -# || $self->payby eq 'COMP' -# || $taxable_charged == 0 ) { -# my $cust_main_county = qsearchs('cust_main_county',{ -# 'state' => $self->state, -# 'county' => $self->county, -# 'country' => $self->country, -# } ) or die "fatal: can't find tax rate for state/county/country ". -# $self->state. "/". $self->county. "/". $self->country. "\n"; -# my $tax = sprintf( "%.2f", -# $taxable_charged * ( $cust_main_county->getfield('tax') / 100 ) -# ); - - if ( dbdef->table('cust_bill_pkg')->column('itemdesc') ) { #1.5 schema - - foreach my $taxname ( grep { $tax{$_} > 0 } keys %tax ) { - my $tax = sprintf("%.2f", $tax{$taxname} ); - $charged = sprintf( "%.2f", $charged+$tax ); - - my $cust_bill_pkg = new FS::cust_bill_pkg ({ - 'pkgnum' => 0, - 'setup' => $tax, - 'recur' => 0, - 'sdate' => '', - 'edate' => '', - 'itemdesc' => $taxname, - }); - push @cust_bill_pkg, $cust_bill_pkg; - } - - } else { #1.4 schema - - my $tax = 0; - foreach ( values %tax ) { $tax += $_ }; - $tax = sprintf("%.2f", $tax); - if ( $tax > 0 ) { - $charged = sprintf( "%.2f", $charged+$tax ); - - my $cust_bill_pkg = new FS::cust_bill_pkg ({ - 'pkgnum' => 0, - 'setup' => $tax, - 'recur' => 0, - 'sdate' => '', - 'edate' => '', - }); - push @cust_bill_pkg, $cust_bill_pkg; - } - } - my $cust_bill = new FS::cust_bill ( { - 'custnum' => $self->custnum, - '_date' => $time, - 'charged' => $charged, - } ); - $error = $cust_bill->insert; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "can't create invoice for customer #". $self->custnum. ": $error"; - } + my $charged = sprintf( "%.2f", $total_setup + $total_recur ); - my $invnum = $cust_bill->invnum; - my $cust_bill_pkg; - foreach $cust_bill_pkg ( @cust_bill_pkg ) { - #warn $invnum; - $cust_bill_pkg->invnum($invnum); + foreach my $taxname ( grep { $tax{$_} > 0 } keys %tax ) { + my $tax = sprintf("%.2f", $tax{$taxname} ); + $charged = sprintf( "%.2f", $charged+$tax ); + + my $cust_bill_pkg = new FS::cust_bill_pkg ({ + 'invnum' => $invnum, + 'pkgnum' => 0, + 'setup' => $tax, + 'recur' => 0, + 'sdate' => '', + 'edate' => '', + 'itemdesc' => $taxname, + }); $error = $cust_bill_pkg->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "can't create invoice line item for customer #". $self->custnum. - ": $error"; + return "can't create invoice line item for invoice #$invnum: $error"; } + $total_setup += $tax; + + } + + $cust_bill->charged( sprintf( "%.2f", $total_setup + $total_recur ) ); + $error = $cust_bill->replace; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't update charged for invoice #$invnum: $error"; } $dbh->commit or die $dbh->errstr if $oldAutoCommit; diff --git a/FS/FS/cust_tax_exempt.pm b/FS/FS/cust_tax_exempt.pm index da0de000a..3e398877a 100644 --- a/FS/FS/cust_tax_exempt.pm +++ b/FS/FS/cust_tax_exempt.pm @@ -3,6 +3,8 @@ package FS::cust_tax_exempt; use strict; use vars qw( @ISA ); use FS::Record qw( qsearch qsearchs ); +use FS::cust_main; +use FS::cust_main_county; @ISA = qw(FS::Record); @@ -27,7 +29,7 @@ FS::cust_tax_exempt - Object methods for cust_tax_exempt records =head1 DESCRIPTION -An FS::cust_tax_exempt object represents a historical record of a customer tax +An FS::cust_tax_exempt object represents a record of an old-style customer tax exemption. Currently this is only used for "texas tax". FS::cust_tax_exempt inherits from FS::Record. The following fields are currently supported: @@ -47,6 +49,12 @@ inherits from FS::Record. The following fields are currently supported: =back +=head1 NOTE + +Old-style customer tax exemptions are only useful for legacy migrations - if +you are looking for current customer tax exemption data see +L. + =head1 METHODS =over 4 @@ -115,6 +123,17 @@ sub check { ; } +=item cust_main_county + +Returns the FS::cust_main_county object associated with this tax exemption. + +=cut + +sub cust_main_county { + my $self = shift; + qsearchs( 'cust_main_county', { 'taxnum' => $self->taxnum } ); +} + =back =head1 BUGS diff --git a/FS/FS/cust_tax_exempt_pkg.pm b/FS/FS/cust_tax_exempt_pkg.pm new file mode 100644 index 000000000..7193ace74 --- /dev/null +++ b/FS/FS/cust_tax_exempt_pkg.pm @@ -0,0 +1,135 @@ +package FS::cust_tax_exempt_pkg; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); +use FS::cust_bill_pkg; +use FS::cust_main_county; + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cust_tax_exempt_pkg - Object methods for cust_tax_exempt_pkg records + +=head1 SYNOPSIS + + use FS::cust_tax_exempt_pkg; + + $record = new FS::cust_tax_exempt_pkg \%hash; + $record = new FS::cust_tax_exempt_pkg { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cust_tax_exempt_pkg object represents a record of a customer tax +exemption. Currently this is only used for "texas tax". FS::cust_tax_exempt +inherits from FS::Record. The following fields are currently supported: + +=over 4 + +=item exemptpkgnum - primary key + +=item billpkgnum - invoice line item (see L) + +=item taxnum - tax rate (see L) + +=item year + +=item month + +=item amount + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new exemption record. To add the example to the database, see +L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cust_tax_exempt_pkg'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=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. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + $self->ut_numbern('exemptnum') +# || $self->ut_foreign_key('custnum', 'cust_main', 'custnum') + || $self->ut_foreign_key('billpkgnum', 'cust_bill_pkg', 'billpkgnum') + || $self->ut_foreign_key('taxnum', 'cust_main_county', 'taxnum') + || $self->ut_number('year') #check better + || $self->ut_number('month') #check better + || $self->ut_money('amount') + || $self->SUPER::check + ; +} + +=back + +=head1 BUGS + +Texas tax is still a royal pain in the ass. + +=head1 SEE ALSO + +L, L, L, schema.html from +the base documentation. + +=cut + +1; + diff --git a/FS/FS/h_cust_bill.pm b/FS/FS/h_cust_bill.pm new file mode 100644 index 000000000..7a3d81146 --- /dev/null +++ b/FS/FS/h_cust_bill.pm @@ -0,0 +1,33 @@ +package FS::h_cust_bill; + +use strict; +use vars qw( @ISA ); +use FS::h_Common; +use FS::cust_bill; + +@ISA = qw( FS::h_Common FS::cust_bill ); + +sub table { 'h_cust_bill' }; + +=head1 NAME + +FS::h_cust_bill - Historical record of customer tax changes (old-style) + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +An FS::h_cust_bill object represents historical changes to invoices. +FS::h_cust_bill inherits from FS::h_Common and FS::cust_bill. + +=head1 BUGS + +=head1 SEE ALSO + +L, L, L, schema.html from the base +documentation. + +=cut + +1; + diff --git a/FS/FS/h_cust_tax_exempt.pm b/FS/FS/h_cust_tax_exempt.pm new file mode 100644 index 000000000..9d2318bd5 --- /dev/null +++ b/FS/FS/h_cust_tax_exempt.pm @@ -0,0 +1,40 @@ +package FS::h_cust_tax_exempt; + +use strict; +use vars qw( @ISA ); +use FS::h_Common; +use FS::cust_tax_exempt; + +@ISA = qw( FS::h_Common FS::cust_tax_exempt ); + +sub table { 'h_cust_tax_exempt' }; + +=head1 NAME + +FS::h_cust_tax_exempt - Historical record of customer tax changes (old-style) + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +An FS::h_cust_tax_exempt object represents historical changes to old-style +customer tax exemptions. FS::h_cust_tax_exempt inherits from FS::h_Common and +FS::cust_tax_exempt. + +=head1 NOTE + +Old-style customer tax exemptions are only useful for legacy migrations - if +you are looking for current customer tax exemption data see +L. + +=head1 BUGS + +=head1 SEE ALSO + +L, L, L, +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index e7d9dea34..54ea52555 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -65,7 +65,9 @@ FS/cust_refund.pm FS/cust_credit_refund.pm FS/cust_svc.pm FS/h_Common.pm +FS/h_cust_bill.pm FS/h_cust_svc.pm +FS/h_cust_tax_exempt.pm FS/h_domain_record.pm FS/h_svc_acct.pm FS/h_svc_broadband.pm @@ -152,6 +154,7 @@ FS/queue_arg.pm FS/queue_depend.pm FS/msgcat.pm FS/cust_tax_exempt.pm +FS/cust_tax_exempt_pkg.pm FS/clientapi_session.pm FS/clientapi_session_field.pm t/agent.t @@ -189,7 +192,9 @@ t/cust_pay_refund.t t/cust_pkg.t t/cust_refund.t t/cust_svc.t +t/h_cust_bill.t t/h_cust_svc.t +t/h_cust_tax_exempt.t t/h_Common.t t/h_cust_svc.t t/h_domain_record.t @@ -200,6 +205,7 @@ t/h_svc_external.t t/h_svc_forward.t t/h_svc_www.t t/cust_tax_exempt.t +t/cust_tax_exempt_pkg.t t/domain_record.t t/nas.t t/part_bill_event.t diff --git a/FS/t/cust_tax_exempt_pkg.t b/FS/t/cust_tax_exempt_pkg.t new file mode 100644 index 000000000..099a0ce8a --- /dev/null +++ b/FS/t/cust_tax_exempt_pkg.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cust_tax_exempt_pkg; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/h_cust_bill.t b/FS/t/h_cust_bill.t new file mode 100644 index 000000000..ceccb2a3d --- /dev/null +++ b/FS/t/h_cust_bill.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::h_cust_bill; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/h_cust_tax_exempt.t b/FS/t/h_cust_tax_exempt.t new file mode 100644 index 000000000..432238aa5 --- /dev/null +++ b/FS/t/h_cust_tax_exempt.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::h_cust_tax_exempt; +$loaded=1; +print "ok 1\n"; diff --git a/README.2.0.0 b/README.2.0.0 new file mode 100644 index 000000000..3f5f1158f --- /dev/null +++ b/README.2.0.0 @@ -0,0 +1,17 @@ + +make install-perl-modules +run "freeside-upgrade username" to uprade your database schema + +(if freeside-upgrade hangs, try stopping Apache, all Freeside processes, and + anything else connected to your database, especially on older Pg versions) + +If you have any records in the cust_tax_exempt table, you *MUST* migrate them +to the new cust_tax_exempt_pkg table. An example script to get you started is +in bin/fs-migrate-cust_tax_exempt - it may need to be customized for your +specific data. + +------ + +make install-docs + (or "make deploy" if you've got everything setup in the Makefile) + -- cgit v1.2.1 From b782294eb91805f708a7776fe67f1c0863f4096b Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 26 Jan 2006 15:27:10 +0000 Subject: whew, FINALLY can fix monthly exemption columns to work correctly. also make them agent-specific. also fix package exemption columns, they were bunk too, sheesh. start adding package classes for package class tax reporting. --- FS/FS/Schema.pm | 11 ++ FS/FS/cust_tax_exempt_pkg.pm | 3 +- FS/FS/part_pkg.pm | 7 ++ FS/FS/pkg_class.pm | 111 ++++++++++++++++++ FS/MANIFEST | 2 + FS/t/pkg_class.t | 5 + httemplate/search/cust_bill_pkg.cgi | 32 ++++-- httemplate/search/cust_tax_exempt_pkg.cgi | 143 ++++++++++++++++++++++++ httemplate/search/report_tax.cgi | 179 +++++++++++++++++------------- httemplate/search/report_tax.html | 30 ++++- 10 files changed, 429 insertions(+), 94 deletions(-) create mode 100644 FS/FS/pkg_class.pm create mode 100644 FS/t/pkg_class.t create mode 100644 httemplate/search/cust_tax_exempt_pkg.cgi diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index d502a12be..a0637f50c 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -625,6 +625,7 @@ sub tables_hashref { 'plandata', 'text', 'NULL', '', 'disabled', 'char', 'NULL', 1, 'taxclass', 'varchar', 'NULL', $char_d, + 'classnum', 'int', 'NULL', '', ], 'primary_key' => 'pkgpart', 'unique' => [], @@ -1277,6 +1278,16 @@ sub tables_hashref { 'index' => [ [ 'disabled' ] ], }, + 'pkg_class' => { + 'columns' => [ + 'classnum', 'serial', '', '', + 'classname', 'varchar', '', $char_d, + ], + 'primary_key' => 'classnum', + 'unique' => [], + 'index' => [], + }, + }; } diff --git a/FS/FS/cust_tax_exempt_pkg.pm b/FS/FS/cust_tax_exempt_pkg.pm index 7193ace74..28fa24327 100644 --- a/FS/FS/cust_tax_exempt_pkg.pm +++ b/FS/FS/cust_tax_exempt_pkg.pm @@ -3,10 +3,11 @@ package FS::cust_tax_exempt_pkg; use strict; use vars qw( @ISA ); use FS::Record qw( qsearch qsearchs ); +use FS::cust_main_Mixin; use FS::cust_bill_pkg; use FS::cust_main_county; -@ISA = qw(FS::Record); +@ISA = qw( FS::cust_main_Mixin FS::Record ); =head1 NAME diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 73f3bae04..a5e3c2170 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -438,6 +438,13 @@ sub check { ; return $error if $error; + if ( $self->classnum !~ /^$/ ) { + my $error = $self->ut_foreign_key('classnum', 'pkg_class', 'classnum'); + return $error if $error; + } else { + $self->classnum(''); + } + return 'Unknown plan '. $self->plan unless exists($plans{$self->plan}); diff --git a/FS/FS/pkg_class.pm b/FS/FS/pkg_class.pm new file mode 100644 index 000000000..0fa6e4810 --- /dev/null +++ b/FS/FS/pkg_class.pm @@ -0,0 +1,111 @@ +package FS::pkg_class; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch ); +use FS::part_pkg; + +@ISA = qw( FS::Record ); + +=head1 NAME + +FS::pkg_class - Object methods for pkg_class records + +=head1 SYNOPSIS + + use FS::pkg_class; + + $record = new FS::pkg_class \%hash; + $record = new FS::pkg_class { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::pkg_class object represents an package class. Every package definition +(see L) has, optionally, a package class. FS::pkg_class inherits +from FS::Record. The following fields are currently supported: + +=over 4 + +=item classnum - primary key (assigned automatically for new package classes) + +=item classname - Text name of this package class + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new package class. To add the package class to the database, see +L<"insert">. + +=cut + +sub table { 'pkg_class'; } + +=item insert + +Adds this package class to the database. If there is an error, returns the +error, otherwise returns false. + +=item delete + +Deletes this package class from the database. Only package classes with no +associated package definitions can be deleted. If there is an error, returns +the error, otherwise returns false. + +=cut + +sub delete { + my $self = shift; + + return "Can't delete an pkg_class with part_pkg records!" + if qsearch( 'part_pkg', { 'classnum' => $self->classnum } ); + + $self->SUPER::delete; +} + +=item replace OLD_RECORD + +Replaces OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=item check + +Checks all fields to make sure this is a valid package class. If there is an +error, returns the error, otherwise returns false. Called by the insert and +replace methods. + +=cut + +sub check { + my $self = shift; + + $self->ut_numbern('classnum') + or $self->ut_text('classname') + or $self->SUPER::check; + +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L, L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index 54ea52555..c8f10f291 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -125,6 +125,7 @@ FS/part_svc.pm FS/part_svc_column.pm FS/part_svc_router.pm FS/part_virtual_field.pm +FS/pkg_class.pm FS/pkg_svc.pm FS/rate.pm FS/rate_detail.pm @@ -258,6 +259,7 @@ t/part_pop_local.t t/part_referral.t t/part_svc.t t/part_svc_column.t +t/pkg_class.t t/pkg_svc.t t/port.t t/prepay_credit.t diff --git a/FS/t/pkg_class.t b/FS/t/pkg_class.t new file mode 100644 index 000000000..fb3774f8c --- /dev/null +++ b/FS/t/pkg_class.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::pkg_class; +$loaded=1; +print "ok 1\n"; diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi index 082ccc893..cc0f97536 100644 --- a/httemplate/search/cust_bill_pkg.cgi +++ b/httemplate/search/cust_bill_pkg.cgi @@ -4,7 +4,7 @@ my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); my $join_cust = " JOIN cust_bill USING ( invnum ) - JOIN cust_main USING ( custnum ) + LEFT JOIN cust_main USING ( custnum ) "; my $join_pkg = " @@ -17,6 +17,10 @@ my $where = " AND payby != 'COMP' "; +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $where .= " AND agentnum = $1 "; +} + if ( $cgi->param('out') ) { $where .= " @@ -62,19 +66,27 @@ my $count_query; if ( $cgi->param('pkg_tax') ) { $count_query = - "SELECT COUNT(*), SUM( ( CASE WHEN part_pkg.setuptax = 'Y' - THEN cust_bill_pkg.setup - ELSE 0 ) - + - ( CASE WHEN part_pkg.recurtax = 'Y' - THEN cust_bill_pkg.recur - ELSE 0 ) - )"; + "SELECT COUNT(*), SUM( + ( CASE WHEN part_pkg.setuptax = 'Y' + THEN cust_bill_pkg.setup + ELSE 0 + END + ) + + + ( CASE WHEN part_pkg.recurtax = 'Y' + THEN cust_bill_pkg.recur + ELSE 0 + END + ) + ) + "; $where .= " AND ( ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 ) OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) - )"; + ) + AND ( tax != 'Y' OR tax IS NULL ) + "; } else { diff --git a/httemplate/search/cust_tax_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi new file mode 100644 index 000000000..bc1e743b2 --- /dev/null +++ b/httemplate/search/cust_tax_exempt_pkg.cgi @@ -0,0 +1,143 @@ +<% + +my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); + +my $join_cust = " + JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) +"; + +my $join_pkg = " + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) +"; + +my $join = " + JOIN cust_bill_pkg USING ( billpkgnum ) + $join_cust + $join_pkg +"; + +my $where = " + WHERE _date >= $beginning AND _date <= $ending +"; +# AND payby != 'COMP' + +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $where .= " AND agentnum = $1 "; +} + +if ( $cgi->param('out') ) { + + $where .= " + AND 0 = ( + SELECT COUNT(*) FROM cust_main_county AS county_out + WHERE ( county_out.county = cust_main.county + OR ( county_out.county IS NULL AND cust_main.county = '' ) + OR ( county_out.county = '' AND cust_main.county IS NULL) + OR ( county_out.county IS NULL AND cust_main.county IS NULL) + ) + AND ( county_out.state = cust_main.state + OR ( county_out.state IS NULL AND cust_main.state = '' ) + OR ( county_out.state = '' AND cust_main.state IS NULL ) + OR ( county_out.state IS NULL AND cust_main.state IS NULL ) + ) + AND county_out.country = cust_main.country + AND county_out.tax > 0 + ) + "; + +} elsif ( $cgi->param('country' ) ) { + + my $county = dbh->quote( $cgi->param('county') ); + my $state = dbh->quote( $cgi->param('state') ); + my $country = dbh->quote( $cgi->param('country') ); + $where .= " + AND ( county = $county OR $county = '' ) + AND ( state = $state OR $state = '' ) + AND country = $country + "; + $where .= ' AND taxclass = '. dbh->quote( $cgi->param('taxclass') ) + if $cgi->param('taxclass'); + +} + +my $count_query = "SELECT COUNT(*), SUM(amount)". + " FROM cust_tax_exempt_pkg $join $where"; + +my $query = { + 'table' => 'cust_tax_exempt_pkg', + 'addl_from' => $join, + 'hashref' => {}, + 'select' => join(', ', + 'cust_tax_exempt_pkg.*', + #'cust_bill_pkg.*', + #'cust_bill._date', + #'part_pkg.pkg', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), + ), + 'extra_sql' => $where, +}; + +my $ilink = [ "${p}view/cust_bill.cgi?", 'invnum' ]; +my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + +my $conf = new FS::Conf; +my $money_char = $conf->config('money_char') || '$'; + +%><%= include( 'elements/search.html', + 'title' => 'Tax exemptions', + 'name' => 'tax exemptions', + 'query' => $query, + 'count_query' => $count_query, + 'count_addl' => [ $money_char. '%.2f total', ], + 'header' => [ + '#', + 'Date', + 'Amount', + + #'Description', + #'Setup charge', + #'Recurring charge', + #'Invoice', + #'Date', + + FS::UI::Web::cust_header(), + ], + 'fields' => [ + 'exemptpkgnum', + sub { $_[0]->month. '/'. $_[0]->year; }, + 'amount', + + #sub { $_[0]->pkgnum > 0 + # ? $_[0]->get('pkg') + # : $_[0]->get('itemdesc') + # }, + ##strikethrough or "N/A ($amount)" or something these when + ## they're not applicable to pkg_tax search + #sub { sprintf($money_char.'%.2f', shift->setup ) }, + #sub { sprintf($money_char.'%.2f', shift->recur ) }, + #'invnum', + #sub { time2str('%b %d %Y', shift->_date ) }, + + \&FS::UI::Web::cust_fields, + ], + 'links' => [ + '', + '', + '', + + #'', + #'', + #'', + #'', + #$ilink, + #$ilink, + + ( map { $clink } FS::UI::Web::cust_header() ), + ], + 'align' => 'rrr', # 'rlrrrc', + ) +%> + diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi index 9062f0626..b1c6d3809 100755 --- a/httemplate/search/report_tax.cgi +++ b/httemplate/search/report_tax.cgi @@ -7,22 +7,25 @@ my $user = getotaker; my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); -my $from_join_cust = " - FROM cust_bill_pkg +my $join_cust = " JOIN cust_bill USING ( invnum ) - JOIN cust_main USING ( custnum ) + LEFT JOIN cust_main USING ( custnum ) "; +my $from_join_cust = " + FROM cust_bill_pkg + $join_cust +"; my $join_pkg = " - JOIN cust_pkg USING ( pkgnum ) - JOIN part_pkg USING ( pkgpart ) + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) "; my $where = " WHERE _date >= $beginning AND _date <= $ending AND ( county = ? OR ? = '' ) AND ( state = ? OR ? = '' ) AND country = ? - AND payby != 'COMP' "; +# AND payby != 'COMP' my @base_param = qw( county county state state country ); my $agentname = ''; @@ -46,8 +49,6 @@ my $gotcust = " ) "; -my $monthly_exempt_warning = 0; -my $taxclass_flag = 0; my($total, $tot_taxable, $owed, $tax) = ( 0, 0, 0, 0, 0 ); my( $exempt_cust, $exempt_pkg, $exempt_monthly ) = ( 0, 0 ); my $out = 'Out of taxable region(s)'; @@ -59,17 +60,18 @@ foreach my $r (qsearch('cust_main_county', {}, '', $gotcust) ) { $regions{$label}->{'label'} = $label; $regions{$label}->{'url_param'} = join(';', map "$_=".$r->$_(), qw( county state country ) ); - my $fromwhere = $from_join_cust. $join_pkg. $where; my @param = @base_param; + my $mywhere = $where; if ( $r->taxclass ) { - $fromwhere .= " AND taxclass = ? "; + $mywhere .= " AND taxclass = ? "; push @param, 'taxclass'; $regions{$label}->{'url_param'} .= ';taxclass='. $r->taxclass if $cgi->param('show_taxclasses'); - $taxclass_flag = 1; } + my $fromwhere = $from_join_cust. $join_pkg. $mywhere. " AND payby != 'COMP' "; + # my $label = getlabel($r); # $regions{$label}->{'label'} = $label; @@ -83,57 +85,80 @@ foreach my $r (qsearch('cust_main_county', {}, '', $gotcust) ) { $total += $t; $regions{$label}->{'total'} += $t; - ## calculate package-exemption for this region - - foreach my $e ( grep { $r->get($_.'tax') =~ /^Y/i } - qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) { - my $x = scalar_sql($r, \@param, - "SELECT SUM($e) $fromwhere AND $nottax" - ); - $exempt_pkg += $x; - $regions{$label}->{'exempt_pkg'} += $x; - } - ## calculate customer-exemption for this region - my($taxable, $x_cust) = (0, 0); - foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i } - qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) { - $taxable += scalar_sql($r, \@param, - "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )" - ); - - $x_cust += scalar_sql($r, \@param, - "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'" - ); - } +## my $taxable = $t; + +# my($taxable, $x_cust) = (0, 0); +# foreach my $e ( grep { $r->get($_.'tax') !~ /^Y/i } +# qw( cust_bill_pkg.setup cust_bill_pkg.recur ) ) { +# $taxable += scalar_sql($r, \@param, +# "SELECT SUM($e) $fromwhere AND $nottax AND ( tax != 'Y' OR tax IS NULL )" +# ); +# +# $x_cust += scalar_sql($r, \@param, +# "SELECT SUM($e) $fromwhere AND $nottax AND tax = 'Y'" +# ); +# } + + my $x_cust = scalar_sql($r, \@param, + "SELECT SUM(cust_bill_pkg.setup+cust_bill_pkg.recur) + $fromwhere AND $nottax AND tax = 'Y' " + ); $exempt_cust += $x_cust; $regions{$label}->{'exempt_cust'} += $x_cust; + + ## calculate package-exemption for this region - ## calculate monthly exemption (texas tax) for this region + my $x_pkg = scalar_sql($r, \@param, + "SELECT SUM( + ( CASE WHEN part_pkg.setuptax = 'Y' + THEN cust_bill_pkg.setup + ELSE 0 + END + ) + + + ( CASE WHEN part_pkg.recurtax = 'Y' + THEN cust_bill_pkg.recur + ELSE 0 + END + ) + ) + $fromwhere + AND $nottax + AND ( + ( part_pkg.setuptax = 'Y' AND cust_bill_pkg.setup > 0 ) + OR ( part_pkg.recurtax = 'Y' AND cust_bill_pkg.recur > 0 ) + ) + AND ( tax != 'Y' OR tax IS NULL ) + " + ); + $exempt_pkg += $x_pkg; + $regions{$label}->{'exempt_pkg'} += $x_pkg; - my($sday,$smon,$syear) = (localtime($beginning) )[ 3, 4, 5 ]; - $monthly_exempt_warning=1 if $sday != 1 && $beginning; - $smon++; $syear+=1900; + ## calculate monthly exemption (texas tax) for this region - my $eending = ( $ending == 4294967295 ) ? time : $ending; - my($eday,$emon,$eyear) = (localtime($eending) )[ 3, 4, 5 ]; - $emon++; $eyear+=1900; + # count up all the cust_tax_exempt_pkg records associated with + # the actual line items. - my $x_monthly = scalar_sql($r, [ 'taxnum' ], - "SELECT SUM(amount) FROM cust_tax_exempt where taxnum = ? ". - " AND ( year > $syear OR ( year = $syear and month >= $smon ) )". - " AND ( year < $eyear OR ( year = $eyear and month <= $emon ) )" + my $x_monthly = scalar_sql($r, \@param, + "SELECT SUM(amount) + FROM cust_tax_exempt_pkg + JOIN cust_bill_pkg USING ( billpkgnum ) + $join_cust $join_pkg + $mywhere" ); - if ( $x_monthly ) { - warn $r->taxnum(). ": $x_monthly\n"; - $taxable -= $x_monthly; - } +# if ( $x_monthly ) { +# #warn $r->taxnum(). ": $x_monthly\n"; +# $taxable -= $x_monthly; +# } $exempt_monthly += $x_monthly; $regions{$label}->{'exempt_monthly'} += $x_monthly; + my $taxable = $t - $x_cust - $x_pkg - $x_monthly; + $tot_taxable += $taxable; $regions{$label}->{'taxable'} += $taxable; @@ -149,7 +174,7 @@ foreach my $r (qsearch('cust_main_county', {}, '', $gotcust) ) { } -my $taxwhere = "$from_join_cust $where"; +my $taxwhere = "$from_join_cust $where AND payby != 'COMP' "; my @taxparam = @base_param; my %base_regions = (); #foreach my $label ( keys %regions ) { @@ -165,7 +190,7 @@ foreach my $r ( my $label = getlabel($r); - my $fromwhere = $join_pkg. $where; + my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' "; my @param = @base_param; #match itemdesc if necessary! @@ -246,7 +271,7 @@ sub getlabel { $label = "$label (". $r->taxclass. ")" if $r->taxclass && $cgi->param('show_taxclasses') - && ! $opt{'no_taxclasses'}; + && ! $opt{'no_taxclass'}; #$label = $r->taxname. " ($label)" if $r->taxname; } return $label; @@ -266,14 +291,19 @@ sub scalar_sql { %> <% - -my $baselink = $p. "search/cust_bill_pkg.cgi?begin=$beginning;end=$ending"; - +my $dateagentlink = "begin=$beginning;end=$ending"; +$dateagentlink .= ';agentnum='. $cgi->param('agentnum') + if length($agentname); +my $baselink = $p. "search/cust_bill_pkg.cgi?$dateagentlink"; +my $exemptlink = $p. "search/cust_tax_exempt_pkg.cgi?$dateagentlink"; %> - <%= header( "$agentname Sales Tax Report - ". - time2str('%h %o %Y through ', $beginning ). + ( $beginning + ? time2str('%h %o %Y ', $beginning ) + : '' + ). + 'through '. ( $ending == 4294967295 ? 'now' : time2str('%h %o %Y', $ending ) @@ -320,32 +350,36 @@ my $baselink = $p. "search/cust_bill_pkg.cgi?begin=$beginning;end=$ending"; $bgcolor = $bgcolor1; } - my $link = $baselink; + my $link = ''; if ( $region->{'label'} ne 'Total' ) { if ( $region->{'label'} eq $out ) { - $link .= ';out=1'; + $link = ';out=1'; } else { - $link .= ';'. $region->{'url_param'}; + $link = ';'. $region->{'url_param'}; } } + + + + %> <%= $region->{'label'} %> - <%= $money_char %><%= sprintf('%.2f', $region->{'total'} ) %> + <%= $money_char %><%= sprintf('%.2f', $region->{'total'} ) %> - - <%= $money_char %><%= sprintf('%.2f', $region->{'exempt_cust'} ) %> + <%= $money_char %><%= sprintf('%.2f', $region->{'exempt_cust'} ) %> - - <%= $money_char %><%= sprintf('%.2f', $region->{'exempt_pkg'} ) %> + <%= $money_char %><%= sprintf('%.2f', $region->{'exempt_pkg'} ) %> - - <%= $money_char %><%= sprintf('%.2f', $region->{'exempt_monthly'} ) %> + <%= $money_char %><%= sprintf('%.2f', $region->{'exempt_monthly'} ) %> = @@ -359,7 +393,7 @@ my $baselink = $p. "search/cust_bill_pkg.cgi?begin=$beginning;end=$ending"; <% unless ( $cgi->param('show_taxclasses') ) { %> - <%= $money_char %><%= sprintf('%.2f', $region->{'tax'} ) %> + <%= $money_char %><%= sprintf('%.2f', $region->{'tax'} ) %> <% } %> @@ -387,12 +421,12 @@ my $baselink = $p. "search/cust_bill_pkg.cgi?begin=$beginning;end=$ending"; $bgcolor = $bgcolor1; } - my $link = $baselink; + my $link = ''; #if ( $region->{'label'} ne 'Total' ) { if ( $region->{'label'} eq $out ) { - $link .= ';out=1'; + $link = ';out=1'; } else { - $link .= ';'. $region->{'url_param'}; + $link = ';'. $region->{'url_param'}; } #} %> @@ -400,7 +434,7 @@ my $baselink = $p. "search/cust_bill_pkg.cgi?begin=$beginning;end=$ending"; <%= $region->{'label'} %> - <%= $money_char %><%= sprintf('%.2f', $region->{'tax'} ) %> + <%= $money_char %><%= sprintf('%.2f', $region->{'tax'} ) %> @@ -417,15 +451,6 @@ my $baselink = $p. "search/cust_bill_pkg.cgi?begin=$beginning;end=$ending"; <% } %> - -<% if ( $monthly_exempt_warning ) { %> -
- Partial-month tax reports (except for current month) may not be correct due - to month-granularity tax exemption (usually "texas tax"). For an accurate - report, start on the first of a month and end on the last day of a month (or - leave blank for to now). -<% } %> - diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html index eeaccc1ab..7a8ecd4f0 100755 --- a/httemplate/search/report_tax.html +++ b/httemplate/search/report_tax.html @@ -5,18 +5,36 @@

Tax Report Criteria

+ + <%= include( '/elements/tr-select-agent.html' ) %> + <%= include( '/elements/tr-input-beginning_ending.html' ) %> - - - - -
Show tax classes
-
+ <% my $conf = new FS::Conf; + if ( $conf->exists('enable_taxclasses') ) { + %> + + + Show tax classes + + <% } %> + <% my @pkg_class = qsearch('pkg_class', {}); + if ( @pkg_class ) { + %> + + + Show package classes + + <% } %> + + + +
+ -- cgit v1.2.1 From b5ecccfac56fc5d4eaa617a8c08dd168ffb74bac Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 27 Jan 2006 01:33:57 +0000 Subject: on tax exemption report, show more info on the specific line item and invoice --- httemplate/search/cust_tax_exempt_pkg.cgi | 67 +++++++++++++++++-------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/httemplate/search/cust_tax_exempt_pkg.cgi b/httemplate/search/cust_tax_exempt_pkg.cgi index bc1e743b2..e61947b7d 100644 --- a/httemplate/search/cust_tax_exempt_pkg.cgi +++ b/httemplate/search/cust_tax_exempt_pkg.cgi @@ -71,9 +71,9 @@ my $query = { 'hashref' => {}, 'select' => join(', ', 'cust_tax_exempt_pkg.*', - #'cust_bill_pkg.*', - #'cust_bill._date', - #'part_pkg.pkg', + 'cust_bill_pkg.*', + 'cust_bill.*', + 'part_pkg.pkg', 'cust_main.custnum', FS::UI::Web::cust_sql_fields(), ), @@ -94,32 +94,42 @@ my $money_char = $conf->config('money_char') || '$'; 'count_addl' => [ $money_char. '%.2f total', ], 'header' => [ '#', - 'Date', + 'Month', 'Amount', - - #'Description', - #'Setup charge', - #'Recurring charge', - #'Invoice', - #'Date', - + 'Line item', + 'Invoice', + 'Date', FS::UI::Web::cust_header(), ], 'fields' => [ 'exemptpkgnum', sub { $_[0]->month. '/'. $_[0]->year; }, - 'amount', - - #sub { $_[0]->pkgnum > 0 - # ? $_[0]->get('pkg') - # : $_[0]->get('itemdesc') - # }, - ##strikethrough or "N/A ($amount)" or something these when - ## they're not applicable to pkg_tax search - #sub { sprintf($money_char.'%.2f', shift->setup ) }, - #sub { sprintf($money_char.'%.2f', shift->recur ) }, - #'invnum', - #sub { time2str('%b %d %Y', shift->_date ) }, + sub { $money_char. $_[0]->amount; }, + + sub { + $_[0]->billpkgnum. ': '. + ( $_[0]->pkgnum > 0 + ? $_[0]->get('pkg') + : $_[0]->get('itemdesc') + ). + ' ('. + ( $_[0]->setup > 0 + ? $money_char. $_[0]->setup. ' setup' + : '' + ). + ( $_[0]->setup > 0 && $_[0]->recur > 0 + ? ' / ' + : '' + ). + ( $_[0]->recur > 0 + ? $money_char. $_[0]->recur. ' recur' + : '' + ). + ')'; + }, + + 'invnum', + sub { time2str('%b %d %Y', shift->_date ) }, \&FS::UI::Web::cust_fields, ], @@ -128,16 +138,13 @@ my $money_char = $conf->config('money_char') || '$'; '', '', - #'', - #'', - #'', - #'', - #$ilink, - #$ilink, + '', + $ilink, + $ilink, ( map { $clink } FS::UI::Web::cust_header() ), ], - 'align' => 'rrr', # 'rlrrrc', + 'align' => 'rrrlrc', # 'rlrrrc', ) %> -- cgit v1.2.1 From 0a4a314b33a22b314ac5c4c85840a4bbff49cdf3 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 27 Jan 2006 07:34:17 +0000 Subject: small visual fix to alternating row colors when show_taxclasses is on --- httemplate/search/report_tax.cgi | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi index b1c6d3809..61ed26fac 100755 --- a/httemplate/search/report_tax.cgi +++ b/httemplate/search/report_tax.cgi @@ -413,6 +413,8 @@ my $exemptlink = $p. "search/cust_tax_exempt_pkg.cgi?$dateagentlink"; <% #some false laziness w/above + $bgcolor1 = '#eeeeee'; + $bgcolor2 = '#ffffff'; foreach my $region ( @base_regions ) { if ( $bgcolor eq $bgcolor1 ) { @@ -440,6 +442,14 @@ my $exemptlink = $p. "search/cust_tax_exempt_pkg.cgi?$dateagentlink"; <% } %> + <% + if ( $bgcolor eq $bgcolor1 ) { + $bgcolor = $bgcolor2; + } else { + $bgcolor = $bgcolor1; + } + %> + Total -- cgit v1.2.1 From 7066204f45124e0100d5330cce63584ff9e4bacb Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 31 Jan 2006 02:59:59 +0000 Subject: fix "table not found" dbdef error message to recommend freeside-upgrade instead create + dbdef-create --- FS/FS/Record.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 19da3d181..4a0fe3fd2 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -250,7 +250,7 @@ sub qsearch { my $table = $cache ? $cache->table : $stable; my $dbdef_table = dbdef->table($table) or die "No schema for table $table found - ". - "do you need to create it or run dbdef-create?"; + "do you need to run freeside-upgrade?"; my $pkey = $dbdef_table->primary_key; my @real_fields = grep exists($record->{$_}), real_fields($table); -- cgit v1.2.1 From c1bb4ddb71147d0571bd301a6d8c452fdf0e1bc9 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 31 Jan 2006 04:26:54 +0000 Subject: move header() to include(/elements/header.html) so it can be changed in one place, thanks to Scott Edwards --- httemplate/browse/addr_block.cgi | 2 +- httemplate/browse/agent.cgi | 2 +- httemplate/browse/agent_type.cgi | 2 +- httemplate/browse/cust_pay_batch.cgi | 2 +- httemplate/browse/part_bill_event.cgi | 2 +- httemplate/browse/part_export.cgi | 2 +- httemplate/browse/part_pkg.cgi | 2 +- httemplate/browse/part_referral.cgi | 2 +- httemplate/browse/part_svc.cgi | 2 +- httemplate/browse/part_virtual_field.cgi | 2 +- httemplate/browse/payment_gateway.html | 2 +- httemplate/browse/queue.cgi | 2 +- httemplate/browse/rate.cgi | 2 +- httemplate/browse/router.cgi | 2 +- httemplate/browse/svc_acct_pop.cgi | 2 +- httemplate/config/config-view.cgi | 2 +- httemplate/config/config.cgi | 2 +- httemplate/edit/REAL_cust_pkg.cgi | 2 +- httemplate/edit/agent.cgi | 2 +- httemplate/edit/agent_payment_gateway.html | 2 +- httemplate/edit/agent_type.cgi | 2 +- httemplate/edit/bulk-cust_svc.html | 2 +- httemplate/edit/cust_pay.cgi | 2 +- httemplate/edit/part_export.cgi | 2 +- httemplate/edit/part_pkg.cgi | 2 +- httemplate/edit/part_svc.cgi | 2 +- httemplate/edit/payment_gateway.html | 2 +- httemplate/edit/prepay_credit.cgi | 2 +- httemplate/edit/process/prepay_credit.cgi | 2 +- httemplate/edit/process/reg_code.cgi | 2 +- httemplate/edit/rate.cgi | 2 +- httemplate/edit/rate_region.cgi | 2 +- httemplate/edit/reg_code.cgi | 2 +- httemplate/edit/svc_acct.cgi | 2 +- httemplate/edit/svc_broadband.cgi | 2 +- httemplate/edit/svc_forward.cgi | 2 +- httemplate/misc/batch-cust_pay.html | 2 +- httemplate/misc/cust_main-import.cgi | 2 +- httemplate/misc/cust_main-import_charges.cgi | 2 +- httemplate/misc/expire_pkg.cgi | 2 +- httemplate/misc/link.cgi | 2 +- httemplate/misc/meta-import.cgi | 2 +- httemplate/misc/process/cust_main-import.cgi | 2 +- httemplate/misc/process/cust_main-import_charges.cgi | 2 +- httemplate/misc/process/meta-import.cgi | 2 +- httemplate/misc/upload-batch.cgi | 2 +- httemplate/misc/whois.cgi | 2 +- httemplate/search/report_prepaid_income.cgi | 2 +- httemplate/search/report_tax.cgi | 2 +- httemplate/search/svc_external.cgi | 2 +- httemplate/view/cust_bill.cgi | 2 +- httemplate/view/cust_main.cgi | 2 +- httemplate/view/svc_acct.cgi | 4 ++-- httemplate/view/svc_broadband.cgi | 2 +- httemplate/view/svc_domain.cgi | 2 +- httemplate/view/svc_external.cgi | 2 +- 56 files changed, 57 insertions(+), 57 deletions(-) diff --git a/httemplate/browse/addr_block.cgi b/httemplate/browse/addr_block.cgi index 06ac556cf..d453adf8e 100644 --- a/httemplate/browse/addr_block.cgi +++ b/httemplate/browse/addr_block.cgi @@ -1,4 +1,4 @@ -<%= header('Address Blocks', menubar('Main Menu' => $p)) %> +<%= include("/elements/header.html",'Address Blocks', menubar('Main Menu' => $p)) %> <% use NetAddr::IP; diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi index 05300d0bd..17cc8bd40 100755 --- a/httemplate/browse/agent.cgi +++ b/httemplate/browse/agent.cgi @@ -11,7 +11,7 @@ my $conf = new FS::Conf; %> -<%= header('Agent Listing', menubar( +<%= include("/elements/header.html",'Agent Listing', menubar( 'Main Menu' => $p, 'Agent Types' => $p. 'browse/agent_type.cgi', # 'Add new agent' => '../edit/agent.cgi' diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi index 5473804e8..2e1bdad42 100755 --- a/httemplate/browse/agent_type.cgi +++ b/httemplate/browse/agent_type.cgi @@ -1,5 +1,5 @@ -<%= header("Agent Type Listing", menubar( +<%= include("/elements/header.html","Agent Type Listing", menubar( 'Main Menu' => $p, 'Agents' => $p. 'browse/agent.cgi', )) %> diff --git a/httemplate/browse/cust_pay_batch.cgi b/httemplate/browse/cust_pay_batch.cgi index 3420e97b6..0f05ecb25 100755 --- a/httemplate/browse/cust_pay_batch.cgi +++ b/httemplate/browse/cust_pay_batch.cgi @@ -1,5 +1,5 @@ -<%= header("Pending credit card batch", menubar( 'Main Menu' => $p,)) %> +<%= include("/elements/header.html","Pending credit card batch", menubar( 'Main Menu' => $p,)) %>
Download batch in format '; -print "Invoice Event #", $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)"; +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +<% } %> -print ntable("#cccccc",2), <Payby +Invoice Event #<%= $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)" %> -for (qw(CARD DCRD CHEK DCHK LECB BILL COMP)) { - print qq!"; - } else { - print ">$_"; - } -} +<%= ntable("#cccccc",2) %> -my $days = $hashref->{seconds}/86400; + + Event name + + -print < -Event -After days -END + + For + + customers + + + + <% my $days = $hashref->{seconds}/86400; %> -print 'Disabled'; -print '{disabled} eq "Y"; -print '>'; -print ''; + + After + days + -print 'Action'; + + Test event + + + + + + + + Disabled + + {disabled} eq 'Y' ? ' CHECKED' : '' %>> + + + + + Action + + +<% #print ntable(); @@ -113,7 +151,7 @@ tie my %events, 'Tie::IxHash', 'code' => '$cust_main->suspend();', 'weight' => 10, }, - 'suspend' => { + 'suspend-if-balance' => { 'name' => 'Suspend if balance (this invoice and previous) over', 'code' => '$cust_bill->cust_suspend_if_balance_over( %%%balanceover%%% );', 'html' => " $money_char ". '', @@ -174,13 +212,13 @@ tie my %events, 'Tie::IxHash', }, 'send' => { - 'name' => 'Send invoice (email/print)', + 'name' => 'Send invoice (email/print/fax)', 'code' => '$cust_bill->send();', 'weight' => 50, }, 'send_alternate' => { - 'name' => 'Send invoice (email/print) with alternate template', + 'name' => 'Send invoice (email/print/fax) with alternate template', 'code' => '$cust_bill->send(\'%%%templatename%%%\');', 'html' => '', @@ -188,7 +226,7 @@ tie my %events, 'Tie::IxHash', }, 'send_if_newest' => { - 'name' => 'Send invoice (email/print) with alternate template, if it is still the newest invoice (useful for late notices - set to 31 days or later)', + 'name' => 'Send invoice (email/print/fax) with alternate template, if it is still the newest invoice (useful for late notices - set to 31 days or later)', 'code' => '$cust_bill->send_if_newest(\'%%%if_newest_templatename%%%\');', 'html' => '', @@ -196,7 +234,7 @@ tie my %events, 'Tie::IxHash', }, 'send_agent' => { - 'name' => 'Send invoice (email/print) ', + 'name' => 'Send invoice (email/print/fax) ', 'code' => '$cust_bill->send(\'%%%agent_templatename%%%\', [ %%%agentnum%%% ], \'%%%agent_invoice_from%%%\');', 'html' => sub { ' @@ -263,6 +301,7 @@ tie my %events, 'Tie::IxHash', 'code' => '$cust_bill->spool_csv( \'format\' => \'%%%spoolformat%%%\', \'dest\' => \'%%%spooldest%%%\', + \'balanceover\' => \'%%%spoolbalanceover%%%\', \'agent_spools\' => \'%%%spoolagent_spools%%%\', );', 'html' => sub { @@ -303,6 +342,13 @@ tie my %events, 'Tie::IxHash', $html .= ''. ''. + + ''. + ''. + ''. ''. ' <% } %> diff --git a/httemplate/edit/payment_gateway.html b/httemplate/edit/payment_gateway.html index cf825df00..1eda11249 100644 --- a/httemplate/edit/payment_gateway.html +++ b/httemplate/edit/payment_gateway.html @@ -34,70 +34,89 @@ Gateway #<%= $payment_gateway->gatewaynum || "(NEW)" %> - - + - + - +
if balance (this invoice and previous) over '. + "$money_char ". + ''. + '
Individual per-agent spools? {'spoolagent_spools'} ? 'CHECKED' : '' ). -- cgit v1.2.1 From 87d6769f31ddd6826211db1a9784ca8dc2297391 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 31 Jan 2006 15:01:41 +0000 Subject: oops, forgot $ --- FS/FS/payby.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm index 3d43dff60..4425df040 100644 --- a/FS/FS/payby.pm +++ b/FS/FS/payby.pm @@ -103,7 +103,7 @@ sub payby2longname { sub cust_payby { my $self = shift; - grep { ! exists $hash{$_}->{cust_main} } self->payby; + grep { ! exists $hash{$_}->{cust_main} } $self->payby; } sub cust_payby2longname { -- cgit v1.2.1 From 56d54e6cb3d973558e3cb10d8673762b2fa19e67 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 1 Feb 2006 07:58:09 +0000 Subject: HEAD isn't 1.5.8 anymore --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2c5abf9c9..dae5f23b4 100644 --- a/Makefile +++ b/Makefile @@ -102,8 +102,8 @@ RT_PATH = /opt/rt3 FREESIDE_PATH = `pwd` PERL_INC_DEV_KLUDGE = /usr/local/share/perl/5.8.7/ -VERSION=1.5.8cvs -TAG=freeside_1_5_8 +VERSION=1.7.0cvs +TAG=freeside_1_7_0 help: @echo "supported targets:" -- cgit v1.2.1 From a9c8414692c1777da3ff78b83a1e6bbb0729f6eb Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 1 Feb 2006 23:13:48 +0000 Subject: finish adding freeside-monthly and monthly events --- FS/FS/Cron/backup.pm | 43 +++++++++ FS/FS/Cron/bill.pm | 119 +++++++++++++++++++++++++ FS/FS/Cron/vacuum.pm | 23 +++++ FS/FS/cust_bill.pm | 7 ++ FS/FS/cust_main.pm | 31 ++++--- FS/MANIFEST | 6 ++ FS/bin/freeside-daily | 160 ++++------------------------------ FS/bin/freeside-monthly | 91 +++++++++++++++++++ FS/t/Cron-backup.t | 5 ++ FS/t/Cron-bill.t | 5 ++ FS/t/Cron-vacuum.t | 5 ++ httemplate/browse/part_bill_event.cgi | 3 +- 12 files changed, 340 insertions(+), 158 deletions(-) create mode 100644 FS/FS/Cron/backup.pm create mode 100644 FS/FS/Cron/bill.pm create mode 100644 FS/FS/Cron/vacuum.pm create mode 100755 FS/bin/freeside-monthly create mode 100644 FS/t/Cron-backup.t create mode 100644 FS/t/Cron-bill.t create mode 100644 FS/t/Cron-vacuum.t diff --git a/FS/FS/Cron/backup.pm b/FS/FS/Cron/backup.pm new file mode 100644 index 000000000..204069a12 --- /dev/null +++ b/FS/FS/Cron/backup.pm @@ -0,0 +1,43 @@ +package FS::Cron::backup; + +use strict; +use vars qw( @ISA @EXPORT_OK ); +use Exporter; +use FS::UID qw(driver_name datasrc); + +@ISA = qw( Exporter ); +@EXPORT_OK = qw( backup_scp ); + +sub backup_scp { + my $conf = new FS::Conf; + my $dest = $conf->config('dump-scpdest'); + if ( $dest ) { + datasrc =~ /dbname=([\w\.]+)$/ or die "unparsable datasrc ". datasrc; + my $database = $1; + eval "use Net::SCP qw(scp);"; + die $@ if $@; + if ( driver_name eq 'Pg' ) { + system("pg_dump $database >/var/tmp/$database.sql") + } else { + die "database dumps not yet supported for ". driver_name; + } + if ( $conf->config('dump-pgpid') ) { + eval 'use GnuPG;'; + die $@ if $@; + my $gpg = new GnuPG; + $gpg->encrypt( plaintext => "/var/tmp/$database.sql", + output => "/var/tmp/$database.gpg", + recipient => $conf->config('dump-pgpid'), + ); + chmod 0600, '/var/tmp/$database.gpg'; + scp("/var/tmp/$database.gpg", $dest); + unlink "/var/tmp/$database.gpg" or die $!; + } else { + chmod 0600, '/var/tmp/$database.sql'; + scp("/var/tmp/$database.sql", $dest); + } + unlink "/var/tmp/$database.sql" or die $!; + } +} + +1; diff --git a/FS/FS/Cron/bill.pm b/FS/FS/Cron/bill.pm new file mode 100644 index 000000000..774bf3006 --- /dev/null +++ b/FS/FS/Cron/bill.pm @@ -0,0 +1,119 @@ +package FS::Cron::bill; + +use strict; +use vars qw( @ISA @EXPORT_OK ); +use Exporter; +use Date::Parse; +use FS::Record qw(qsearch qsearchs); +use FS::cust_main; + +@ISA = qw( Exporter ); +@EXPORT_OK = qw ( bill ); + +sub bill { + + my %opt = @_; + + $FS::cust_main::DEBUG = 1 if $opt{'v'}; + + my %search = (); + $search{'payby'} = $opt{'p'} if $opt{'p'}; + $search{'agentnum'} = $opt{'a'} if $opt{'a'}; + + #we're at now now (and later). + my($time)= $opt{'d'} ? str2time($opt{'d'}) : $^T; + $time += $opt{'y'} * 86400 if $opt{'y'}; + + # select * from cust_main where + my $where_pkg = <<"END"; + 0 < ( select count(*) from cust_pkg + where cust_main.custnum = cust_pkg.custnum + and ( cancel is null or cancel = 0 ) + and ( setup is null or setup = 0 + or bill is null or bill <= $time + or ( expire is not null and expire <= $^T ) + ) + ) +END + + # or + my $where_bill_event = <<"END"; + 0 < ( select count(*) from cust_bill + where cust_main.custnum = cust_bill.custnum + and 0 < 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 + ) + and 0 < ( select count(*) from part_bill_event + where payby = cust_main.payby + and ( disabled is null or disabled = '' ) + and seconds <= $time - cust_bill._date + and 0 = ( select count(*) from cust_bill_event + where cust_bill.invnum = cust_bill_event.invnum + and part_bill_event.eventpart = cust_bill_event.eventpart + and status = 'done' + ) + + ) + ) +END + + my $extra_sql = ( scalar(%search) ? ' AND ' : ' WHERE ' ). "( $where_pkg OR $where_bill_event )"; + + my @cust_main; + if ( @ARGV ) { + @cust_main = map { qsearchs('cust_main', { custnum => $_, %search } ) } @ARGV + } else { + @cust_main = qsearch('cust_main', \%search, '', $extra_sql ); + } + ; + + my($cust_main,%saw); + foreach $cust_main ( @cust_main ) { + + # $^T not $time because -d is for pre-printing invoices + foreach my $cust_pkg ( + grep { $_->expire && $_->expire <= $^T } $cust_main->ncancelled_pkgs + ) { + my $error = $cust_pkg->cancel; + warn "Error cancelling expired pkg ". $cust_pkg->pkgnum. " for custnum ". + $cust_main->custnum. ": $error" + if $error; + } + # $^T not $time because -d is for pre-printing invoices + foreach my $cust_pkg ( + grep { $_->part_pkg->is_prepaid + && $_->bill && $_->bill < $^T && ! $_->susp + } + $cust_main->ncancelled_pkgs + ) { + my $error = $cust_pkg->suspend; + warn "Error suspending package ". $cust_pkg->pkgnum. + " for custnum ". $cust_main->custnum. + ": $error" + if $error; + } + + my $error = $cust_main->bill( 'time' => $time, + 'resetup' => $opt{'s'}, + ); + warn "Error billing, custnum ". $cust_main->custnum. ": $error" if $error; + + $cust_main->apply_payments; + $cust_main->apply_credits; + + $error = $cust_main->collect( 'invoice_time' => $time, + 'freq' => $opt{'freq'}, + ); + warn "Error collecting, custnum". $cust_main->custnum. ": $error" if $error; + + } + +} diff --git a/FS/FS/Cron/vacuum.pm b/FS/FS/Cron/vacuum.pm new file mode 100644 index 000000000..075572d50 --- /dev/null +++ b/FS/FS/Cron/vacuum.pm @@ -0,0 +1,23 @@ +package FS::Cron::vacuum; + +use vars qw( @ISA @EXPORT_OK); +use Exporter; +use FS::UID qw(driver_name dbh); +use FS::Schema qw(dbdef); + +@ISA = qw( Exporter ); +@EXPORT_OK = qw( vacuum ); + +sub vacuum { + + if ( driver_name eq 'Pg' ) { + dbh->{AutoCommit} = 1; #so we can vacuum + foreach my $table ( dbdef->tables ) { + my $sth = dbh->prepare("VACUUM ANALYZE $table") or die dbh->errstr; + $sth->execute or die $sth->errstr; + } + } + +} + +1; diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 159c9e405..cce028b2c 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -836,6 +836,8 @@ Options are: =item agent_spools - if set to a true value, will spool to per-agent files rather than a single global file +=item balanceover - if set, only spools the invoice if the total amount owed on this invoice and all older invoices is greater than the specified amount. + =back =cut @@ -852,6 +854,11 @@ sub spool_csv { || ! keys %invoicing_list; } + if ( $opt{'balanceover'} ) { + return 'N/A' + if $cust_main->total_owed_date($self->_date) < $opt{'balanceover'}; + } + my $spooldir = "/usr/local/etc/freeside/export.". datasrc. "/cust_bill"; mkdir $spooldir, 0700 unless -d $spooldir; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index b5ccf5a3f..973fe7c81 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -118,8 +118,6 @@ FS::cust_main - Object methods for cust_main records $error = $record->collect; $error = $record->collect %options; $error = $record->collect 'invoice_time' => $time, - 'batch_card' => 'yes', - 'report_badcard' => 'yes', ; =head1 DESCRIPTION @@ -1886,7 +1884,7 @@ sub bill { $dbh->rollback if $oldAutoCommit; return "fatal: can't lookup exising exemption: ". dbh->errstr; }; - my $existing_exemption = $sth->fetchrow_arrayref->[0]; + my $existing_exemption = $sth->fetchrow_arrayref->[0] || 0; my $remaining_exemption = $tax->exempt_amount - $existing_exemption; @@ -2001,17 +1999,11 @@ for conversion functions. retry - Retry card/echeck/LEC transactions even when not scheduled by invoice events. -retry_card - Deprecated alias for 'retry' - -batch_card - This option is deprecated. See the invoice events web interface -to control whether cards are batched or run against a realtime gateway. - -report_badcard - This option is deprecated. - -force_print - This option is deprecated; see the invoice events web interface. - quiet - set true to surpress email card/ACH decline notices. +freq - "1d" for the traditional, daily events (the default), or "1m" for the +new monthly events + =cut sub collect { @@ -2052,6 +2044,13 @@ sub collect { } } + my $extra_sql = ''; + if ( defined $options{'freq'} && $options{'freq'} eq '1m' ) { + $extra_sql = " AND freq = '1m' "; + } else { + $extra_sql = " AND ( freq = '1d' OR freq IS NULL OR freq = '' ) "; + } + foreach my $cust_bill ( $self->open_cust_bill ) { # don't try to charge for the same invoice if it's already in a batch @@ -2073,8 +2072,12 @@ sub collect { 'status' => 'done', } ) } - qsearch('part_bill_event', { 'payby' => $self->payby, - 'disabled' => '', } ) + qsearch( { + 'table' => 'part_bill_event', + 'hashref' => { 'payby' => $self->payby, + 'disabled' => '', }, + 'extra_sql' => $extra_sql, + } ) ) { last if $cust_bill->owed <= 0 # don't run subsequent events if owed<=0 diff --git a/FS/MANIFEST b/FS/MANIFEST index d8adfb9e0..452041e35 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -33,6 +33,9 @@ FS/ClientAPI/passwd.pm FS/ClientAPI/MyAccount.pm FS/Conf.pm FS/ConfItem.pm +FS/Cron/backup.pm +FS/Cron/bill.pm +FS/Cron/vacuum.pm FS/Daemon.pm FS/Misc.pm FS/Record.pm @@ -167,6 +170,9 @@ t/ClientAPI.t t/ClientAPI_SessionCache.t t/Conf.t t/ConfItem.t +t/Cron-backup.t +t/Cron-bill.t +t/Cron-vacuum.t t/Daemon.t t/Misc.t t/Record.t diff --git a/FS/bin/freeside-daily b/FS/bin/freeside-daily index 603da12b8..b9742c4d1 100755 --- a/FS/bin/freeside-daily +++ b/FS/bin/freeside-daily @@ -1,157 +1,29 @@ #!/usr/bin/perl -w use strict; -use Fcntl qw(:flock); -use Date::Parse; use Getopt::Std; -use FS::UID qw(adminsuidsetup driver_name dbh datasrc); -use FS::Record qw(qsearch qsearchs dbdef); -use FS::Conf; -use FS::cust_main; +use FS::UID qw(adminsuidsetup); &untaint_argv; #what it sounds like (eww) -use vars qw($opt_d $opt_v $opt_p $opt_a $opt_s $opt_y); -getopts("p:a:d:vsy:"); -my $user = shift or die &usage; +#use vars qw($opt_d $opt_v $opt_p $opt_a $opt_s $opt_y); +use vars qw(%opt); +getopts("p:a:d:vsy:", \%opt); +my $user = shift or die &usage; adminsuidsetup $user; -$FS::cust_main::DEBUG = 1 if $opt_v; - -my %search = (); -$search{'payby'} = $opt_p if $opt_p; -$search{'agentnum'} = $opt_a if $opt_a; - -#we're at now now (and later). -my($time)= $opt_d ? str2time($opt_d) : $^T; -$time += $opt_y * 86400 if $opt_y; - -# select * from cust_main where -my $where_pkg = <<"END"; - 0 < ( select count(*) from cust_pkg - where cust_main.custnum = cust_pkg.custnum - and ( cancel is null or cancel = 0 ) - and ( setup is null or setup = 0 - or bill is null or bill <= $time - or ( expire is not null and expire <= $^T ) - ) - ) -END - -# or -my $where_bill_event = <<"END"; - 0 < ( select count(*) from cust_bill - where cust_main.custnum = cust_bill.custnum - and 0 < 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 - ) - and 0 < ( select count(*) from part_bill_event - where payby = cust_main.payby - and ( disabled is null or disabled = '' ) - and seconds <= $time - cust_bill._date - and 0 = ( select count(*) from cust_bill_event - where cust_bill.invnum = cust_bill_event.invnum - and part_bill_event.eventpart = cust_bill_event.eventpart - and status = 'done' - ) - - ) - ) -END - -my $extra_sql = ( scalar(%search) ? ' AND ' : ' WHERE ' ). "( $where_pkg OR $where_bill_event )"; - -my @cust_main; -if ( @ARGV ) { - @cust_main = map { qsearchs('cust_main', { custnum => $_, %search } ) } @ARGV -} else { - @cust_main = qsearch('cust_main', \%search, '', $extra_sql ); -} -; - -my($cust_main,%saw); -foreach $cust_main ( @cust_main ) { - - # $^T not $time because -d is for pre-printing invoices - foreach my $cust_pkg ( - grep { $_->expire && $_->expire <= $^T } $cust_main->ncancelled_pkgs - ) { - my $error = $cust_pkg->cancel; - warn "Error cancelling expired pkg ". $cust_pkg->pkgnum. " for custnum ". - $cust_main->custnum. ": $error" - if $error; - } - # $^T not $time because -d is for pre-printing invoices - foreach my $cust_pkg ( - grep { $_->part_pkg->is_prepaid - && $_->bill && $_->bill < $^T && ! $_->susp - } - $cust_main->ncancelled_pkgs - ) { - my $error = $cust_pkg->suspend; - warn "Error suspending package ". $cust_pkg->pkgnum. - " for custnum ". $cust_main->custnum. - ": $error" - if $error; - } +use FS::Cron::bill qw(bill); +bill(%opt); - my $error = $cust_main->bill( 'time' => $time, - 'resetup' => $opt_s, ); - warn "Error billing, custnum ". $cust_main->custnum. ": $error" if $error; +use FS::Cron::vacuum qw(vacuum); +vacuum(); - $cust_main->apply_payments; - $cust_main->apply_credits; - - $error = $cust_main->collect( 'invoice_time' => $time ); - warn "Error collecting, custnum". $cust_main->custnum. ": $error" if $error; - -} - -if ( driver_name eq 'Pg' ) { - dbh->{AutoCommit} = 1; #so we can vacuum - foreach my $table ( dbdef->tables ) { - my $sth = dbh->prepare("VACUUM ANALYZE $table") or die dbh->errstr; - $sth->execute or die $sth->errstr; - } -} - -my $conf = new FS::Conf; -my $dest = $conf->config('dump-scpdest'); -if ( $dest ) { - datasrc =~ /dbname=([\w\.]+)$/ or die "unparsable datasrc ". datasrc; - my $database = $1; - eval "use Net::SCP qw(scp);"; - if ( driver_name eq 'Pg' ) { - system("pg_dump $database >/var/tmp/$database.sql") - } else { - die "database dumps not yet supported for ". driver_name; - } - if ( $conf->config('dump-pgpid') ) { - eval 'use GnuPG'; - my $gpg = new GnuPG; - $gpg->encrypt( plaintext => "/var/tmp/$database.sql", - output => "/var/tmp/$database.gpg", - recipient => $conf->config('dump-pgpid'), - ); - chmod 0600, '/var/tmp/$database.gpg'; - scp("/var/tmp/$database.gpg", $dest); - unlink "/var/tmp/$database.gpg" or die $!; - } else { - chmod 0600, '/var/tmp/$database.sql'; - scp("/var/tmp/$database.sql", $dest); - } - unlink "/var/tmp/$database.sql" or die $!; -} +use FS::Cron::backup qw(backup_scp); +backup_scp(); +### # subroutines +### sub untaint_argv { foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV @@ -166,6 +38,10 @@ sub usage { die "Usage:\n\n freeside-daily [ -d 'date' ] user [ custnum custnum ... ]\n"; } +### +# documentation +### + =head1 NAME freeside-daily - Run daily billing and invoice collection events. @@ -179,8 +55,6 @@ freeside-daily - Run daily billing and invoice collection events. Bills customers and runs invoice collection events. Should be run from crontab daily. -This script replaces freeside-bill from 1.3.1. - Bills customers. Searches for customers who are due for billing and calls the bill and collect methods of a cust_main object. See L. diff --git a/FS/bin/freeside-monthly b/FS/bin/freeside-monthly new file mode 100755 index 000000000..a6c75e715 --- /dev/null +++ b/FS/bin/freeside-monthly @@ -0,0 +1,91 @@ +#!/usr/bin/perl -w + +use strict; +use Getopt::Std; +use FS::UID qw(adminsuidsetup); + +&untaint_argv; #what it sounds like (eww) +#use vars qw($opt_d $opt_v $opt_p $opt_a $opt_s $opt_y); +use vars qw(%opt); +getopts("p:a:d:vsy:", \%opt); + +my $user = shift or die &usage; +adminsuidsetup $user; + +use FS::Cron::bill qw(bill); +bill(%opt, 'freq'=>'1m' ); + +### +# subroutines +### + +sub untaint_argv { + foreach $_ ( $[ .. $#ARGV ) { #untaint @ARGV + #$ARGV[$_] =~ /^([\w\-\/]*)$/ || die "Illegal arguement \"$ARGV[$_]\""; + # Date::Parse + $ARGV[$_] =~ /^(.*)$/ || die "Illegal arguement \"$ARGV[$_]\""; + $ARGV[$_]=$1; + } +} + +sub usage { + die "Usage:\n\n freeside-monthly [ -d 'date' ] user [ custnum custnum ... ]\n"; +} + +### +# documentation +### + +=head1 NAME + +freeside-monthly - Run monthly billing and invoice collection events. + +=head1 SYNOPSIS + + freeside-monthly [ -d 'date' ] [ -y days ] [ -p 'payby' ] [ -a agentnum ] [ -s ] [ -v ] user [ custnum custnum ... ] + +=head1 DESCRIPTION + +Bills customers and runs invoice collection events, for the alternate monthly +event chain. If you have defined monthly event checks, should be run from +crontab monthly. + +Bills customers. Searches for customers who are due for billing and calls +the bill and collect methods of a cust_main object. See L. + + -d: Pretend it's 'date'. Date is in any format Date::Parse is happy with, + but be careful. + + -y: In addition to -d, which specifies an absolute date, the -y switch + specifies an offset, in days. For example, "-y 15" would increment the + "pretend date" 15 days from whatever was specified by the -d switch + (or now, if no -d switch was given). + + -p: Only process customers with the specified payby (I, I, I, I, I, I, I) + + -a: Only process customers with the specified agentnum + + -s: re-charge setup fees + + -v: enable debugging + +user: From the mapsecrets file - see config.html from the base documentation + +custnum: if one or more customer numbers are specified, only bills those +customers. Otherwise, bills all customers. + +=head1 NOTE + +In most cases, you would use freeside-daily only and not freeside-monthly. +freeside-monthly would only be used in cases where you have events that can +only be run once each month, for example, batching invoices to a third-party +print/mail provider. + +=head1 BUGS + +=head1 SEE ALSO + +L, L, config.html from the base documentation + +=cut + diff --git a/FS/t/Cron-backup.t b/FS/t/Cron-backup.t new file mode 100644 index 000000000..847d41aed --- /dev/null +++ b/FS/t/Cron-backup.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::Cron::backup; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/Cron-bill.t b/FS/t/Cron-bill.t new file mode 100644 index 000000000..42c7b4f9e --- /dev/null +++ b/FS/t/Cron-bill.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::Cron::bill; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/Cron-vacuum.t b/FS/t/Cron-vacuum.t new file mode 100644 index 000000000..eaa6b762a --- /dev/null +++ b/FS/t/Cron-vacuum.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::Cron::vacuum; +$loaded=1; +print "ok 1\n"; diff --git a/httemplate/browse/part_bill_event.cgi b/httemplate/browse/part_bill_event.cgi index 0b6d0cb2b..380e4d78b 100755 --- a/httemplate/browse/part_bill_event.cgi +++ b/httemplate/browse/part_bill_event.cgi @@ -32,7 +32,8 @@ my $total = scalar(@part_bill_event); my $oldfreq = ''; my @payby_part_bill_event = grep { $payby eq $_->payby } - sort { $a->seconds <=> $b->seconds + sort { $a->freq cmp $b->freq # for now + || $a->seconds <=> $b->seconds || $a->weight <=> $b->weight || $a->eventpart <=> $b->eventpart } -- cgit v1.2.1 From 860e628d3d0d2ba432d401de5c9d4784c918be54 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 5 Feb 2006 12:27:20 +0000 Subject: payment gateway editing --- FS/FS/option_Common.pm | 2 +- httemplate/browse/payment_gateway.html | 11 +-- httemplate/edit/payment_gateway.html | 103 ++++++++++++++++----------- httemplate/edit/process/payment_gateway.html | 2 +- 4 files changed, 70 insertions(+), 48 deletions(-) diff --git a/FS/FS/option_Common.pm b/FS/FS/option_Common.pm index f258fa1d6..ad3c26958 100644 --- a/FS/FS/option_Common.pm +++ b/FS/FS/option_Common.pm @@ -140,7 +140,7 @@ sub delete { } -=item replace [ HASHREF | OPTION => VALUE ... ] +=item replace OLD_RECORD [ HASHREF | OPTION => VALUE ... ] Replaces the OLD_RECORD with this one in the database. If there is an error, returns the error, otherwise returns false. diff --git a/httemplate/browse/payment_gateway.html b/httemplate/browse/payment_gateway.html index 002932ccd..791906b78 100644 --- a/httemplate/browse/payment_gateway.html +++ b/httemplate/browse/payment_gateway.html @@ -40,10 +40,13 @@ <%= $payment_gateway->disabled ? 'DISABLED' : '' %><%= $payment_gateway->gateway_module %> - <%= !$payment_gateway->disabled - ? ' (disable)' - : '' - %> + + (edit) + <%= !$payment_gateway->disabled + ? '(disable)' + : '' + %> + <%= $payment_gateway->gateway_username %> -
Gateway: + + <% if ( $payment_gateway->gatewaynum ) { %> + + <%= $payment_gateway->gateway_module %> + + + <% } else { %> + + + <% } %> - +
Username:
Password:
Action:
Options: + +
diff --git a/httemplate/edit/process/payment_gateway.html b/httemplate/edit/process/payment_gateway.html index b9e4d47da..42205a02d 100644 --- a/httemplate/edit/process/payment_gateway.html +++ b/httemplate/edit/process/payment_gateway.html @@ -17,7 +17,7 @@ my %options = @options; my $error; if ( $gatewaynum ) { - $error=$new->replace($old); + $error=$new->replace($old, \%options); } else { $error=$new->insert(\%options); $gatewaynum=$new->getfield('gatewaynum'); -- cgit v1.2.1 From 1acbcf55d8cd4675408f8ad3406f431623918c8d Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 7 Feb 2006 11:12:29 +0000 Subject: remove inadvertant extra table statement preventing page from showing up in konq --- httemplate/edit/cust_pay.cgi | 2 -- 1 file changed, 2 deletions(-) diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi index 94fd06ca5..a03a245eb 100755 --- a/httemplate/edit/cust_pay.cgi +++ b/httemplate/edit/cust_pay.cgi @@ -52,8 +52,6 @@ $title .= " against Invoice #$linknum" if $link eq 'invnum';

<% } %> -<%= ntable("#cccccc",2) %> - -- cgit v1.2.1 From b5bf46cd466f032971095ace1d26af0b98921ada Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 7 Feb 2006 13:49:44 +0000 Subject: well, it was already fatal. at least now the error message is better. --- FS/FS/svc_acct.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index c1851d3ce..759d7372e 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -1077,7 +1077,10 @@ sub radius_check { my $password = $self->_password; my $pw_attrib = length($password) <= 12 ? $radius_password : 'Crypt-Password'; $check{$pw_attrib} = $password; - my $cust_pkg = $self->cust_svc->cust_pkg; + my $cust_svc = $self->cust_svc; + die "FATAL: no cust_svc record for svc_acct.svcnum ". $self->svcnum. "\n" + unless $cust_svc; + my $cust_pkg = $cust_svc->cust_pkg; if ( $cust_pkg && $cust_pkg->part_pkg->is_prepaid && $cust_pkg->bill ) { $check{'Expiration'} = time2str('%B %e %Y %T', $cust_pkg->bill ); #http://lists.cistron.nl/pipermail/freeradius-users/2005-January/040184.html } -- cgit v1.2.1 From 7279a60bfb12751895e06dfa4bad60e32a7d7376 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 8 Feb 2006 02:26:23 +0000 Subject: update error message when secrets file cannot be found --- FS/FS/UID.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/UID.pm b/FS/FS/UID.pm index c0c9f7af4..83b652b0f 100644 --- a/FS/FS/UID.pm +++ b/FS/FS/UID.pm @@ -262,7 +262,7 @@ sub getsecrets { $secrets = $1; die "Illegal mapsecrets line for user?!" unless $secrets; ($datasrc, $db_user, $db_pass) = $conf->config($secrets) - or die "Can't get secrets: $!"; + or die "Can't get secrets: $secrets: $!\n"; $FS::Conf::default_dir = $conf_dir. "/conf.$datasrc"; undef $driver_name; ($datasrc, $db_user, $db_pass); -- cgit v1.2.1 From 4c9e6e4863e2045d984933ab0e229b5dd500e1f7 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 8 Feb 2006 03:50:42 +0000 Subject: slightly html-ize the 1.5.8 upgrade instructions --- httemplate/docs/index.html | 4 +- httemplate/docs/upgrade10.html | 116 +++++++++++++++++++++++------------------ 2 files changed, 66 insertions(+), 54 deletions(-) diff --git a/httemplate/docs/index.html b/httemplate/docs/index.html index 7254d76f3..bec62e34a 100644 --- a/httemplate/docs/index.html +++ b/httemplate/docs/index.html @@ -8,11 +8,9 @@

Configuration and setup

    diff --git a/httemplate/docs/upgrade10.html b/httemplate/docs/upgrade10.html index ac2c6238d..8d90ab7a2 100644 --- a/httemplate/docs/upgrade10.html +++ b/httemplate/docs/upgrade10.html @@ -1,76 +1,86 @@ -
    -this is incomplete
    -
    -NOTE: Version numbering has been simplified.  1.5.7 is the version after
    -1.5.0pre6.  It is still a development version - releases with odd numbered 
    -middle parts (NN in x.NN.x) are development versions, like Perl or Linux.
    -
    -If migrating from 1.5.7, see README.1.5.8 instead
    -
    -If migrating from 1.5.0pre6, see README.1.5.7 instead
    -
    -install DBD::Pg 1.32, 1.41 or later (not 1.40) (or, if you're using a Perl version before 5.6, you could try installing DBD::Pg 1.22 with this patch and commenting out the "use DBD::Pg 1.32" at the top of DBIx/DBSchema/DBD/Pg.pm)
    -install DBIx::DBSchema 0.27 (or later)
    -  (if you are running Pg version 7.2.x or earlier, install at least
    -   DBIx::DBSchema 0.29)
    -install Net::SSH 0.08
    -install HTML::Widgets::SelectLayers 0.05
    -install Business::CreditCard 0.28
    -
    -- If using Apache::ASP, add PerlSetVar RequestBinaryRead Off and PerlSetVar IncludesDir /your/freeside/document/root/ to your Apache configuration and make sure you are using Apache::ASP minimum version 2.55.
    -- In httpd.conf, change <Files ~ \.cgi> to  <Files ~ (\.cgi|\.html)>
    -- In httpd.conf, change AddHandler perl-script .cgi or SetHandler perl-script to AddHandler perl-script .cgi .html
    -
    -install NetAddr::IP, Chart::Base, Locale::SubCountry, Text::CSV_XS, 
    +
    +  Upgrading to 1.5.8
    +
    +
    +

    Upgrading to 1.5.8 from 1.4.1 or 1.4.2

    + +Note: Version numbering has been simplified. 1.5.7 and 1.5.8 are the +versions following 1.5.0pre6. They are still development versions - releases +with odd numbered middle parts (NN in x.NN.x) are development versions, like +Perl or Linux. + + +
    + +
      +
    • If migrating from 1.5.0pre6, see README.1.5.7 instead +
    • If migrating from 1.5.7, see README.1.5.8 instead +
    • install DBD::Pg 1.32, 1.41 or later (not 1.40) (or, if you're using a Perl version before 5.6, you could try installing DBD::Pg 1.22 with this patch and commenting out the "use DBD::Pg 1.32" at the top of DBIx/DBSchema/DBD/Pg.pm) +
    • install DBIx::DBSchema 0.27 (or later) (if you are running Pg version 7.2.x or earlier, install at least DBIx::DBSchema 0.29) +
    • install Net::SSH 0.08 or later +
    • install HTML::Widgets::SelectLayers 0.05 or later +
    • install Business::CreditCard 0.28 or later + +
    • If using Apache::ASP, add PerlSetVar RequestBinaryRead Off and PerlSetVar IncludesDir /your/freeside/document/root/ to your Apache configuration and make sure you are using Apache::ASP minimum version 2.55. +
        +
      • In httpd.conf, change <Files ~ \.cgi> to <Files ~ (\.cgi|\.html)> +
      • In httpd.conf, change AddHandler perl-script .cgi or SetHandler perl-script to AddHandler perl-script .cgi .html +
      +
    • install NetAddr::IP, Chart::Base, Locale::SubCountry, Text::CSV_XS, Spreadsheet::WriteExcel, IO-stringy (IO::Scalar), Frontier::RPC (Frontier::RPC2), MIME::Entity (MIME-tools), IPC::Run3, Net::Whois::Raw, JSON and Term::ReadKey +
    • Apply the following changes to your database: +
       INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 20, 'svc_external-id', 'en_US', 'External ID' );
       INSERT INTO msgcat ( msgnum, msgcode, locale, msg ) VALUES ( 21, 'svc_external-title', 'en_US', 'Title' );
       
       DROP INDEX cust_bill_pkg1;
      +
      -On recent Pg versions: - +
    • On recent Pg versions: +
       ALTER TABLE cust_main ALTER COLUMN payinfo varchar(512) NULL;
       ALTER TABLE h_cust_main ALTER COLUMN payinfo varchar(512) NULL;
      +
      +On older Pg versions that don't support altering columns directly, you will need to dump the database, edit the schema definitions in the dump file, and reload. -Or on older Pg versions that don't support altering columns directly: -(dump database, edit & reload) - -On recent Pg versions: - +
    • On recent Pg versions: +
       ALTER TABLE svc_forward ALTER COLUMN srcsvc DROP NOT NULL;
       ALTER TABLE h_svc_forward ALTER COLUMN srcsvc DROP NOT NULL;
       ALTER TABLE svc_forward ALTER COLUMN dstsvc DROP NOT NULL;
       ALTER TABLE h_svc_forward ALTER COLUMN dstsvc DROP NOT NULL;
       ALTER TABLE cust_main ALTER COLUMN zip DROP NOT NULL;
       ALTER TABLE h_cust_main ALTER COLUMN zip DROP NOT NULL;
      -
      +
      Or on Pg versions that don't support DROP NOT NULL (tested on 7.1 and 7.2 so far): +
       UPDATE pg_attribute SET attnotnull = FALSE WHERE ( attname = 'srcsvc' OR attname = 'dstsvc' ) AND ( attrelid = ( SELECT oid FROM pg_class WHERE relname = 'svc_forward' ) OR attrelid = ( SELECT oid FROM pg_class WHERE relname = 'h_svc_forward' ) );
       UPDATE pg_attribute SET attnotnull = FALSE WHERE ( attname = 'zip' ) AND ( attrelid = ( SELECT oid FROM pg_class WHERE relname = 'cust_main' ) OR attrelid = ( SELECT oid FROM pg_class WHERE relname = 'h_cust_main' ) );
      +
      -If you created your database with a version before 1.4.2, dump database, edit: -- cust_main and h_cust_main: increase otaker from 8 to 32 -- cust_main and h_cust_main: change ss from char(11) to varchar(11) ( "character(11)" to "character varying(11)" ) -- cust_credit and h_cust_credit: increase otaker from 8 to 32 -- cust_pkg and h_cust_pkg: increase otaker from 8 to 32 -- cust_refund and h_cust_refund: increase otaker from 8 to 32 -- domain_record and h_domain_record: increase reczone from 80 to 255 -- domain_record and h_domain_record: change rectype from char to varchar ( "character(5)" to "character varying(5)" ) -- domain_record and h_domain_record: increase recdata from 80 to 255 -then reload - -mandatory again: - -make install-perl-modules to install the new libraries and CLI utilities -run "freeside-upgrade username" to create the remaining new tables and columns - -optionally: - +
    • If you created your database with a version before 1.4.2, dump database, edit the following, then reload: +
        +
      • cust_main and h_cust_main: increase otaker from 8 to 32 +
      • cust_main and h_cust_main: change ss from char(11) to varchar(11) ( "character(11)" to "character varying(11)" ) +
      • cust_credit and h_cust_credit: increase otaker from 8 to 32 +
      • cust_pkg and h_cust_pkg: increase otaker from 8 to 32 +
      • cust_refund and h_cust_refund: increase otaker from 8 to 32 +
      • domain_record and h_domain_record: increase reczone from 80 to 255 +
      • domain_record and h_domain_record: change rectype from char to varchar ( "character(5)" to "character varying(5)" ) +
      • domain_record and h_domain_record: increase recdata from 80 to 255 +
      + +
    • make install-perl-modules to install the new libraries and CLI utilities +
    • run "freeside-upgrade username" to create the remaining new tables and columns + +
    • optionally: +
       CREATE INDEX cust_main4 ON cust_main ( daytime );
       CREATE INDEX cust_main5 ON cust_main ( night );
       CREATE INDEX cust_main6 ON cust_main ( fax );
      @@ -89,5 +99,9 @@ CREATE INDEX cust_pay4 ON cust_pay (_date);
       CREATE INDEX part_referral1 ON part_referral ( disabled );
       CREATE INDEX part_pkg2 ON part_pkg ( promo_code );
       CREATE INDEX h_part_pkg2 ON h_part_pkg ( promo_code );
      -
       
      + +
    + + + -- cgit v1.2.1 From eb035e51ad8df6b3301c81679a9f4b51b6ea4c4d Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 8 Feb 2006 03:50:53 +0000 Subject: remove ancient upgrade instructions --- httemplate/docs/upgrade7.html | 24 --- httemplate/docs/upgrade8.html | 394 ------------------------------------------ 2 files changed, 418 deletions(-) delete mode 100644 httemplate/docs/upgrade7.html delete mode 100644 httemplate/docs/upgrade8.html diff --git a/httemplate/docs/upgrade7.html b/httemplate/docs/upgrade7.html deleted file mode 100644 index d9dcfe2ae..000000000 --- a/httemplate/docs/upgrade7.html +++ /dev/null @@ -1,24 +0,0 @@ - - Upgrading to 1.3.1 - - -

    Upgrading to 1.3.1 from 1.3.0

    -
      -
    • If migrating from 1.0.0, see these instructions first. -
    • If migrating from less than 1.1.4, see these instructions first. -
    • If migrating from less than 1.2.0, see these instructions first. -
    • If migrating from less than 1.2.2, see these instructions first. -
    • If migrating from less than 1.2.3, see these instructions first. -
    • If migrating from less than 1.3.0, see these instructions first. -
    • Back up your data and current Freeside installation. -
    • Copy or symlink htdocs to the new copy. -
    • Change to the FS directory in the new tarball, and build and install the - Perl modules: -
      -$ cd FS/
      -$ perl Makefile.PL
      -$ make
      -$ su
      -# make install UNINST=1
      -
    • Run bin/dbdef-create. - diff --git a/httemplate/docs/upgrade8.html b/httemplate/docs/upgrade8.html deleted file mode 100644 index 9ca7cb7b9..000000000 --- a/httemplate/docs/upgrade8.html +++ /dev/null @@ -1,394 +0,0 @@ - - Upgrading to 1.4.0 - - -

      Upgrading to 1.4.0 from 1.3.1

      - - - - - - - - - -
      Apache::ASPMason
        -
      • Run make aspdocs -
      • Copy aspdocs/ to your web server's document space. -
      • Create a Global directory, such as /usr/local/etc/freeside/asp-global/ -
      • Copy htetc/global.asa to the Global directory. -
      • Configure Apache for the Global directory and to execute .cgi files using Apache::ASP. For example: -
        -<Directory /usr/local/apache/htdocs/freeside-asp>
        -<Files ~ (\.cgi)>
        -AddHandler perl-script .cgi
        -PerlHandler Apache::ASP
        -</Files>
        -<Perl>
        -$MLDBM::RemoveTaint = 1;
        -</Perl>
        -PerlSetVar Global /usr/local/etc/freeside/asp-global/
        -</Directory>
        -
        -
        -
      • (use version 1.0x - Freeside is not yet compatible with version 1.1x) -
      • Run make masondocs -
      • Copy masondocs/ to your web server's document space. -
      • Copy htetc/handler.pl to your web server's configuration directory. -
      • Edit handler.pl and set an appropriate data_dir, such as /usr/local/etc/freeside/mason-data -
      • Configure Apache to use the handler.pl file and to execute .cgi files using HTML::Mason. For example: -
        -<Directory /usr/local/apache/htdocs/freeside-mason>
        -<Files ~ (\.cgi)>
        -AddHandler perl-script .cgi
        -PerlHandler HTML::Mason
        -</Files>
        -<Perl>
        -require "/usr/local/apache/conf/handler.pl";
        -</Perl>
        -</Directory>
        -
        -
      -
        -
      • Build and install the Perl modules: -
        -$ su
        -# make install-perl-modules
        -
      • Apply the following changes to your database: -
        -CREATE TABLE svc_forward (
        -  svcnum int NOT NULL,
        -  srcsvc int NOT NULL,
        -  dstsvc int NOT NULL,
        -  dst varchar(80),
        -  PRIMARY KEY (svcnum)
        -);
        -ALTER TABLE part_svc ADD svc_forward__srcsvc varchar(80) NULL;
        -ALTER TABLE part_svc ADD svc_forward__srcsvc_flag char(1) NULL;
        -ALTER TABLE part_svc ADD svc_forward__dstsvc varchar(80) NULL;
        -ALTER TABLE part_svc ADD svc_forward__dstsvc_flag char(1) NULL;
        -ALTER TABLE part_svc ADD svc_forward__dst varchar(80) NULL;
        -ALTER TABLE part_svc ADD svc_forward__dst_flag char(1) NULL;
        -
        -CREATE TABLE cust_credit_bill (
        -  creditbillnum int primary key,
        -  crednum int not null,
        -  invnum int not null,
        -  _date int not null,
        -  amount decimal(10,2) not null
        -);
        -
        -CREATE TABLE cust_bill_pay (
        -  billpaynum int primary key,
        -  invnum int not null,
        -  paynum int not null,
        -  _date int not null,
        -  amount decimal(10,2) not null
        -);
        -
        -CREATE TABLE cust_credit_refund (
        -  creditrefundnum int primary key,
        -  crednum int not null,
        -  refundnum int not null,
        -  _date int not null,
        -  amount decimal(10,2) not null
        -);
        -
        -CREATE TABLE part_svc_column (
        -  columnnum int primary key,
        -  svcpart int not null,
        -  columnname varchar(64) not null,
        -  columnvalue varchar(80) null,
        -  columnflag char(1) null
        -);
        -
        -CREATE TABLE queue (
        -  jobnum int primary key,
        -  job text not null,
        -  _date int not null,
        -  status varchar(80) not null,
        -  statustext text null,
        -  svcnum int null
        -);
        -CREATE INDEX queue1 ON queue ( svcnum );
        -CREATE INDEX queue2 ON queue ( status );
        -
        -CREATE TABLE queue_arg (
        -  argnum int primary key,
        -  jobnum int not null,
        -  arg text null
        -);
        -CREATE INDEX queue_arg1 ON queue_arg ( jobnum );
        -
        -CREATE TABLE queue_depend (
        -  dependnum int primary key,
        -  jobnum int not null,
        -  depend_jobnum int not null
        -);
        -CREATE INDEX queue_depend1 ON queue_depend ( jobnum );
        -CREATE INDEX queue_depend2 ON queue_depend ( depend_jobnum );
        -
        -CREATE TABLE part_pop_local (
        -  localnum int primary key,
        -  popnum int not null,
        -  city varchar(80) null,
        -  state char(2) null,
        -  npa char(3) not null,
        -  nxx char(3) not null
        -);
        -CREATE UNIQUE INDEX part_pop_local1 ON part_pop_local ( npa, nxx );
        -
        -CREATE TABLE cust_bill_event (
        -  eventnum int primary key,
        -  invnum int not null,
        -  eventpart int not null,
        -  _date int not null,
        -  status varchar(80) not null, 
        -  statustext text
        -);
        -CREATE UNIQUE INDEX cust_bill_event1 ON cust_bill_event ( eventpart, invnum );
        -CREATE INDEX cust_bill_event2 ON cust_bill_event ( invnum );
        -
        -CREATE TABLE part_bill_event (
        -  eventpart int primary key,
        -  payby char(4) not null,
        -  event varchar(80) not null,
        -  eventcode text null,
        -  seconds int null,
        -  weight int not null,
        -  plan varchar(80) null,
        -  plandata text null,
        -  disabled char(1) null
        -);
        -CREATE INDEX part_bill_event1 ON part_bill_event ( payby );
        -
        -CREATE TABLE export_svc (
        -  exportsvcnum int primary key,
        -  exportnum int not null,
        -  svcpart int not null
        -);
        -CREATE UNIQUE INDEX export_svc1 ON export_svc ( exportnum, svcpart );
        -CREATE INDEX export_svc2 ON export_svc ( exportnum );
        -CREATE INDEX export_svc3 ON export_svc ( svcpart );
        -
        -CREATE TABLE part_export (
        -  exportnum int primary key,
        -  machine varchar(80) not null,
        -  exporttype varchar(80) not null,
        -  nodomain char(1) NULL
        -);
        -CREATE INDEX part_export1 ON part_export ( machine );
        -CREATE INDEX part_export2 ON part_export ( exporttype );
        -
        -CREATE TABLE part_export_option (
        -  optionnum int primary key,
        -  exportnum int not null,
        -  optionname varchar(80) not null,
        -  optionvalue text NULL
        -);
        -CREATE INDEX part_export_option1 ON part_export_option ( exportnum );
        -CREATE INDEX part_export_option2 ON part_export_option ( optionname );
        -
        -CREATE TABLE radius_usergroup (
        -  usergroupnum int primary key,
        -  svcnum int not null,
        -  groupname varchar(80) not null
        -);
        -CREATE INDEX radius_usergroup1 ON radius_usergroup ( svcnum );
        -CREATE INDEX radius_usergroup2 ON radius_usergroup ( groupname );
        -
        -CREATE TABLE msgcat (
        -  msgnum int primary key,
        -  msgcode varchar(80) not null,
        -  locale varchar(16) not null,
        -  msg text not null
        -);
        -CREATE INDEX msgcat1 ON msgcat ( msgcode, locale );
        -
        -CREATE TABLE cust_tax_exempt (
        -  exemptnum int primary key,
        -  custnum int not null,
        -  taxnum int not null,
        -  year int not null,
        -  month int not null,
        -  amount decimal(10,2)
        -);
        -CREATE UNIQUE INDEX cust_tax_exempt1 ON cust_tax_exempt ( taxnum, year, month );
        -
        -ALTER TABLE svc_acct ADD domsvc integer NULL;
        -ALTER TABLE part_svc ADD svc_acct__domsvc varchar(80) NULL;
        -ALTER TABLE part_svc ADD svc_acct__domsvc_flag char(1) NULL;
        -ALTER TABLE svc_domain ADD catchall integer NULL;
        -ALTER TABLE cust_main ADD referral_custnum integer NULL;
        -ALTER TABLE cust_main ADD comments text NULL;
        -ALTER TABLE cust_pay ADD custnum integer;
        -ALTER TABLE cust_pay_batch ADD paybatchnum integer;
        -ALTER TABLE cust_refund ADD custnum integer;
        -ALTER TABLE cust_pkg ADD manual_flag char(1) NULL;
        -ALTER TABLE part_pkg ADD plan varchar(80) NULL;
        -ALTER TABLE part_pkg ADD plandata text NULL;
        -ALTER TABLE part_pkg ADD setuptax char(1) NULL;
        -ALTER TABLE part_pkg ADD recurtax char(1) NULL;
        -ALTER TABLE part_pkg ADD disabled char(1) NULL;
        -ALTER TABLE part_svc ADD disabled char(1) NULL;
        -ALTER TABLE cust_bill ADD closed char(1) NULL;
        -ALTER TABLE cust_pay ADD closed char(1) NULL;
        -ALTER TABLE cust_credit ADD closed char(1) NULL;
        -ALTER TABLE cust_refund ADD closed char(1) NULL;
        -ALTER TABLE cust_bill_event ADD status varchar(80);
        -ALTER TABLE cust_bill_event ADD statustext text NULL;
        -ALTER TABLE svc_acct ADD sec_phrase varchar(80) NULL;
        -ALTER TABLE part_svc ADD svc_acct__sec_phrase varchar(80) NULL;
        -ALTER TABLE part_svc ADD svc_acct__sec_phrase_flag char(1) NULL;
        -ALTER TABLE part_pkg ADD taxclass varchar(80) NULL;
        -ALTER TABLE cust_main_county ADD taxclass varchar(80) NULL;
        -ALTER TABLE cust_main_county ADD exempt_amount decimal(10,2);
        -CREATE INDEX cust_main3 ON cust_main ( referral_custnum );
        -CREATE INDEX cust_credit_bill1 ON cust_credit_bill ( crednum );
        -CREATE INDEX cust_credit_bill2 ON cust_credit_bill ( invnum );
        -CREATE INDEX cust_bill_pay1 ON cust_bill_pay ( invnum );
        -CREATE INDEX cust_bill_pay2 ON cust_bill_pay ( paynum );
        -CREATE INDEX cust_credit_refund1 ON cust_credit_refund ( crednum );
        -CREATE INDEX cust_credit_refund2 ON cust_credit_refund ( refundnum );
        -CREATE UNIQUE INDEX cust_pay_batch_pkey ON cust_pay_batch ( paybatchnum );
        -CREATE UNIQUE INDEX part_svc_column1 ON part_svc_column ( svcpart, columnname );
        -CREATE INDEX cust_pay2 ON cust_pay ( paynum );
        -CREATE INDEX cust_pay3 ON cust_pay ( custnum );
        -CREATE INDEX cust_pay4 ON cust_pay ( paybatch );
        -
        - -
      • If you are using PostgreSQL, apply the following changes to your database: -
        -CREATE UNIQUE INDEX agent_pkey ON agent ( agentnum );
        -CREATE UNIQUE INDEX agent_type_pkey ON agent_type ( typenum );
        -CREATE UNIQUE INDEX cust_bill_pkey ON cust_bill ( invnum );
        -CREATE UNIQUE INDEX cust_credit_pkey ON cust_credit ( crednum );
        -CREATE UNIQUE INDEX cust_main_pkey ON cust_main ( custnum );
        -CREATE UNIQUE INDEX cust_main_county_pkey ON cust_main_county ( taxnum );
        -CREATE UNIQUE INDEX cust_main_invoice_pkey ON cust_main_invoice ( destnum );
        -CREATE UNIQUE INDEX cust_pay_pkey ON cust_pay ( paynum );
        -CREATE UNIQUE INDEX cust_pkg_pkey ON cust_pkg ( pkgnum );
        -CREATE UNIQUE INDEX cust_refund_pkey ON cust_refund ( refundnum );
        -CREATE UNIQUE INDEX cust_svc_pkey ON cust_svc ( svcnum );
        -CREATE UNIQUE INDEX domain_record_pkey ON domain_record ( recnum );
        -CREATE UNIQUE INDEX nas_pkey ON nas ( nasnum );
        -CREATE UNIQUE INDEX part_pkg_pkey ON part_pkg ( pkgpart );
        -CREATE UNIQUE INDEX part_referral_pkey ON part_referral ( refnum );
        -CREATE UNIQUE INDEX part_svc_pkey ON part_svc ( svcpart );
        -CREATE UNIQUE INDEX port_pkey ON port ( portnum );
        -CREATE UNIQUE INDEX prepay_credit_pkey ON prepay_credit ( prepaynum );
        -CREATE UNIQUE INDEX session_pkey ON session ( sessionnum );
        -CREATE UNIQUE INDEX svc_acct_pkey ON svc_acct ( svcnum );
        -CREATE UNIQUE INDEX svc_acct_pop_pkey ON svc_acct_pop ( popnum );
        -CREATE UNIQUE INDEX svc_acct_sm_pkey ON svc_acct_sm ( svcnum );
        -CREATE UNIQUE INDEX svc_domain_pkey ON svc_domain ( svcnum );
        -CREATE UNIQUE INDEX svc_www_pkey ON svc_www ( svcnum );
        -
        -
      • If you wish to enable service/shipping addresses, apply the following - changes to your database: -
        -ALTER TABLE cust_main ADD COLUMN ship_last varchar(80) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_first varchar(80) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_company varchar(80) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_address1 varchar(80) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_address2 varchar(80) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_city varchar(80) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_county varchar(80) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_state varchar(80) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_zip varchar(10) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_country char(2) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_daytime varchar(20) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_night varchar(20) NULL;
        -ALTER TABLE cust_main ADD COLUMN ship_fax varchar(12) NULL;
        -CREATE INDEX cust_main4 ON cust_main ( ship_last );
        -CREATE INDEX cust_main5 ON cust_main ( ship_company );
        -
        -
      • If you are using the signup server, reinstall it according to the instructions. The 1.3.x signup server is not compatible with 1.4.x. -
      • Run bin/dbdef-create username -
      • If you have svc_acct_sm records or service definitions: -
          -
        • Create a service definition with table svc_forward -
        • Run bin/fs-migrate-svc_acct_sm username -
        -
      • Or if you just have svc_acct records: -
          -
        • Order and provision a package for your default domain and note down the Service # or svcnum. -
        • UPDATE svc_acct SET domsvc = svcnum -
        • Update your service definitions to have default (or fixed) domsvc. -
        -
      • Run bin/fs-migrate-payrefusername -
      • Run bin/fs-migrate-part_svcusername -
      • After running bin/fs-migrate-payref, apply the following changes to your database: - -
        PostgreSQLMySQL, others
        -
        -CREATE TABLE cust_pay_temp (
        -  paynum int primary key,
        -  custnum int not null,
        -  paid decimal(10,2) not null,
        -  _date int null,
        -  payby char(4) not null,
        -  payinfo varchar(16) null,
        -  paybatch varchar(80) null,
        -  closed char(1) null
        -);
        -INSERT INTO cust_pay_temp SELECT paynum, custnum, paid, _date, payby, payinfo, paybatch, closed FROM cust_pay;
        -DROP TABLE cust_pay;
        -ALTER TABLE cust_pay_temp RENAME TO cust_pay;
        -CREATE UNIQUE INDEX cust_pay1 ON cust_pay (paynum);
        -CREATE TABLE cust_refund_temp (
        -  refundnum int primary key,
        -  custnum int not null,
        -  _date int null,
        -  refund decimal(10,2) not null,
        -  otaker varchar(8) not null,
        -  reason varchar(80) not null,
        -  payby char(4) not null,
        -  payinfo varchar(16) null,
        -  paybatch varchar(80) null,
        -  closed char(1) null
        -);
        -INSERT INTO cust_refund_temp SELECT refundnum, custnum, _date, refund, otaker, reason, payby, payinfo, '', closed from cust_refund;
        -DROP TABLE cust_refund;
        -ALTER TABLE cust_refund_temp RENAME TO cust_refund;
        -CREATE UNIQUE INDEX cust_refund1 ON cust_refund (refundnum);
        -
        -
        -
        -ALTER TABLE cust_pay DROP COLUMN invnum;
        -ALTER TABLE cust_refund DROP COLUMN crednum;
        -
        -
        -
      • IMPORTANT: After applying the second set of database changes, run bin/dbdef-create username again. -
      • IMPORTANT: run bin/create-history-tables username -
      • IMPORTANT: After running bin/create-history-tables, run bin/dbdef-create username again. -
      • As the freeside UNIX user, run bin/populate-msgcat username to populate the message catalog - -
      • set the locale configuration value to en_US. -
      • the mxmachines, nsmachines, arecords and cnamerecords configuration values have been deprecated. Set the defaultrecords configuration value instead. -
      • Create the `/usr/local/etc/freeside/cache.datasrc' directory - (owned by the freeside user). -
      • freeside-queued was installed with the Perl modules. Start it now and ensure that is run upon system startup. -
      • Set appropriate invoice events for your site. At the very least, you'll want to set some invoice events "After 0 days": a BILL invoice event to print invoices, a CARD invoice event to batch or run cards real-time, and a COMP invoice event to "pay" complimentary customers. If you were using the -i option to freeside-bill it should be removed. -
      • Use freeside-daily instead of freeside-bill. -
      • If you would like Freeside to notify your customers when their credit - cards and other billing arrangements are about to expire, arrange for - freeside-expiration-alerter to be run daily by cron or similar - facility. The message it sends can be configured from the - Configuration choice of the main menu as alerter_template. -
      • Export has been rewritten. If you were using the icradiusmachines, - icradius_mysqldest, icradius_mysqlsource, or icradius_secrets files, add - an appropriate "sqlradius" export to all relevant Service Definitions - instead. Use MySQL replication or - point the "sqlradius" export directly at your external ICRADIUS or FreeRADIUS - database (or through an SSL-necrypting proxy...) -
      - -- cgit v1.2.1 From 37220994fec0ec4f8913be99f66aa082c78897a4 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 8 Feb 2006 22:53:18 +0000 Subject: don't leave ssh zombies around either --- FS/bin/freeside-selfservice-server | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/FS/bin/freeside-selfservice-server b/FS/bin/freeside-selfservice-server index c73349a60..6026fd18f 100644 --- a/FS/bin/freeside-selfservice-server +++ b/FS/bin/freeside-selfservice-server @@ -1,7 +1,7 @@ #!/usr/bin/perl -w use strict; -use vars qw( $Debug %kids $kids $max_kids $ssh_pid $keepalives ); +use vars qw( $Debug %kids $kids $max_kids $ssh_pid %old_ssh_pid $keepalives ); use subs qw( lock_write unlock_write myshutdown usage ); use Fcntl qw(:flock); use POSIX qw(:sys_wait_h); @@ -102,6 +102,7 @@ while (1) { if ( $ssh_pid ) { warn "sending TERM signal to ssh process $ssh_pid\n" if $Debug; kill 'TERM', $ssh_pid; + $old_ssh_pid{$ssh_pid} = 1; $ssh_pid = 0; } last; @@ -180,6 +181,10 @@ sub reap_kids { delete $kids{$kid}; } } + + foreach my $pid ( keys %old_ssh_pid ) { + waitpid($pid, WNOHANG) and delete $old_ssh_pid{$pid}; + } #warn "done reaping\n"; } -- cgit v1.2.1 From c8a6843a92a556315b85e6b3f4ed1bebdfd2e35d Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 9 Feb 2006 07:18:08 +0000 Subject: update jscalendar --- httemplate/elements/calendar-en.js | 10 +- httemplate/elements/calendar-setup.js | 45 ++- httemplate/elements/calendar-win2k-2.css | 1 + httemplate/elements/calendar.js | 653 ++++++++++++++++++------------- httemplate/elements/calendar_stripped.js | 12 +- 5 files changed, 419 insertions(+), 302 deletions(-) diff --git a/httemplate/elements/calendar-en.js b/httemplate/elements/calendar-en.js index e9d6a222e..0dbde793d 100644 --- a/httemplate/elements/calendar-en.js +++ b/httemplate/elements/calendar-en.js @@ -1,7 +1,7 @@ // ** I18N // Calendar EN language -// Author: Mihai Bazon, +// Author: Mihai Bazon, // Encoding: any // Distributed under the same terms as the calendar itself. @@ -43,6 +43,10 @@ Calendar._SDN = new Array "Sat", "Sun"); +// First day of the week. "0" means display Sunday first, "1" means display +// Monday first, etc. +Calendar._FD = 0; + // full month names Calendar._MN = new Array ("January", @@ -79,8 +83,8 @@ 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" + +"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-) +"For latest version visit: http://www.dynarch.com/projects/calendar/\n" + "Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." + "\n\n" + "Date selection:\n" + diff --git a/httemplate/elements/calendar-setup.js b/httemplate/elements/calendar-setup.js index 55e22b933..b27d9bed0 100644 --- a/httemplate/elements/calendar-setup.js +++ b/httemplate/elements/calendar-setup.js @@ -19,7 +19,7 @@ * than modifying calendar.js itself). */ -// $Id: calendar-setup.js,v 1.4 2004-09-22 11:04:41 ivan Exp $ +// $Id: calendar-setup.js,v 1.5 2006-02-09 07:18:08 ivan Exp $ /** * This function "patches" an input field (or other element) to use a calendar @@ -71,7 +71,8 @@ Calendar.setup = function (params) { param_default("singleClick", true); param_default("disableFunc", null); param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined - param_default("firstDay", 0); // defaults to "Sunday" first + param_default("dateText", null); + param_default("firstDay", null); param_default("align", "Br"); param_default("range", [1900, 2999]); param_default("weekNumbers", true); @@ -88,6 +89,7 @@ Calendar.setup = function (params) { param_default("position", null); param_default("cache", false); param_default("showOthers", false); + param_default("multiple", null); var tmp = ["inputField", "displayArea", "button"]; for (var i in tmp) { @@ -95,7 +97,7 @@ Calendar.setup = function (params) { params[tmp[i]] = document.getElementById(params[tmp[i]]); } } - if (!(params.flat || params.inputField || params.displayArea || params.button)) { + if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) { alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code"); return false; } @@ -103,13 +105,6 @@ Calendar.setup = function (params) { function onSelect(cal) { var p = cal.params; var update = (cal.dateClicked || p.electric); - if (update && p.flat) { - if (typeof p.flatCallback == "function") - p.flatCallback(cal); - else - alert("No flatCallback given -- doing nothing."); - return false; - } if (update && p.inputField) { p.inputField.value = cal.date.print(p.ifFormat); if (typeof p.inputField.onchange == "function") @@ -117,10 +112,14 @@ Calendar.setup = function (params) { } if (update && p.displayArea) p.displayArea.innerHTML = cal.date.print(p.daFormat); - if (update && p.singleClick && cal.dateClicked) - cal.callCloseHandler(); if (update && typeof p.onUpdate == "function") p.onUpdate(cal); + if (update && p.flat) { + if (typeof p.flatCallback == "function") + p.flatCallback(cal); + } + if (update && p.singleClick && cal.dateClicked) + cal.callCloseHandler(); }; if (params.flat != null) { @@ -131,12 +130,20 @@ Calendar.setup = function (params) { return false; } var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect); + cal.showsOtherMonths = params.showOthers; 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.setDateStatusHandler(params.dateStatusFunc); + cal.getDateText = params.dateText; + if (params.ifFormat) { + cal.setDateFormat(params.ifFormat); + } + if (params.inputField && typeof params.inputField.value == "string") { + cal.parseDate(params.inputField.value); + } cal.create(params.flat); cal.show(); return false; @@ -148,6 +155,8 @@ Calendar.setup = function (params) { var dateFmt = params.inputField ? params.ifFormat : params.daFormat; var mustCreate = false; var cal = window.calendar; + if (dateEl) + params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt); if (!(cal && params.cache)) { window.calendar = cal = new Calendar(params.firstDay, params.date, @@ -162,15 +171,23 @@ Calendar.setup = function (params) { cal.setDate(params.date); cal.hide(); } + if (params.multiple) { + cal.multiple = {}; + for (var i = params.multiple.length; --i >= 0;) { + var d = params.multiple[i]; + var ds = d.print("%Y%m%d"); + cal.multiple[ds] = d; + } + } cal.showsOtherMonths = params.showOthers; cal.yearStep = params.step; cal.setRange(params.range[0], params.range[1]); cal.params = params; cal.setDateStatusHandler(params.dateStatusFunc); + cal.getDateText = params.dateText; cal.setDateFormat(dateFmt); if (mustCreate) cal.create(); - cal.parseDate(dateEl.value || dateEl.innerHTML); cal.refresh(); if (!params.position) cal.showAtElement(params.button || params.displayArea || params.inputField, params.align); @@ -178,4 +195,6 @@ Calendar.setup = function (params) { cal.showAt(params.position[0], params.position[1]); return false; }; + + return cal; }; diff --git a/httemplate/elements/calendar-win2k-2.css b/httemplate/elements/calendar-win2k-2.css index 6001cfaa4..6f37b7dcd 100644 --- a/httemplate/elements/calendar-win2k-2.css +++ b/httemplate/elements/calendar-win2k-2.css @@ -206,6 +206,7 @@ background: #e4d8e0; font-size: 90%; padding: 1px; + z-index: 100; } .calendar .combo .label, diff --git a/httemplate/elements/calendar.js b/httemplate/elements/calendar.js index ec18d80ce..f5c74f608 100644 --- a/httemplate/elements/calendar.js +++ b/httemplate/elements/calendar.js @@ -1,16 +1,18 @@ -/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ - * ------------------------------------------------------------------ +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo + * ----------------------------------------------------------- * - * The DHTML Calendar, version 0.9.6 "Keep cool but don't freeze" + * The DHTML Calendar, version 1.0 "It is happening again" * * Details and latest version at: - * http://dynarch.com/mishoo/calendar.epl + * www.dynarch.com/projects/calendar + * + * This script is developed by Dynarch.com. Visit us at www.dynarch.com. * * 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.4 2004-09-22 11:04:41 ivan Exp $ +// $Id: calendar.js,v 1.5 2006-02-09 07:18:08 ivan Exp $ /** The Calendar object constructor. */ Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { @@ -18,6 +20,8 @@ Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { this.activeDiv = null; this.currentDateEl = null; this.getDateStatus = null; + this.getDateToolTip = null; + this.getDateText = null; this.timeout = null; this.onSelected = onSelected || null; this.onClose = onClose || null; @@ -29,13 +33,15 @@ Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) { this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"]; this.isPopup = true; this.weekNumbers = true; - this.firstDayOfWeek = firstDayOfWeek; // 0 for Sunday, 1 for Monday, etc. + this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc. this.showsOtherMonths = false; this.dateStr = dateStr; this.ar_days = null; this.showsTime = false; this.time24 = true; this.yearStep = 2; + this.hiliteToday = true; + this.multiple = null; // HTML elements this.table = null; this.element = null; @@ -146,20 +152,19 @@ Calendar.addClass = function(el, className) { el.className += " " + className; }; +// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately. Calendar.getElement = function(ev) { - if (Calendar.is_ie) { - return window.event.srcElement; - } else { - return ev.currentTarget; - } + var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget; + while (f.nodeType != 1 || /^div$/i.test(f.tagName)) + f = f.parentNode; + return f; }; Calendar.getTargetElement = function(ev) { - if (Calendar.is_ie) { - return window.event.srcElement; - } else { - return ev.target; - } + var f = Calendar.is_ie ? window.event.srcElement : ev.target; + while (f.nodeType != 1) + f = f.parentNode; + return f; }; Calendar.stopEvent = function(ev) { @@ -295,7 +300,7 @@ Calendar.showYearsCombo = function (fwd) { var show = false; for (var i = 12; i > 0; --i) { if (Y >= cal.minYear && Y <= cal.maxYear) { - yr.firstChild.data = Y; + yr.innerHTML = Y; yr.year = Y; yr.style.display = "block"; show = true; @@ -416,7 +421,7 @@ Calendar.tableMouseOver = function (ev) { } else if ( ++i >= range.length ) i = 0; var newval = range[i]; - el.firstChild.data = newval; + el.innerHTML = newval; cal.onUpdateTime(); } @@ -504,7 +509,7 @@ Calendar.dayMouseDown = function(ev) { Calendar._C = cal; if (el.navtype != 300) with (Calendar) { if (el.navtype == 50) { - el._current = el.firstChild.data; + el._current = el.innerHTML; addEvent(document, "mousemove", tableMouseOver); } else addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver); @@ -541,7 +546,7 @@ Calendar.dayMouseOver = function(ev) { if (el.ttip.substr(0, 1) == "_") { el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1); } - el.calendar.tooltips.firstChild.data = el.ttip; + el.calendar.tooltips.innerHTML = el.ttip; } if (el.navtype != 300) { Calendar.addClass(el, "hilite"); @@ -555,14 +560,13 @@ Calendar.dayMouseOver = function(ev) { Calendar.dayMouseOut = function(ev) { with (Calendar) { var el = getElement(ev); - if (isRelated(el, ev) || _C || el.disabled) { + if (isRelated(el, ev) || _C || el.disabled) return false; - } removeClass(el, "hilite"); - if (el.caldate) { + if (el.caldate) removeClass(el.parentNode, "rowhilite"); - } - el.calendar.tooltips.firstChild.data = _TT["SEL_DATE"]; + if (el.calendar) + el.calendar.tooltips.innerHTML = _TT["SEL_DATE"]; return stopEvent(ev); } }; @@ -577,17 +581,23 @@ Calendar.cellClick = function(el, ev) { 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; + if (cal.currentDateEl) { + Calendar.removeClass(cal.currentDateEl, "selected"); + Calendar.addClass(el, "selected"); + closing = (cal.currentDateEl == el); + if (!closing) { + cal.currentDateEl = el; + } } - cal.date = new Date(el.caldate); + cal.date.setDateOnly(el.caldate); date = cal.date; - newdate = true; + var other_month = !(cal.dateClicked = !el.otherMonth); + if (!other_month && !cal.currentDateEl) + cal._toggleMultipleDate(new Date(date)); + else + newdate = !el.disabled; // a date was clicked - if (!(cal.dateClicked = !el.otherMonth)) + if (other_month) cal._init(cal.firstDayOfWeek, date); } else { if (el.navtype == 200) { @@ -595,7 +605,9 @@ Calendar.cellClick = function(el, ev) { cal.callCloseHandler(); return; } - date = (el.navtype == 0) ? new Date() : new Date(cal.date); + date = new Date(cal.date); + if (el.navtype == 0) + date.setDateOnly(new Date()); // TODAY // 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. @@ -622,7 +634,7 @@ Calendar.cellClick = function(el, ev) { 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" + + "and send it back to to get it into the distribution ;-)\n\n" + "Thank you!\n" + "http://dynarch.com/mishoo/calendar.epl\n"; } @@ -659,7 +671,7 @@ Calendar.cellClick = function(el, ev) { return; case 50: var range = el._range; - var current = el.firstChild.data; + var current = el.innerHTML; for (var i = range.length; --i >= 0;) if (range[i] == current) break; @@ -669,15 +681,13 @@ Calendar.cellClick = function(el, ev) { } else if ( ++i >= range.length ) i = 0; var newval = range[i]; - el.firstChild.data = newval; + el.innerHTML = newval; cal.onUpdateTime(); return; case 0: // TODAY will bring us here - 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. + if ((typeof cal.getDateStatus == "function") && + cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) { return false; } break; @@ -685,14 +695,15 @@ Calendar.cellClick = function(el, ev) { if (!date.equalsTo(cal.date)) { cal.setDate(date); newdate = true; - } + } else if (el.navtype == 0) + newdate = closing = true; } if (newdate) { - cal.callHandler(); + ev && cal.callHandler(); } if (closing) { Calendar.removeClass(el, "hilite"); - cal.callCloseHandler(); + ev && cal.callCloseHandler(); } }; @@ -749,13 +760,7 @@ Calendar.prototype.create = function (_par) { Calendar._add_evs(cell); cell.calendar = cal; cell.navtype = navtype; - if (text.substr(0, 1) != "&") { - cell.appendChild(document.createTextNode(text)); - } - else { - // FIXME: dirty hack for entities - cell.innerHTML = text; - } + cell.innerHTML = "
      " + text + "
      "; return cell; }; @@ -797,11 +802,10 @@ Calendar.prototype.create = function (_par) { if (this.weekNumbers) { cell = Calendar.createElement("td", row); cell.className = "name wn"; - cell.appendChild(document.createTextNode(Calendar._TT["WK"])); + cell.innerHTML = 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; @@ -818,11 +822,9 @@ Calendar.prototype.create = function (_par) { 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); } @@ -845,7 +847,7 @@ Calendar.prototype.create = function (_par) { function makeTimePart(className, init, range_start, range_end) { var part = Calendar.createElement("span", cell); part.className = className; - part.appendChild(document.createTextNode(init)); + part.innerHTML = init; part.calendar = cal; part.ttip = Calendar._TT["TIME_PART"]; part.navtype = 50; @@ -870,7 +872,7 @@ Calendar.prototype.create = function (_par) { 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.innerHTML = ":"; span.className = "colon"; var M = makeTimePart("minute", mins, 0, 59); var AP = null; @@ -883,30 +885,32 @@ Calendar.prototype.create = function (_par) { 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"; + var pm, hrs = this.date.getHours(), + mins = this.date.getMinutes(); + if (t12) { + pm = (hrs >= 12); + if (pm) hrs -= 12; + if (hrs == 0) hrs = 12; + AP.innerHTML = pm ? "pm" : "am"; + } + H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs; + M.innerHTML = (mins < 10) ? ("0" + mins) : mins; }; cal.onUpdateTime = function() { var date = this.date; - var h = parseInt(H.firstChild.data, 10); + var h = parseInt(H.innerHTML, 10); if (t12) { - if (/pm/i.test(AP.firstChild.data) && h < 12) + if (/pm/i.test(AP.innerHTML) && h < 12) h += 12; - else if (/am/i.test(AP.firstChild.data) && h == 12) + else if (/am/i.test(AP.innerHTML) && 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.setMinutes(parseInt(M.innerHTML, 10)); date.setFullYear(y); date.setMonth(m); date.setDate(d); @@ -938,7 +942,7 @@ Calendar.prototype.create = function (_par) { var mn = Calendar.createElement("div"); mn.className = Calendar.is_ie ? "label-IEfix" : "label"; mn.month = i; - mn.appendChild(document.createTextNode(Calendar._SMN[i])); + mn.innerHTML = Calendar._SMN[i]; div.appendChild(mn); } @@ -948,7 +952,6 @@ Calendar.prototype.create = function (_par) { for (i = 12; i > 0; --i) { var yr = Calendar.createElement("div"); yr.className = Calendar.is_ie ? "label-IEfix" : "label"; - yr.appendChild(document.createTextNode("")); div.appendChild(yr); } @@ -958,14 +961,14 @@ Calendar.prototype.create = function (_par) { /** keyboard navigation, only for popup calendars */ Calendar._keyEvent = function(ev) { - if (!window.calendar) { + var cal = window._dynarch_popupCalendar; + if (!cal || cal.multiple) return false; - } (Calendar.is_ie) && (ev = window.event); - var cal = window.calendar; - var act = (Calendar.is_ie || ev.type == "keypress"); + var act = (Calendar.is_ie || ev.type == "keypress"), + K = ev.keyCode; if (ev.ctrlKey) { - switch (ev.keyCode) { + switch (K) { case 37: // KEY left act && Calendar.cellClick(cal._nav_pm); break; @@ -981,7 +984,7 @@ Calendar._keyEvent = function(ev) { default: return false; } - } else switch (ev.keyCode) { + } else switch (K) { case 32: // KEY space (now) Calendar.cellClick(cal._nav_now); break; @@ -993,48 +996,78 @@ Calendar._keyEvent = function(ev) { case 39: // KEY right case 40: // KEY down 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: // KEY left - (--date >= 0) && (ne = cal.ar_days[date]); - break; - case 38: // KEY up - date -= 7; - (date >= 0) && (ne = cal.ar_days[date]); - break; - case 39: // KEY right - (++date < cal.ar_days.length) && (ne = cal.ar_days[date]); - break; - case 40: // KEY down - date += 7; - (date < cal.ar_days.length) && (ne = cal.ar_days[date]); + var prev, x, y, ne, el, step; + prev = K == 37 || K == 38; + step = (K == 37 || K == 39) ? 1 : 7; + function setVars() { + el = cal.currentDateEl; + var p = el.pos; + x = p & 15; + y = p >> 4; + ne = cal.ar_days[y][x]; + };setVars(); + function prevMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() - step); + cal.setDate(date); + }; + function nextMonth() { + var date = new Date(cal.date); + date.setDate(date.getDate() + step); + cal.setDate(date); + }; + while (1) { + switch (K) { + case 37: // KEY left + if (--x >= 0) + ne = cal.ar_days[y][x]; + else { + x = 6; + K = 38; + continue; + } + break; + case 38: // KEY up + if (--y >= 0) + ne = cal.ar_days[y][x]; + else { + prevMonth(); + setVars(); + } + break; + case 39: // KEY right + if (++x < 7) + ne = cal.ar_days[y][x]; + else { + x = 0; + K = 40; + continue; + } + break; + case 40: // KEY down + if (++y < cal.ar_days.length) + ne = cal.ar_days[y][x]; + else { + nextMonth(); + setVars(); + } + break; + } break; } - if (!ne) { - if (prev) { - Calendar.cellClick(cal._nav_pm); - } else { - Calendar.cellClick(cal._nav_nm); - } - date = (prev) ? cal.date.getMonthDays() : 1; - el = cal.currentDateEl; - ne = cal.ar_days[date - 1]; + if (ne) { + if (!ne.disabled) + Calendar.cellClick(ne); + else if (prev) + prevMonth(); + else + nextMonth(); } - Calendar.removeClass(el, "selected"); - Calendar.addClass(ne, "selected"); - cal.date = new Date(ne.caldate); - cal.callHandler(); - cal.currentDateEl = ne; } break; case 13: // KEY enter - if (act) { - cal.callHandler(); - cal.hide(); - } + if (act) + Calendar.cellClick(cal.currentDateEl, ev); break; default: return false; @@ -1046,7 +1079,10 @@ Calendar._keyEvent = function(ev) { * (RE)Initializes the calendar to the given date and firstDayOfWeek */ Calendar.prototype._init = function (firstDayOfWeek, date) { - var today = new Date(); + var today = new Date(), + TY = today.getFullYear(), + TM = today.getMonth(), + TD = today.getDate(); this.table.style.visibility = "hidden"; var year = date.getFullYear(); if (year < this.minYear) { @@ -1074,21 +1110,24 @@ Calendar.prototype._init = function (firstDayOfWeek, date) { var row = this.tbody.firstChild; var MN = Calendar._SMN[month]; - var ar_days = new Array(); + var ar_days = this.ar_days = new Array(); var weekend = Calendar._TT["WEEKEND"]; + var dates = this.multiple ? (this.datesCells = {}) : null; for (var i = 0; i < 6; ++i, row = row.nextSibling) { var cell = row.firstChild; if (this.weekNumbers) { cell.className = "day wn"; - cell.firstChild.data = date.getWeekNumber(); + cell.innerHTML = date.getWeekNumber(); cell = cell.nextSibling; } row.className = "daysrow"; - var hasdays = false; - for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(date.getDate() + 1)) { - var iday = date.getDate(); + var hasdays = false, iday, dpos = ar_days[i] = []; + for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) { + iday = date.getDate(); var wday = date.getDay(); cell.className = "day"; + cell.pos = i << 4 | j; + dpos[j] = cell; var current_month = (date.getMonth() == month); if (!current_month) { if (this.showsOtherMonths) { @@ -1105,9 +1144,16 @@ Calendar.prototype._init = function (firstDayOfWeek, date) { hasdays = true; } cell.disabled = false; - cell.firstChild.data = iday; - if (typeof this.getDateStatus == "function") { + cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday; + if (dates) + dates[date.print("%Y%m%d")] = cell; + if (this.getDateStatus) { var status = this.getDateStatus(date, year, month, iday); + if (this.getDateToolTip) { + var toolTip = this.getDateToolTip(date, year, month, iday); + if (toolTip) + cell.title = toolTip; + } if (status === true) { cell.className += " disabled"; cell.disabled = true; @@ -1118,33 +1164,66 @@ Calendar.prototype._init = function (firstDayOfWeek, date) { } } if (!cell.disabled) { - ar_days[ar_days.length] = cell; cell.caldate = new Date(date); cell.ttip = "_"; - if (current_month && iday == mday) { + if (!this.multiple && current_month + && iday == mday && this.hiliteToday) { cell.className += " selected"; this.currentDateEl = cell; } - if (date.getFullYear() == today.getFullYear() && - date.getMonth() == today.getMonth() && - iday == today.getDate()) { + if (date.getFullYear() == TY && + date.getMonth() == TM && + iday == TD) { cell.className += " today"; cell.ttip += Calendar._TT["PART_TODAY"]; } - if (weekend.indexOf(wday.toString()) != -1) { + if (weekend.indexOf(wday.toString()) != -1) cell.className += cell.otherMonth ? " oweekend" : " weekend"; - } } } if (!(hasdays || this.showsOtherMonths)) row.className = "emptyrow"; } - this.ar_days = ar_days; - this.title.firstChild.data = Calendar._MN[month] + ", " + year; + this.title.innerHTML = Calendar._MN[month] + ", " + year; this.onSetTime(); this.table.style.visibility = "visible"; + this._initMultipleDates(); // PROFILE - // this.tooltips.firstChild.data = "Generated in " + ((new Date()) - today) + " ms"; + // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms"; +}; + +Calendar.prototype._initMultipleDates = function() { + if (this.multiple) { + for (var i in this.multiple) { + var cell = this.datesCells[i]; + var d = this.multiple[i]; + if (!d) + continue; + if (cell) + cell.className += " selected"; + } + } +}; + +Calendar.prototype._toggleMultipleDate = function(date) { + if (this.multiple) { + var ds = date.print("%Y%m%d"); + var cell = this.datesCells[ds]; + if (cell) { + var d = this.multiple[ds]; + if (!d) { + Calendar.addClass(cell, "selected"); + this.multiple[ds] = date; + } else { + Calendar.removeClass(cell, "selected"); + delete this.multiple[ds]; + } + } + } +}; + +Calendar.prototype.setDateToolTipHandler = function (unaryFunction) { + this.getDateToolTip = unaryFunction; }; /** @@ -1209,7 +1288,7 @@ Calendar.prototype.destroy = function () { var el = this.element.parentNode; el.removeChild(this.element); Calendar._C = null; - window.calendar = null; + window._dynarch_popupCalendar = null; }; /** @@ -1226,14 +1305,15 @@ Calendar.prototype.reparent = function (new_parent) { // document, if the calendar is shown. If the click was outside the open // calendar this function closes it. Calendar._checkCalendar = function(ev) { - if (!window.calendar) { + var calendar = window._dynarch_popupCalendar; + if (!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) { // calls closeHandler which should hide the calendar. - window.calendar.callCloseHandler(); + window._dynarch_popupCalendar.callCloseHandler(); return Calendar.stopEvent(ev); } }; @@ -1254,7 +1334,7 @@ Calendar.prototype.show = function () { this.element.style.display = "block"; this.hidden = false; if (this.isPopup) { - window.calendar = this; + window._dynarch_popupCalendar = this; Calendar.addEvent(document, "keydown", Calendar._keyEvent); Calendar.addEvent(document, "keypress", Calendar._keyEvent); Calendar.addEvent(document, "mousedown", Calendar._checkCalendar); @@ -1344,8 +1424,8 @@ Calendar.prototype.showAtElement = function (el, opts) { 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 + case "l": p.x += el.offsetWidth - w; break; + case "r": break; // already there } p.width = w; p.height = h + 40; @@ -1373,14 +1453,140 @@ Calendar.prototype.setTtDateFormat = function (str) { * Tries to identify the date represented in a string. If successful it also * calls this.setDate which moves the calendar to the given date. */ -Calendar.prototype.parseDate = function (str, fmt) { +Calendar.prototype.parseDate = function(str, fmt) { + if (!fmt) + fmt = this.dateFormat; + this.setDate(Date.parseDate(str, fmt)); +}; + +Calendar.prototype.hideShowCovered = function () { + if (!Calendar.is_ie && !Calendar.is_opera) + return; + 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; + + 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 = 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"; + } + } + } +}; + +/** Internal function; it displays the bar with the names of the weekday. */ +Calendar.prototype._displayWeekdays = function () { + var fdow = this.firstDayOfWeek; + var cell = this.firstdayname; + var weekend = Calendar._TT["WEEKEND"]; + for (var i = 0; i < 7; ++i) { + cell.className = "day name"; + var realday = (i + fdow) % 7; + if (i) { + cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); + cell.navtype = 100; + cell.calendar = this; + cell.fdow = realday; + Calendar._add_evs(cell); + } + if (weekend.indexOf(realday.toString()) != -1) { + Calendar.addClass(cell, "weekend"); + } + cell.innerHTML = Calendar._SDN[(i + fdow) % 7]; + cell = cell.nextSibling; + } +}; + +/** Internal function. Hides all combo boxes that might be displayed. */ +Calendar.prototype._hideCombos = function () { + this.monthsCombo.style.display = "none"; + this.yearsCombo.style.display = "none"; +}; + +/** Internal function. Starts dragging the element. */ +Calendar.prototype._dragStart = function (ev) { + if (this.dragging) { + return; + } + this.dragging = true; + var posX; + var posY; + if (Calendar.is_ie) { + posY = window.event.clientY + document.body.scrollTop; + posX = window.event.clientX + document.body.scrollLeft; + } else { + posY = ev.clientY + window.scrollY; + posX = ev.clientX + window.scrollX; + } + var st = this.element.style; + this.xOffs = posX - parseInt(st.left); + this.yOffs = posY - parseInt(st.top); + with (Calendar) { + addEvent(document, "mousemove", calDragIt); + addEvent(document, "mouseup", calDragEnd); + } +}; + +// BEGIN: DATE OBJECT PATCHES + +/** Adds the number of days array to the Date object. */ +Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); + +/** Constants used for time computations */ +Date.SECOND = 1000 /* milliseconds */; +Date.MINUTE = 60 * Date.SECOND; +Date.HOUR = 60 * Date.MINUTE; +Date.DAY = 24 * Date.HOUR; +Date.WEEK = 7 * Date.DAY; + +Date.parseDate = function(str, fmt) { + var today = new Date(); var y = 0; var m = -1; var d = 0; var a = str.split(/\W+/); - if (!fmt) { - fmt = this.dateFormat; - } var b = fmt.match(/%./g); var i = 0, j = 0; var hr = 0; @@ -1422,6 +1628,8 @@ Calendar.prototype.parseDate = function (str, fmt) { case "%p": if (/pm/i.test(a[i]) && hr < 12) hr += 12; + else if (/am/i.test(a[i]) && hr >= 12) + hr -= 12; break; case "%M": @@ -1429,10 +1637,13 @@ Calendar.prototype.parseDate = function (str, fmt) { break; } } - if (y != 0 && m != -1 && d != 0) { - this.setDate(new Date(y, m, d, hr, min, 0)); - return; - } + if (isNaN(y)) y = today.getFullYear(); + if (isNaN(m)) m = today.getMonth(); + if (isNaN(d)) d = today.getDate(); + if (isNaN(hr)) hr = today.getHours(); + if (isNaN(min)) min = today.getMinutes(); + if (y != 0 && m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); y = 0; m = -1; d = 0; for (i = 0; i < a.length; ++i) { if (a[i].search(/[a-zA-Z]+/) != -1) { @@ -1455,142 +1666,13 @@ Calendar.prototype.parseDate = function (str, fmt) { d = a[i]; } } - if (y == 0) { - var today = new Date(); + if (y == 0) 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") { // 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 = 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"; - } - } - } - }; - 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. */ -Calendar.prototype._displayWeekdays = function () { - var fdow = this.firstDayOfWeek; - var cell = this.firstdayname; - var weekend = Calendar._TT["WEEKEND"]; - for (var i = 0; i < 7; ++i) { - cell.className = "day name"; - var realday = (i + fdow) % 7; - if (i) { - cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]); - cell.navtype = 100; - cell.calendar = this; - cell.fdow = realday; - Calendar._add_evs(cell); - } - if (weekend.indexOf(realday.toString()) != -1) { - Calendar.addClass(cell, "weekend"); - } - cell.firstChild.data = Calendar._SDN[(i + fdow) % 7]; - cell = cell.nextSibling; - } -}; - -/** Internal function. Hides all combo boxes that might be displayed. */ -Calendar.prototype._hideCombos = function () { - this.monthsCombo.style.display = "none"; - this.yearsCombo.style.display = "none"; -}; - -/** Internal function. Starts dragging the element. */ -Calendar.prototype._dragStart = function (ev) { - if (this.dragging) { - return; - } - this.dragging = true; - var posX; - var posY; - if (Calendar.is_ie) { - posY = window.event.clientY + document.body.scrollTop; - posX = window.event.clientX + document.body.scrollLeft; - } else { - posY = ev.clientY + window.scrollY; - posX = ev.clientX + window.scrollX; - } - var st = this.element.style; - this.xOffs = posX - parseInt(st.left); - this.yOffs = posY - parseInt(st.top); - with (Calendar) { - addEvent(document, "mousemove", calDragIt); - addEvent(document, "mouseup", calDragEnd); - } + if (m != -1 && d != 0) + return new Date(y, m, d, hr, min, 0); + return today; }; -// BEGIN: DATE OBJECT PATCHES - -/** Adds the number of days array to the Date object. */ -Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31); - -/** Constants used for time computations */ -Date.SECOND = 1000 /* milliseconds */; -Date.MINUTE = 60 * Date.SECOND; -Date.HOUR = 60 * Date.MINUTE; -Date.DAY = 24 * Date.HOUR; -Date.WEEK = 7 * Date.DAY; - /** Returns the number of days in the current month */ Date.prototype.getMonthDays = function(month) { var year = this.getFullYear(); @@ -1623,7 +1705,7 @@ Date.prototype.getWeekNumber = function() { return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1; }; -/** Checks dates equality (ignores time) */ +/** Checks date and time equality */ Date.prototype.equalsTo = function(date) { return ((this.getFullYear() == date.getFullYear()) && (this.getMonth() == date.getMonth()) && @@ -1632,6 +1714,15 @@ Date.prototype.equalsTo = function(date) { (this.getMinutes() == date.getMinutes())); }; +/** Set only the year, month, date parts (keep existing time) */ +Date.prototype.setDateOnly = function(date) { + var tmp = new Date(date); + this.setDate(1); + this.setFullYear(tmp.getFullYear()); + this.setMonth(tmp.getMonth()); + this.setDate(tmp.getDate()); +}; + /** Prints the date in a string according to the given format. */ Date.prototype.print = function (str) { var m = this.getMonth(); @@ -1684,7 +1775,7 @@ Date.prototype.print = function (str) { s["%%"] = "%"; // a literal '%' character var re = /%./g; - if (!Calendar.is_ie5) + if (!Calendar.is_ie5 && !Calendar.is_khtml) return str.replace(re, function (par) { return s[par] || par; }); var a = str.match(re); @@ -1712,4 +1803,4 @@ Date.prototype.setFullYear = function(y) { // global object that remembers the calendar -window.calendar = null; +window._dynarch_popupCalendar = null; diff --git a/httemplate/elements/calendar_stripped.js b/httemplate/elements/calendar_stripped.js index 6a8e326af..4fe03f1ea 100644 --- a/httemplate/elements/calendar_stripped.js +++ b/httemplate/elements/calendar_stripped.js @@ -1,12 +1,14 @@ -/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/ - * ------------------------------------------------------------------ +/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo + * ----------------------------------------------------------- * - * The DHTML Calendar, version 0.9.6 "Keep cool but don't freeze" + * The DHTML Calendar, version 1.0 "It is happening again" * * Details and latest version at: - * http://dynarch.com/mishoo/calendar.epl + * www.dynarch.com/projects/calendar + * + * This script is developed by Dynarch.com. Visit us at www.dynarch.com. * * 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(firstDayOfWeek,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.firstDayOfWeek=firstDayOfWeek;this.showsOtherMonths=false;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.yearStep=2;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_ie5=(Calendar.is_ie&&/msie 5\.0/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=this.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{var mcw=mc.offsetWidth;if(typeof mcw=="undefined")mcw=50;s.left=(cd.offsetLeft+cd.offsetWidth-mcw)+"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?cal.yearStep:-cal.yearStep;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var ycw=yc.offsetWidth;if(typeof ycw=="undefined")ycw=50;s.left=(cd.offsetLeft+cd.offsetWidth-ycw)+"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<0)i=range.length-1;}else if(++i>=range.length)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,"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;addEvent(document,"mousemove",tableMouseOver);}else addEvent(document,Calendar.is_ie5?"mousemove":"mouseover",tableMouseOver);addClass(el,"hilite active");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)=="_"){el.ttip=el.caldate.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=new Date(el.caldate);date=cal.date;newdate=true;if(!(cal.dateClicked=!el.otherMonth))cal._init(cal.firstDayOfWeek,date);}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<0)i=range.length-1;}else if(++i>=range.length)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=Calendar._TT["TIME"]||" ";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.firstDayOfWeek,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.callCloseHandler();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.firstDayOfWeek=firstDayOfWeek;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var day1=(date.getDay()-this.firstDayOfWeek)%7;if(day1<0)day1+=7;date.setDate(-day1);date.setDate(date.getDate()+1);var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var ar_days=new Array();var weekend=Calendar._TT["WEEKEND"];for(var i=0;i<6;++i,row=row.nextSibling){var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.firstChild.data=date.getWeekNumber();cell=cell.nextSibling;}row.className="daysrow";var hasdays=false;for(var j=0;j<7;++j,cell=cell.nextSibling,date.setDate(date.getDate()+1)){var iday=date.getDate();var wday=date.getDay();cell.className="day";var current_month=(date.getMonth()==month);if(!current_month){if(this.showsOtherMonths){cell.className+=" othermonth";cell.otherMonth=true;}else{cell.className="emptycell";cell.innerHTML=" ";cell.disabled=true;continue;}}else{cell.otherMonth=false;hasdays=true;}cell.disabled=false;cell.firstChild.data=iday;if(typeof this.getDateStatus=="function"){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=new Date(date);cell.ttip="_";if(current_month&&iday==mday){cell.className+=" selected";this.currentDateEl=cell;}if(date.getFullYear()==today.getFullYear()&&date.getMonth()==today.getMonth()&&iday==today.getDate()){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(weekend.indexOf(wday.toString())!=-1){cell.className+=cell.otherMonth?" oweekend":" weekend";}}}if(!(hasdays||this.showsOtherMonths))row.className="emptyrow";}this.ar_days=ar_days;this.title.firstChild.data=Calendar._MN[month]+", "+year;this.onSetTime();this.table.style.visibility="visible";};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.firstDayOfWeek,date);}};Calendar.prototype.refresh=function(){this._init(this.firstDayOfWeek,this.date);};Calendar.prototype.setFirstDayOfWeek=function(firstDayOfWeek){this._init(firstDayOfWeek,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;}function fixPosition(box){if(box.x<0)box.x=0;if(box.y<0)box.y=0;var cp=document.createElement("div");var s=cp.style;s.position="absolute";s.right=s.bottom=s.width=s.height="0px";document.body.appendChild(cp);var br=Calendar.getAbsolutePos(cp);document.body.removeChild(cp);if(Calendar.is_ie){br.y+=document.body.scrollTop;br.x+=document.body.scrollLeft;}else{br.y+=window.scrollY;br.x+=window.scrollX;}var tmp=box.x+box.width-br.x;if(tmp>0)box.x-=tmp;tmp=box.y+box.height-br.y;if(tmp>0)box.y-=tmp;};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;}p.width=w;p.height=h+40;self.monthsCombo.style.display="none";fixPosition(p);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.match(/%./g);var i=0,j=0;var hr=0;var min=0;for(i=0;i29)?1900:2000);break;case "%b":case "%B":for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}break;case "%H":case "%I":case "%k":case "%l":hr=parseInt(a[i],10);break;case "%P":case "%p":if(/pm/i.test(a[i])&&hr<12)hr+=12;break;case "%M":min=parseInt(a[i],10);break;}}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)||(CY2=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=/%./g;if(!Calendar.is_ie5)return str.replace(re,function(par){return s[par]||par;});var a=str.match(re);for(var i=0;i0;){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_ie5=(Calendar.is_ie&&/msie 5\.0/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=this.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){var f=Calendar.is_ie?window.event.srcElement:ev.currentTarget;while(f.nodeType!=1||/^div$/i.test(f.tagName))f=f.parentNode;return f;};Calendar.getTargetElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.target;while(f.nodeType!=1)f=f.parentNode;return f;};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{var mcw=mc.offsetWidth;if(typeof mcw=="undefined")mcw=50;s.left=(cd.offsetLeft+cd.offsetWidth-mcw)+"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.innerHTML=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?cal.yearStep:-cal.yearStep;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var ycw=yc.offsetWidth;if(typeof ycw=="undefined")ycw=50;s.left=(cd.offsetLeft+cd.offsetWidth-ycw)+"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<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=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,"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.innerHTML;addEvent(document,"mousemove",tableMouseOver);}else addEvent(document,Calendar.is_ie5?"mousemove":"mouseover",tableMouseOver);addClass(el,"hilite active");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)=="_"){el.ttip=el.caldate.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.innerHTML=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");if(el.calendar)el.calendar.tooltips.innerHTML=_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"){if(cal.currentDateEl){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}}cal.date.setDateOnly(el.caldate);date=cal.date;var other_month=!(cal.dateClicked=!el.otherMonth);if(!other_month&&!cal.currentDateEl)cal._toggleMultipleDate(new Date(date));else newdate=!el.disabled;if(other_month)cal._init(cal.firstDayOfWeek,date);}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=new Date(cal.date);if(el.navtype==0)date.setDateOnly(new 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<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=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;}else if(el.navtype==0)newdate=closing=true;}if(newdate){ev&&cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");ev&&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;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.innerHTML=Calendar._TT["WK"];}for(var i=7;i>0;--i){cell=Calendar.createElement("td",row);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);}for(var j=7;j>0;--j){cell=Calendar.createElement("td",row);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=Calendar._TT["TIME"]||" ";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.innerHTML=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.innerHTML=":";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 pm,hrs=this.date.getHours(),mins=this.date.getMinutes();if(t12){pm=(hrs>=12);if(pm)hrs-=12;if(hrs==0)hrs=12;AP.innerHTML=pm?"pm":"am";}H.innerHTML=(hrs<10)?("0"+hrs):hrs;M.innerHTML=(mins<10)?("0"+mins):mins;};cal.onUpdateTime=function(){var date=this.date;var h=parseInt(H.innerHTML,10);if(t12){if(/pm/i.test(AP.innerHTML)&&h<12)h+=12;else if(/am/i.test(AP.innerHTML)&&h==12)h=0;}var d=date.getDate();var m=date.getMonth();var y=date.getFullYear();date.setHours(h);date.setMinutes(parseInt(M.innerHTML,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";div.appendChild(yr);}this._init(this.firstDayOfWeek,this.date);parent.appendChild(this.element);};Calendar._keyEvent=function(ev){var cal=window._dynarch_popupCalendar;if(!cal||cal.multiple)return false;(Calendar.is_ie)&&(ev=window.event);var act=(Calendar.is_ie||ev.type=="keypress"),K=ev.keyCode;if(ev.ctrlKey){switch(K){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(K){case 32:Calendar.cellClick(cal._nav_now);break;case 27:act&&cal.callCloseHandler();break;case 37:case 38:case 39:case 40:if(act){var prev,x,y,ne,el,step;prev=K==37||K==38;step=(K==37||K==39)?1:7;function setVars(){el=cal.currentDateEl;var p=el.pos;x=p&15;y=p>>4;ne=cal.ar_days[y][x];};setVars();function prevMonth(){var date=new Date(cal.date);date.setDate(date.getDate()-step);cal.setDate(date);};function nextMonth(){var date=new Date(cal.date);date.setDate(date.getDate()+step);cal.setDate(date);};while(1){switch(K){case 37:if(--x>=0)ne=cal.ar_days[y][x];else{x=6;K=38;continue;}break;case 38:if(--y>=0)ne=cal.ar_days[y][x];else{prevMonth();setVars();}break;case 39:if(++x<7)ne=cal.ar_days[y][x];else{x=0;K=40;continue;}break;case 40:if(++ythis.maxYear){year=this.maxYear;date.setFullYear(year);}this.firstDayOfWeek=firstDayOfWeek;this.date=new Date(date);var month=date.getMonth();var mday=date.getDate();var no_days=date.getMonthDays();date.setDate(1);var day1=(date.getDay()-this.firstDayOfWeek)%7;if(day1<0)day1+=7;date.setDate(-day1);date.setDate(date.getDate()+1);var row=this.tbody.firstChild;var MN=Calendar._SMN[month];var ar_days=this.ar_days=new Array();var weekend=Calendar._TT["WEEKEND"];var dates=this.multiple?(this.datesCells={}):null;for(var i=0;i<6;++i,row=row.nextSibling){var cell=row.firstChild;if(this.weekNumbers){cell.className="day wn";cell.innerHTML=date.getWeekNumber();cell=cell.nextSibling;}row.className="daysrow";var hasdays=false,iday,dpos=ar_days[i]=[];for(var j=0;j<7;++j,cell=cell.nextSibling,date.setDate(iday+1)){iday=date.getDate();var wday=date.getDay();cell.className="day";cell.pos=i<<4|j;dpos[j]=cell;var current_month=(date.getMonth()==month);if(!current_month){if(this.showsOtherMonths){cell.className+=" othermonth";cell.otherMonth=true;}else{cell.className="emptycell";cell.innerHTML=" ";cell.disabled=true;continue;}}else{cell.otherMonth=false;hasdays=true;}cell.disabled=false;cell.innerHTML=this.getDateText?this.getDateText(date,iday):iday;if(dates)dates[date.print("%Y%m%d")]=cell;if(this.getDateStatus){var status=this.getDateStatus(date,year,month,iday);if(this.getDateToolTip){var toolTip=this.getDateToolTip(date,year,month,iday);if(toolTip)cell.title=toolTip;}if(status===true){cell.className+=" disabled";cell.disabled=true;}else{if(/disabled/i.test(status))cell.disabled=true;cell.className+=" "+status;}}if(!cell.disabled){cell.caldate=new Date(date);cell.ttip="_";if(!this.multiple&¤t_month&&iday==mday&&this.hiliteToday){cell.className+=" selected";this.currentDateEl=cell;}if(date.getFullYear()==TY&&date.getMonth()==TM&&iday==TD){cell.className+=" today";cell.ttip+=Calendar._TT["PART_TODAY"];}if(weekend.indexOf(wday.toString())!=-1)cell.className+=cell.otherMonth?" oweekend":" weekend";}}if(!(hasdays||this.showsOtherMonths))row.className="emptyrow";}this.title.innerHTML=Calendar._MN[month]+", "+year;this.onSetTime();this.table.style.visibility="visible";this._initMultipleDates();};Calendar.prototype._initMultipleDates=function(){if(this.multiple){for(var i in this.multiple){var cell=this.datesCells[i];var d=this.multiple[i];if(!d)continue;if(cell)cell.className+=" selected";}}};Calendar.prototype._toggleMultipleDate=function(date){if(this.multiple){var ds=date.print("%Y%m%d");var cell=this.datesCells[ds];if(cell){var d=this.multiple[ds];if(!d){Calendar.addClass(cell,"selected");this.multiple[ds]=date;}else{Calendar.removeClass(cell,"selected");delete this.multiple[ds];}}}};Calendar.prototype.setDateToolTipHandler=function(unaryFunction){this.getDateToolTip=unaryFunction;};Calendar.prototype.setDate=function(date){if(!date.equalsTo(this.date)){this._init(this.firstDayOfWeek,date);}};Calendar.prototype.refresh=function(){this._init(this.firstDayOfWeek,this.date);};Calendar.prototype.setFirstDayOfWeek=function(firstDayOfWeek){this._init(firstDayOfWeek,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._dynarch_popupCalendar=null;};Calendar.prototype.reparent=function(new_parent){var el=this.element;el.parentNode.removeChild(el);new_parent.appendChild(el);};Calendar._checkCalendar=function(ev){var calendar=window._dynarch_popupCalendar;if(!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._dynarch_popupCalendar.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._dynarch_popupCalendar=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;}function fixPosition(box){if(box.x<0)box.x=0;if(box.y<0)box.y=0;var cp=document.createElement("div");var s=cp.style;s.position="absolute";s.right=s.bottom=s.width=s.height="0px";document.body.appendChild(cp);var br=Calendar.getAbsolutePos(cp);document.body.removeChild(cp);if(Calendar.is_ie){br.y+=document.body.scrollTop;br.x+=document.body.scrollLeft;}else{br.y+=window.scrollY;br.x+=window.scrollX;}var tmp=box.x+box.width-br.x;if(tmp>0)box.x-=tmp;tmp=box.y+box.height-br.y;if(tmp>0)box.y-=tmp;};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 "l":p.x+=el.offsetWidth-w;break;case "r":break;}p.width=w;p.height=h+40;self.monthsCombo.style.display="none";fixPosition(p);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){if(!fmt)fmt=this.dateFormat;this.setDate(Date.parseDate(str,fmt));};Calendar.prototype.hideShowCovered=function(){if(!Calendar.is_ie&&!Calendar.is_opera)return;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=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)||(CY229)?1900:2000);break;case "%b":case "%B":for(j=0;j<12;++j){if(Calendar._MN[j].substr(0,a[i].length).toLowerCase()==a[i].toLowerCase()){m=j;break;}}break;case "%H":case "%I":case "%k":case "%l":hr=parseInt(a[i],10);break;case "%P":case "%p":if(/pm/i.test(a[i])&&hr<12)hr+=12;else if(/am/i.test(a[i])&&hr>=12)hr-=12;break;case "%M":min=parseInt(a[i],10);break;}}if(isNaN(y))y=today.getFullYear();if(isNaN(m))m=today.getMonth();if(isNaN(d))d=today.getDate();if(isNaN(hr))hr=today.getHours();if(isNaN(min))min=today.getMinutes();if(y!=0&&m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);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)y=today.getFullYear();if(m!=-1&&d!=0)return new Date(y,m,d,hr,min,0);return today;};Date.prototype.getMonthDays=function(month){var year=this.getFullYear();if(typeof month=="undefined"){month=this.getMonth();}if(((0==(year%4))&&((0!=(year%100))||(0==(year%400))))&&month==1){return 29;}else{return Date._MD[month];}};Date.prototype.getDayOfYear=function(){var now=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var then=new Date(this.getFullYear(),0,0,0,0,0);var time=now-then;return Math.floor(time/Date.DAY);};Date.prototype.getWeekNumber=function(){var d=new Date(this.getFullYear(),this.getMonth(),this.getDate(),0,0,0);var DoW=d.getDay();d.setDate(d.getDate()-(DoW+6)%7+3);var ms=d.valueOf();d.setMonth(0);d.setDate(4);return Math.round((ms-d.valueOf())/(7*864e5))+1;};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.setDateOnly=function(date){var tmp=new Date(date);this.setDate(1);this.setFullYear(tmp.getFullYear());this.setMonth(tmp.getMonth());this.setDate(tmp.getDate());};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=/%./g;if(!Calendar.is_ie5&&!Calendar.is_khtml)return str.replace(re,function(par){return s[par]||par;});var a=str.match(re);for(var i=0;i Date: Thu, 16 Feb 2006 21:43:02 +0000 Subject: automate more of the initial data adding... --- FS/FS/Setup.pm | 422 +++++++++++++++++++++++++++++++++++++++++++ FS/bin/freeside-setup | 73 +------- bin/populate-msgcat | 135 -------------- httemplate/docs/admin.html | 45 +---- httemplate/docs/install.html | 11 +- 5 files changed, 434 insertions(+), 252 deletions(-) create mode 100644 FS/FS/Setup.pm delete mode 100755 bin/populate-msgcat diff --git a/FS/FS/Setup.pm b/FS/FS/Setup.pm new file mode 100644 index 000000000..8e89be5eb --- /dev/null +++ b/FS/FS/Setup.pm @@ -0,0 +1,422 @@ +package FS::Setup; + +use strict; +use vars qw( @ISA @EXPORT_OK ); +use Exporter; +#use Tie::DxHash; +use Tie::IxHash; +use FS::UID qw( dbh ); +use FS::Record; + +use FS::svc_domain; +$FS::svc_domain::whois_hack = 1; +$FS::svc_domain::whois_hack = 1; + +@ISA = qw( Exporter ); +@EXPORT_OK = qw( create_initial_data ); + +=head1 NAME + +FS::Setup - Database setup + +=head1 SYNOPSIS + + use FS::Setup; + +=head1 DESCRIPTION + +Currently this module simply provides a place to store common subroutines for +database setup. + +=head1 SUBROUTINES + +=over 4 + +=item + +=cut + +sub create_initial_data { + my %opt = @_; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + $FS::UID::AutoCommit = 0; + + populate_locales(); + + #initial_data data + populate_initial_data(%opt); + + populate_msgcat(); + + if ( $oldAutoCommit ) { + dbh->commit or die dbh->errstr; + } + +} + +sub populate_locales { + + use Locale::Country; + use Locale::SubCountry; + use FS::cust_main_county; + + #cust_main_county + foreach my $country ( sort map uc($_), all_country_codes ) { + + my $subcountry = eval { new Locale::SubCountry($country) }; + my @states = $subcountry ? $subcountry->all_codes : undef; + + if ( !scalar(@states) || ( scalar(@states)==1 && !defined($states[0]) ) ) { + + my $cust_main_county = new FS::cust_main_county({ + 'tax' => 0, + 'country' => $country, + }); + my $error = $cust_main_county->insert; + die $error if $error; + + } else { + + if ( $states[0] =~ /^(\d+|\w)$/ ) { + @states = map $subcountry->full_name($_), @states + } + + foreach my $state ( @states ) { + + my $cust_main_county = new FS::cust_main_county({ + 'state' => $state, + 'tax' => 0, + 'country' => $country, + }); + my $error = $cust_main_county->insert; + die $error if $error; + + } + + } + } + +} + +sub populate_initial_data { + my %opt = @_; + + my $data = initial_data(%opt); + + foreach my $table ( keys %$data ) { + + my $class = "FS::$table"; + eval "use $class;"; + die $@ if $@; + + my @records = @{ $data->{$table} }; + + foreach my $record ( @records ) { + my $args = delete($record->{'_insert_args'}) || []; + my $object = $class->new( $record ); + my $error = $object->insert( @$args ); + die "error inserting record into $table: $error\n" + if $error; + } + + } + +} + +sub initial_data { + my %opt = @_; + + #tie my %hash, 'Tie::DxHash', + tie my %hash, 'Tie::IxHash', + + #billing events + 'part_bill_event' => [ + { 'payby' => 'CARD', + 'event' => 'Batch card', + 'seconds' => 0, + 'eventcode' => '$cust_bill->batch_card();', + 'weight' => 40, + 'plan' => 'batch-card', + }, + { 'payby' => 'BILL', + 'event' => 'Send invoice', + 'seconds' => 0, + 'eventcode' => '$cust_bill->send();', + 'weight' => 50, + 'plan' => 'send', + }, + { 'payby' => 'DCRD', + 'event' => 'Send invoice', + 'seconds' => 0, + 'eventcode' => '$cust_bill->send();', + 'weight' => 50, + 'plan' => 'send', + }, + { 'payby' => 'DCHK', + 'event' => 'Send invoice', + 'seconds' => 0, + 'eventcode' => '$cust_bill->send();', + 'weight' => 50, + 'plan' => 'send', + }, + ], + + #you must create a service definition. An example of a service definition + #would be a dial-up account or a domain. First, it is necessary to create a + #domain definition. Click on View/Edit service definitions and Add a new + #service definition with Table svc_domain (and no modifiers). + 'part_svc' => [ + { 'svc' => 'Domain', + 'svcdb' => 'svc_domain', + } + ], + + #Now that you have created your first service, you must create a package + #including this service which you can sell to customers. Zero, one, or many + #services are bundled into a package. Click on View/Edit package + #definitions and Add a new package definition which includes quantity 1 of + #the svc_domain service you created above. + 'part_pkg' => [ + { 'pkg' => 'System Domain', + 'comment' => '(NOT FOR CUSTOMERS)', + 'freq' => '0', + 'plan' => 'flat', + '_insert_args' => [ + 'pkg_svc' => { 1 => 1 }, # XXX + 'primary_svc' => 1, #XXX + 'options' => { + 'setup_fee' => '0', + 'recur_fee' => '0', + }, + ], + }, + ], + + #After you create your first package, then you must define who is able to + #sell that package by creating an agent type. An example of an agent type + #would be an internal sales representitive which sells regular and + #promotional packages, as opposed to an external sales representitive + #which would only sell regular packages of services. Click on View/Edit + #agent types and Add a new agent type. + 'agent_type' => [ + { 'atype' => 'internal' }, + ], + + #Allow this agent type to sell the package you created above. + 'type_pkgs' => [ + { 'typenum' => 1, #XXX + 'pkgpart' => 1, #XXX + }, + ], + + #After creating a new agent type, you must create an agent. Click on + #View/Edit agents and Add a new agent. + 'agent' => [ + { 'agent' => 'Internal', + 'typenum' => 1, # XXX + }, + ], + + #Set up at least one Advertising source. Advertising sources will help you + #keep track of how effective your advertising is, tracking where customers + #heard of your service offerings. You must create at least one advertising + #source. If you do not wish to use the referral functionality, simply + #create a single advertising source only. Click on View/Edit advertising + #sources and Add a new advertising source. + 'part_referral' => [ + { 'referral' => 'Internal', }, + ], + + #Click on New Customer and create a new customer for your system accounts + #with billing type Complimentary. Leave the First package dropdown set to + #(none). + 'cust_main' => [ + { 'agentnum' => 1, #XXX + 'refnum' => 1, #XXX + 'first' => 'System', + 'last' => 'Accounts', + 'address1' => '1234 System Lane', + 'city' => 'Systemtown', + 'state' => 'CA', + 'zip' => '54321', + 'country' => 'US', + 'payby' => 'COMP', + 'payinfo' => 'system', #or something + 'paydate' => '1/2037', + }, + ], + + #From the Customer View screen of the newly created customer, order the + #package you defined above. + 'cust_pkg' => [ + { 'custnum' => 1, #XXX + 'pkgpart' => 1, #XXX + }, + ], + + #From the Package View screen of the newly created package, choose + #(Provision) to add the customer's service for this new package. + #Add your own domain. + 'svc_domain' => [ + { 'domain' => $opt{'domain'}, + 'pkgnum' => 1, #XXX + 'svcpart' => 1, #XXX + 'action' => 'N', #pseudo-field + }, + ], + + #Go back to View/Edit service definitions on the main menu, and Add a new + #service definition with Table svc_acct. Select your domain in the domsvc + #Modifier. Set Fixed to define a service locked-in to this domain, or + #Default to define a service which may select from among this domain and + #the customer's domains. + + #not yet.... + + #) + ; + + \%hash; + +} + +sub populate_msgcat { + + use FS::Record qw(qsearch); + use FS::msgcat; + + foreach my $del_msgcat ( qsearch('msgcat', {}) ) { + my $error = $del_msgcat->delete; + die $error if $error; + } + + my %messages = msgcat_messages(); + + foreach my $msgcode ( keys %messages ) { + foreach my $locale ( keys %{$messages{$msgcode}} ) { + my $msgcat = new FS::msgcat( { + 'msgcode' => $msgcode, + 'locale' => $locale, + 'msg' => $messages{$msgcode}{$locale}, + }); + my $error = $msgcat->insert; + die $error if $error; + } + } + +} + +sub msgcat_messages { + + # 'msgcode' => { + # 'en_US' => 'Message', + # }, + + ( + + 'passwords_dont_match' => { + 'en_US' => "Passwords don't match", + }, + + 'invalid_card' => { + 'en_US' => 'Invalid credit card number', + }, + + 'unknown_card_type' => { + 'en_US' => 'Unknown card type', + }, + + 'not_a' => { + 'en_US' => 'Not a ', + }, + + 'empty_password' => { + 'en_US' => 'Empty password', + }, + + 'no_access_number_selected' => { + 'en_US' => 'No access number selected', + }, + + 'illegal_text' => { + 'en_US' => 'Illegal (text)', + #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in field', + }, + + 'illegal_or_empty_text' => { + 'en_US' => 'Illegal or empty (text)', + #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in required field', + }, + + 'illegal_username' => { + 'en_US' => 'Illegal username', + }, + + 'illegal_password' => { + 'en_US' => 'Illegal password (', + }, + + 'illegal_password_characters' => { + 'en_US' => ' characters)', + }, + + 'username_in_use' => { + 'en_US' => 'Username in use', + }, + + 'illegal_email_invoice_address' => { + 'en_US' => 'Illegal email invoice address', + }, + + 'illegal_name' => { + 'en_US' => 'Illegal (name)', + #'en_US' => 'Only letters, numbers, spaces and the following punctuation symbols are permitted: , . - \' in field', + }, + + 'illegal_phone' => { + 'en_US' => 'Illegal (phone)', + #'en_US' => '', + }, + + 'illegal_zip' => { + 'en_US' => 'Illegal (zip)', + #'en_US' => '', + }, + + 'expired_card' => { + 'en_US' => 'Expired card', + }, + + 'daytime' => { + 'en_US' => 'Day Phone', + }, + + 'night' => { + 'en_US' => 'Night Phone', + }, + + 'svc_external-id' => { + 'en_US' => 'External ID', + }, + + 'svc_external-title' => { + 'en_US' => 'Title', + }, + + ); +} + +=back + +=head1 BUGS + +Sure. + +=head1 SEE ALSO + +=cut + +1; + diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index a16e51749..bff2bcc63 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -4,23 +4,20 @@ BEGIN { $FS::Schema::setup_hack = 1; } use strict; -use vars qw($opt_s); +use vars qw($opt_s $opt_d $opt_v); use Getopt::Std; -use Locale::Country; -use Locale::SubCountry; use FS::UID qw(adminsuidsetup datasrc checkeuid getsecrets); use FS::Schema qw( dbdef_dist reload_dbdef ); use FS::Record; -use FS::cust_main_county; #use FS::raddb; -use FS::part_bill_event; +use FS::Setup qw(create_initial_data); die "Not running uid freeside!" unless checkeuid(); #my %attrib2db = # map { lc($FS::raddb::attrib{$_}) => $_ } keys %FS::raddb::attrib; -getopts("s"); +getopts("svd:"); my $user = shift or die &usage; getsecrets($user); @@ -94,6 +91,7 @@ my $dbh = adminsuidsetup $user; $|=1; foreach my $statement ( $dbdef->sql($dbh) ) { + warn $statement if $statement =~ /TABLE cdr/; $dbh->do( $statement ) or die "CREATE error: ". $dbh->errstr. "\ndoing statement: $statement"; } @@ -104,69 +102,14 @@ dbdef_create($dbh, $dbdef_file); delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload reload_dbdef($dbdef_file); -#cust_main_county -foreach my $country ( sort map uc($_), all_country_codes ) { +create_initial_data('domain' => $opt_d); - my $subcountry = eval { new Locale::SubCountry($country) }; - my @states = $subcountry ? $subcountry->all_codes : undef; - - if ( !scalar(@states) || ( scalar(@states) == 1 && !defined($states[0]) ) ) { - - my $cust_main_county = new FS::cust_main_county({ - 'tax' => 0, - 'country' => $country, - }); - my $error = $cust_main_county->insert; - die $error if $error; - - } else { - - if ( $states[0] =~ /^(\d+|\w)$/ ) { - @states = map $subcountry->full_name($_), @states - } - - foreach my $state ( @states ) { - - my $cust_main_county = new FS::cust_main_county({ - 'state' => $state, - 'tax' => 0, - 'country' => $country, - }); - my $error = $cust_main_county->insert; - die $error if $error; - - } - - } -} - -#billing events -foreach my $aref ( - #[ 'COMP', 'Comp invoice', '$cust_bill->comp();', 30, 'comp' ], - [ 'CARD', 'Batch card', '$cust_bill->batch_card();', 40, 'batch-card' ], - [ 'BILL', 'Send invoice', '$cust_bill->send();', 50, 'send' ], - [ 'DCRD', 'Send invoice', '$cust_bill->send();', 50, 'send' ], - [ 'DCHK', 'Send invoice', '$cust_bill->send();', 50, 'send' ], -) { - - my $part_bill_event = new FS::part_bill_event({ - 'payby' => $aref->[0], - 'event' => $aref->[1], - 'eventcode' => $aref->[2], - 'seconds' => 0, - 'weight' => $aref->[3], - 'plan' => $aref->[4], - }); - my($error); - $error=$part_bill_event->insert; - die $error if $error; - -} +warn "Freeside database initialized - commiting transaction\n" if $opt_v; $dbh->commit or die $dbh->errstr; $dbh->disconnect or die $dbh->errstr; -#print "Freeside database initialized sucessfully\n"; +warn "Database initialization committed sucessfully\n" if $opt_v; sub dbdef_create { # reverse engineer the schema from the DB and save to file my( $dbh, $file ) = @_; @@ -175,7 +118,7 @@ sub dbdef_create { # reverse engineer the schema from the DB and save to file } sub usage { - die "Usage:\n freeside-setup user\n"; + die "Usage:\n freeside-setup -d domain.name [ -v ] user\n"; } 1; diff --git a/bin/populate-msgcat b/bin/populate-msgcat deleted file mode 100755 index adac92dd0..000000000 --- a/bin/populate-msgcat +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/perl -Tw - -use strict; -use FS::UID qw(adminsuidsetup); -use FS::Record qw(qsearch); -use FS::msgcat; - -my $user = shift or die &usage; -adminsuidsetup $user; - -foreach my $del_msgcat ( qsearch('msgcat', {}) ) { - my $error = $del_msgcat->delete; - die $error if $error; -} - -my %messages = messages(); - -foreach my $msgcode ( keys %messages ) { - foreach my $locale ( keys %{$messages{$msgcode}} ) { - my $msgcat = new FS::msgcat( { - 'msgcode' => $msgcode, - 'locale' => $locale, - 'msg' => $messages{$msgcode}{$locale}, - }); - my $error = $msgcat->insert; - die $error if $error; - } -} - -#print "Message catalog initialized sucessfully\n"; - -sub messages { - - # 'msgcode' => { - # 'en_US' => 'Message', - # }, - - ( - - 'passwords_dont_match' => { - 'en_US' => "Passwords don't match", - }, - - 'invalid_card' => { - 'en_US' => 'Invalid credit card number', - }, - - 'unknown_card_type' => { - 'en_US' => 'Unknown card type', - }, - - 'not_a' => { - 'en_US' => 'Not a ', - }, - - 'empty_password' => { - 'en_US' => 'Empty password', - }, - - 'no_access_number_selected' => { - 'en_US' => 'No access number selected', - }, - - 'illegal_text' => { - 'en_US' => 'Illegal (text)', - #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in field', - }, - - 'illegal_or_empty_text' => { - 'en_US' => 'Illegal or empty (text)', - #'en_US' => 'Only letters, numbers, spaces, and the following punctuation symbols are permitted: ! @ # $ % & ( ) - + ; : \' " , . ? / in required field', - }, - - 'illegal_username' => { - 'en_US' => 'Illegal username', - }, - - 'illegal_password' => { - 'en_US' => 'Illegal password (', - }, - - 'illegal_password_characters' => { - 'en_US' => ' characters)', - }, - - 'username_in_use' => { - 'en_US' => 'Username in use', - }, - - 'illegal_email_invoice_address' => { - 'en_US' => 'Illegal email invoice address', - }, - - 'illegal_name' => { - 'en_US' => 'Illegal (name)', - #'en_US' => 'Only letters, numbers, spaces and the following punctuation symbols are permitted: , . - \' in field', - }, - - 'illegal_phone' => { - 'en_US' => 'Illegal (phone)', - #'en_US' => '', - }, - - 'illegal_zip' => { - 'en_US' => 'Illegal (zip)', - #'en_US' => '', - }, - - 'expired_card' => { - 'en_US' => 'Expired card', - }, - - 'daytime' => { - 'en_US' => 'Day Phone', - }, - - 'night' => { - 'en_US' => 'Night Phone', - }, - - 'svc_external-id' => { - 'en_US' => 'External ID', - }, - - 'svc_external-title' => { - 'en_US' => 'Title', - }, - - ); -} - -sub usage { - die "Usage:\n\n populate-msgcat user\n"; -} - diff --git a/httemplate/docs/admin.html b/httemplate/docs/admin.html index 9ce259c2b..2aa934812 100755 --- a/httemplate/docs/admin.html +++ b/httemplate/docs/admin.html @@ -11,49 +11,8 @@ /home/httpd/html, open https://your_host/freeside/. Replace "your_host" with the name or network address of your web server.
    • Select Configuration from the main menu and update your configuration values. -
    • Next you must create a service definition. An example of a service - definition would be a dial-up account or a domain. First, it is - necessary to create a domain definition. Click on View/Edit service - definitions and Add a new service definition with Table - svc_domain (and no modifiers). -
    • Now that you have created your first service, you must create a package - including this service which you can sell to customers. Zero, one, or many - services are bundled into a package. Click on View/Edit package - definitions and Add a new package definition which includes - quantity 1 of the svc_domain service you created above. - -
    • After you create your first package, then you must define who is - able to sell that package by creating an agent type. An example of - an agent type would be an internal sales representitive which sells - regular and promotional packages, as opposed to an external sales - representitive which would only sell regular packages of services. Click on - View/Edit agent types and Add a new agent type. Allow this - agent type to sell the package you created above. - -
    • After creating a new agent type, you must create an agent. Click on - View/Edit agents and Add a new agent. - -
    • Set up at least one Advertising source. Advertising sources will help - you keep track of how effective your advertising is, tracking where customers - heard of your service offerings. You must create at least one advertising - source. If you do not wish to use the referral functionality, simply create - a single advertising source only. Click on View/Edit advertising - sources and Add a new advertising source. - -
    • Click on New Customer and create a new customer for your system - accounts with billing type Complimentary. Leave the First - package dropdown set to (none). - -
    • From the Customer View screen of the newly created customer, order the - package you defined above. - -
    • From the Package View screen of the newly created package, choose - (Provision) to add the customer's service for this new package. - -
    • Add your own domain. - -
    • Go back to View/Edit service definitions on the main menu, and +
    • Go to View/Edit service definitions on the main menu, and Add a new service definition with Table svc_acct. Select your domain in the domsvc Modifier. Set Fixed to define a service locked-in to this domain, or Default to define a service @@ -69,7 +28,7 @@
    • If you are using Freeside to keep track of sales taxes, define tax information for your locales by clicking on the View/Edit locales and tax - rates on the main menu. + rates on the main menu.
    • If you would like Freeside to notify your customers when their credit cards and other billing arrangements are about to expire, arrange for diff --git a/httemplate/docs/install.html b/httemplate/docs/install.html index 1f80db1a7..02572759e 100644 --- a/httemplate/docs/install.html +++ b/httemplate/docs/install.html @@ -196,17 +196,10 @@ require valid-user
      $ su
       # freeside-adduser fs_queue
       # freeside-adduser fs_selfservice
      -
    • As the freeside UNIX user, run freeside-setup username to create the database tables, passing the username of a Freeside user you created above: +
    • As the freeside UNIX user, run freeside-setup -d domain.name username to create the database tables and initial data, passing the username of a Freeside user you created above:
       $ su freeside
      -$ freeside-setup username
      -
      - Alternately, use the -s option to enable shipping addresses: freeside-setup -s username -
    • As the freeside UNIX user, run bin/populate-msgcat username (in the untar'ed freeside directory) to populate the message catalog, passing the username of a Freeside user you created above: -
      -$ su freeside
      -$ cd /path/to/freeside/
      -$ bin/populate-msgcat username
      +$ freeside-setup -d example.com username
       
    • freeside-queued was installed with the Perl modules. Start it now and ensure that is run upon system startup (Do this manually, or edit the top-level Makefile, replacing INIT_FILE with the appropriate location on your systemand QUEUED_USER with the username of a Freeside user you created above, and run make install-init)
    • Now proceed to the initial administration of your installation. -- cgit v1.2.1 From d8b70477cf7fd3b87464940f13e85547ccdbd31d Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 18 Feb 2006 02:11:44 +0000 Subject: update POD documentation left behind from example template --- FS/FS/cust_tax_exempt_pkg.pm | 6 +++--- FS/FS/domain_record.pm | 4 ++-- FS/FS/msgcat.pm | 7 ++++--- FS/FS/nas.pm | 2 +- FS/FS/part_bill_event.pm | 4 ++-- FS/FS/port.pm | 4 ++-- FS/FS/prepay_credit.pm | 2 +- FS/FS/queue.pm | 2 +- FS/FS/queue_arg.pm | 2 +- FS/FS/rate_detail.pm | 5 +++-- FS/FS/reg_code_pkg.pm | 3 ++- 11 files changed, 22 insertions(+), 19 deletions(-) diff --git a/FS/FS/cust_tax_exempt_pkg.pm b/FS/FS/cust_tax_exempt_pkg.pm index 28fa24327..128921b9c 100644 --- a/FS/FS/cust_tax_exempt_pkg.pm +++ b/FS/FS/cust_tax_exempt_pkg.pm @@ -56,8 +56,8 @@ inherits from FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new exemption record. To add the example to the database, see -L<"insert">. +Creates a new exemption record. To add the examption record to the database, +see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. @@ -96,7 +96,7 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid exemption record. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. diff --git a/FS/FS/domain_record.pm b/FS/FS/domain_record.pm index 3c65a1a05..6513abf25 100644 --- a/FS/FS/domain_record.pm +++ b/FS/FS/domain_record.pm @@ -59,7 +59,7 @@ supported: =item new HASHREF -Creates a new entry. To add the example to the database, see L<"insert">. +Creates a new entry. To add the entry to the database, see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. @@ -229,7 +229,7 @@ sub replace { =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid entry. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. diff --git a/FS/FS/msgcat.pm b/FS/FS/msgcat.pm index 855b8b291..cbdc1d633 100644 --- a/FS/FS/msgcat.pm +++ b/FS/FS/msgcat.pm @@ -52,7 +52,8 @@ If you just want to B message catalogs, see L. =item new HASHREF -Creates a new example. To add the example to the database, see L<"insert">. +Creates a new message catalog entry. To add the message catalog entry to the +database, see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. @@ -91,8 +92,8 @@ 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 +Checks all fields to make sure this is a valid message catalog entry. If there +is an error, returns the error, otherwise returns false. Called by the insert and replace methods. =cut diff --git a/FS/FS/nas.pm b/FS/FS/nas.pm index 3495339e0..97b0ea17d 100644 --- a/FS/FS/nas.pm +++ b/FS/FS/nas.pm @@ -98,7 +98,7 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid NAS. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. diff --git a/FS/FS/part_bill_event.pm b/FS/FS/part_bill_event.pm index 8143e3473..98e15d4a2 100644 --- a/FS/FS/part_bill_event.pm +++ b/FS/FS/part_bill_event.pm @@ -61,8 +61,8 @@ FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new invoice event definition. To add the example to the database, -see L<"insert">. +Creates a new invoice event definition. To add the invoice event definition to +the database, see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. diff --git a/FS/FS/port.pm b/FS/FS/port.pm index 253727ba7..c26ca85d4 100644 --- a/FS/FS/port.pm +++ b/FS/FS/port.pm @@ -52,7 +52,7 @@ from FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new port. To add the example to the database, see L<"insert">. +Creates a new port. To add the port to the database, see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. @@ -91,7 +91,7 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid port. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. diff --git a/FS/FS/prepay_credit.pm b/FS/FS/prepay_credit.pm index 92f2a3032..38e87ad91 100644 --- a/FS/FS/prepay_credit.pm +++ b/FS/FS/prepay_credit.pm @@ -61,7 +61,7 @@ fields are currently supported: =item new HASHREF -Creates a new pre-paid credit. To add the example to the database, see +Creates a new pre-paid credit. To add the pre-paid credit to the database, see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it diff --git a/FS/FS/queue.pm b/FS/FS/queue.pm index f42d99837..5f8bf11f0 100644 --- a/FS/FS/queue.pm +++ b/FS/FS/queue.pm @@ -68,7 +68,7 @@ FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new job. To add the example to the database, see L<"insert">. +Creates a new job. To add the job to the database, see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. diff --git a/FS/FS/queue_arg.pm b/FS/FS/queue_arg.pm index 39c71192f..c96ff1236 100644 --- a/FS/FS/queue_arg.pm +++ b/FS/FS/queue_arg.pm @@ -46,7 +46,7 @@ FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new argument. To add the example to the database, see L<"insert">. +Creates a new argument. To add the argument to the database, see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. diff --git a/FS/FS/rate_detail.pm b/FS/FS/rate_detail.pm index 7d54355fb..1964be2f4 100644 --- a/FS/FS/rate_detail.pm +++ b/FS/FS/rate_detail.pm @@ -56,7 +56,8 @@ inherits from FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new example. To add the example to the database, see L<"insert">. +Creates a new call plan rate. To add the call plan rate to the database, see +L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. @@ -95,7 +96,7 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid call plan rate. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. diff --git a/FS/FS/reg_code_pkg.pm b/FS/FS/reg_code_pkg.pm index 9b9a87712..837b755e6 100644 --- a/FS/FS/reg_code_pkg.pm +++ b/FS/FS/reg_code_pkg.pm @@ -49,7 +49,8 @@ supported: =item new HASHREF -Creates a new example. To add the example to the database, see L<"insert">. +Creates a new registration code. To add the registration code to the database, +see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. -- cgit v1.2.1 From 6d27649c93f3f83c14611c790281a7b667062fc0 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 18 Feb 2006 04:32:57 +0000 Subject: Mason it is --- htetc/global.asa | 263 ------------------------------------------- httemplate/docs/install.html | 39 +------ 2 files changed, 2 insertions(+), 300 deletions(-) delete mode 100644 htetc/global.asa diff --git a/htetc/global.asa b/htetc/global.asa deleted file mode 100644 index bb30608a4..000000000 --- a/htetc/global.asa +++ /dev/null @@ -1,263 +0,0 @@ -BEGIN { eval "use Devel::AutoProfiler;"; } #only if installed... -#BEGIN { package Devel::AutoProfiler; use vars qw(%caller_info); } -#use Devel::AutoProfiler; - -use strict; -use vars qw( $cgi $p ); -use Apache::ASP 2.55; -use CGI 2.47; -#use CGI::Carp qw(fatalsToBrowser); -use Date::Format; -use Date::Parse; -use Time::Local; -use Time::Duration; -use Tie::IxHash; -use URI::Escape; -use HTML::Entities; -use JSON; -use IO::Handle; -use IO::File; -use IO::Scalar; -use Net::Whois::Raw qw(whois); -if ( $] < 5.006 ) { - eval "use Net::Whois::Raw 0.32 qw(whois)"; - die $@ if $@; -} -use Text::CSV_XS; -use Spreadsheet::WriteExcel; -use Business::CreditCard; -use String::Approx qw(amatch); -use Chart::LinesPoints; -use HTML::Widgets::SelectLayers 0.05; -use FS; -use FS::UID qw(cgisuidsetup dbh getotaker datasrc driver_name); -use FS::Record qw(qsearch qsearchs fields dbdef); -use FS::Conf; -use FS::CGI qw(header menubar popurl table itable ntable idiot eidiot - small_custview myexit http_header); -use FS::UI::Web; -use FS::Msgcat qw(gettext geterror); -use FS::Misc qw( send_email ); -use FS::Report::Table::Monthly; -use FS::TicketSystem; - -use FS::agent; -use FS::agent_type; -use FS::domain_record; -use FS::cust_bill; -use FS::cust_bill_pay; -use FS::cust_credit; -use FS::cust_credit_bill; -use FS::cust_main qw(smart_search); -use FS::cust_main_county; -use FS::cust_pay; -use FS::cust_pkg; -use FS::cust_refund; -use FS::cust_svc; -use FS::nas; -use FS::part_bill_event; -use FS::part_pkg; -use FS::part_referral; -use FS::part_svc; -use FS::part_svc_router; -use FS::part_virtual_field; -use FS::pkg_svc; -use FS::port; -use FS::queue qw(joblisting); -use FS::raddb; -use FS::session; -use FS::svc_acct; -use FS::svc_acct_pop qw(popselector); -use FS::svc_domain; -use FS::svc_forward; -use FS::svc_www; -use FS::router; -use FS::addr_block; -use FS::svc_broadband; -use FS::svc_external; -use FS::type_pkgs; -use FS::part_export; -use FS::part_export_option; -use FS::export_svc; -use FS::msgcat; -use FS::rate; -use FS::rate_region; -use FS::rate_prefix; -use FS::payment_gateway; -use FS::agent_payment_gateway; - -sub Script_OnStart { - $Response->AddHeader('Cache-control' => 'no-cache'); -# $Response->AddHeader('Expires' => 0); - $Response->{Expires} = -36288000; - - $cgi = new CGI; - &cgisuidsetup($cgi); - $p = popurl(2); - #print $cgi->header( '-expires' => 'now' ); - #dbh->{'private_profile'} = {} if dbh->can('sprintProfile'); - dbh->{'private_profile'} = {} if UNIVERSAL::can(dbh, 'sprintProfile'); - - #really should check for FS::Profiler or something - # Devel::AutoProfiler _our_ VERSION? thanks a fucking lot - if ( Devel::AutoProfiler->can('__recursively_fetch_subs_in_package') ) { - #should check to see it's my special version. well, switch to FS::Profiler - - #nicked from Devel::AutoProfiler::INIT - my %subs = Devel::AutoProfiler::__recursively_fetch_subs_in_package('main'); - - - SUB : while( my ($name, $ref) = each(%subs) ) - { - #next if $name =~ /^(main::)?Apache::/; - next unless $name =~ /FS/; - foreach my $sub (@Devel::AutoProfiler::do_not_instrument_this_sub) - { - if ($name =~ /$sub/) - { - next SUB; - } - } - next if ($Devel::AutoProfiler::do_not_instrument_this_sub{$name}); - #warn "INIT name is $name \n"; - Devel::AutoProfiler::__instrument_sub($name, $ref); - } - - } - -} - -sub Script_OnFlush { - my $ref = $Response->{BinaryRef}; - #$$ref = $cgi->header( @FS::CGI::header ) . $$ref; - #$$ref = $cgi->header() . $$ref; - #warn "Script_OnFlush called with dbh ". dbh. "\n"; - #if ( dbh->can('sprintProfile') ) { - if ( UNIVERSAL::can(dbh, 'sprintProfile') ) { - #warn "dbh can sprintProfile\n"; - if ( lc($Response->{ContentType}) eq 'text/html' ) { #con - #warn "contenttype is sprintProfile\n"; - $$ref =~ s/<\/BODY>[\s\n]*<\/HTML>[\s\n]*$//i - or warn "can't remove"; - - #$$ref .= '
      '. ("\n"x96). encode_entities(dbh->sprintProfile()). '
      '; - # wtf? konqueror... - $$ref .= '
      '. ("\n"x4096). encode_entities(dbh->sprintProfile()).
      -               "\n\n". &sprintAutoProfile(). '
      '; - - $$ref .= ''; - } - dbh->{'private_profile'} = {}; - } -} - -#if ( defined(@DBIx::Profile::ISA) && DBIx::Profile::db->can('sprintProfile') ) { -#if ( defined(@DBIx::Profile::ISA) && UNIVERSAL::can('DBIx::Profile::db', 'sprintProfile') ) { -if ( defined(@DBIx::Profile::ISA) ) { - - #warn "enabling profiling redirects"; - *CGI::redirect = sub { - my( $self, $location) = @_; - my $page = - $cgi->header. - qq!Redirect to $location!. - '

      '.
      -        ( UNIVERSAL::can(dbh, 'sprintProfile')
      -            ? encode_entities(dbh->sprintProfile())
      -            : 'DBIx::Profile missing sprintProfile method;'.
      -              'unpatched or too old?'                        ).
      -      "\n\n". &sprintAutoProfile().  '
      '. - ''; - dbh->{'private_profile'} = {}; - return $page; - }; - -} - -sub by_total_time -{ - return $a->{total_time_in_sub} <=> $b->{total_time_in_sub}; -} - -sub sprintAutoProfile { - my %caller_info = %Devel::AutoProfiler::caller_info; - return unless keys %caller_info; - - %Devel::AutoProfiler::caller_info = (); - - my @keys = keys(%caller_info); - - foreach my $key (@keys) - { - my $href = $caller_info{$key}; - - $href->{who_am_i} = $key; - } - - my @subs = values(%caller_info); - - #my @sorted = sort by_total_time ( @subs ); - my @sorted = reverse sort by_total_time ( @subs ); - - # print Dumper \@sorted; - - my @readable_info; - - foreach my $sort (@sorted) - { - push(@readable_info, delete($sort->{who_am_i})); - push(@readable_info, $sort); - } - - use Data::Dumper; - return encode_entities(Dumper(\@readable_info)); - -} - -sub include { - my $file = shift; - my $shift = 0; - if ( $file =~ m(^([^/].*)/[^/]+) ) { - unshift @{$Response->{asp}{includes_dir}}, "./$1"; - $shift = 1; - } - $file =~ s(^/)(%%%FREESIDE_DOCUMENT_ROOT%%%/); - #broken in 5.005# ${$Response->TrapInclude($file, @_)}; - my $ref = $Response->TrapInclude($file, @_); - shift @{$Response->{asp}{includes_dir}} if $shift; - $$ref; -} - -if ( defined(@DBIx::Profile::ISA) ) { - - #false laziness w/above - *redirect = sub { - my($location) = @_; - - ${$Response->{BinaryRef}} = - $cgi->header. - qq!Redirect to $location!. - '

      '.
      -        ( UNIVERSAL::can(dbh, 'sprintProfile')
      -            ? encode_entities(dbh->sprintProfile())
      -            : 'DBIx::Profile missing sprintProfile method;'.
      -              'unpatched or too old?'                        ).
      -      "\n\n". &sprintAutoProfile().  '
      '. - ''; - - dbh->{'private_profile'} = {}; - - $Response->End; - - }; - -} else { - - *redirect = sub { - $Response->Redirect(@_); - } - -} - -1; - diff --git a/httemplate/docs/install.html b/httemplate/docs/install.html index 02572759e..6e366cbbb 100644 --- a/httemplate/docs/install.html +++ b/httemplate/docs/install.html @@ -52,7 +52,7 @@ Before installing, you need:
    • Net-SSH
    • String-ShellQuote
    • Net-SCP -
    • HTML::Mason (recommended, enables full functionality) or Apache::ASP (deprecated, integrated RT ticketing will not be available) +
    • HTML::Mason
    • Tie-IxHash
    • Time-Duration
    • HTML-Widgets-SelectLayers @@ -123,15 +123,7 @@ $ su
    • Run a separate iteration of Apache[-SSL] with mod_perl enabled as the freeside user.
    • Edit the Makefile and set TEMPLATE to asp or mason. Also set FREESIDE_DOCUMENT_ROOT.
    • Run make install-docs. -
    - - - - - - - - - - - -
    Mason (recommended)Apache::ASP (deprecated)
      -
    • Configure Apache: +
    • Configure Apache:
       PerlModule HTML::Mason
       # your freeside docuemnt root
      @@ -145,33 +137,6 @@ require "/usr/local/etc/freeside/handler.pl";
       </Perl>
       </Directory>
       
      -
      -
    • Configure Apache: -
      -PerlModule Apache::ASP
      -# your freeside document root
      -<Directory /var/www/freeside>
      -<Files ~ (\.cgi|\.html)>
      -AddHandler perl-script .cgi .html
      -PerlHandler Apache::ASP
      -</Files>
      -<Perl>
      -$MLDBM::RemoveTaint = 1;
      -</Perl>
      -PerlSetVar Global /usr/local/etc/freeside/asp-global/
      -PerlSetVar Debug 2
      -PerlSetVar RequestBinaryRead Off
      -# your freeside document root
      -PerlSetVar IncludesDir /var/www/freeside
      -</Directory>
      -
      -
    -
    • Restrict access to this web interface - see the Apache documentation on user authentication. For example, to configure user authentication with mod_auth (flat files), add something like the following to your Apache httpd.conf file, adjusting for your actual paths:
       #your freeside document root
      -- 
      cgit v1.2.1
      
      
      From fadaa67e77ad8d5d966e252aba7f193e9e3840e3 Mon Sep 17 00:00:00 2001
      From: ivan 
      Date: Sat, 18 Feb 2006 11:14:21 +0000
      Subject: CDR schema and class
      
      ---
       FS/FS/Schema.pm                         | 1004 +++++++++++++++++--------------
       FS/FS/cdr.pm                            |  385 ++++++++++++
       FS/FS/cdr_calltype.pm                   |  115 ++++
       FS/FS/cdr_carrier.pm                    |  116 ++++
       FS/FS/cdr_type.pm                       |  119 ++++
       FS/FS/cust_main.pm                      |    5 +-
       FS/FS/part_pkg/voip_sqlradacct.pm       |    1 +
       FS/MANIFEST                             |   11 +-
       FS/t/cdr.t                              |    5 +
       FS/t/cdr_calltype.t                     |    5 +
       FS/t/cdr_carrier.t                      |    5 +
       FS/t/cdr_type.t                         |    5 +
       FS/t/part_pkg-voip_cdr.t                |    5 +
       README.1.7.0                            |   19 +
       README.2.0.0                            |   17 -
       bin/cdr_calltype.import                 |   41 ++
       htetc/handler.pl                        |    9 +-
       httemplate/misc/cdr-import.html         |   15 +
       httemplate/misc/process/cdr-import.html |   26 +
       httemplate/search/cdr.html              |   20 +
       httemplate/search/report_cdr.html       |    7 +
       21 files changed, 1469 insertions(+), 466 deletions(-)
       create mode 100644 FS/FS/cdr.pm
       create mode 100644 FS/FS/cdr_calltype.pm
       create mode 100644 FS/FS/cdr_carrier.pm
       create mode 100644 FS/FS/cdr_type.pm
       create mode 100644 FS/t/cdr.t
       create mode 100644 FS/t/cdr_calltype.t
       create mode 100644 FS/t/cdr_carrier.t
       create mode 100644 FS/t/cdr_type.t
       create mode 100644 FS/t/part_pkg-voip_cdr.t
       create mode 100644 README.1.7.0
       delete mode 100644 README.2.0.0
       create mode 100755 bin/cdr_calltype.import
       create mode 100644 httemplate/misc/cdr-import.html
       create mode 100644 httemplate/misc/process/cdr-import.html
       create mode 100644 httemplate/search/cdr.html
       create mode 100644 httemplate/search/report_cdr.html
      
      diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
      index 33d0fd6d8..3ca599b49 100644
      --- a/FS/FS/Schema.pm
      +++ b/FS/FS/Schema.pm
      @@ -3,9 +3,9 @@ package FS::Schema;
       use vars qw(@ISA @EXPORT_OK $DEBUG $setup_hack %dbdef_cache);
       use subs qw(reload_dbdef);
       use Exporter;
      -use DBIx::DBSchema 0.25;
      +use DBIx::DBSchema 0.30;
       use DBIx::DBSchema::Table;
      -use DBIx::DBSchema::Column;
      +use DBIx::DBSchema::Column 0.06;
       use DBIx::DBSchema::ColGroup::Unique;
       use DBIx::DBSchema::ColGroup::Index;
       use FS::UID qw(datasrc);
      @@ -92,9 +92,18 @@ sub dbdef_dist {
         my $dbdef = new DBIx::DBSchema map {  
           my @columns;
           while (@{$tables_hashref->{$_}{'columns'}}) {
      -      my($name, $type, $null, $length) =
      -        splice @{$tables_hashref->{$_}{'columns'}}, 0, 4;
      -      push @columns, new DBIx::DBSchema::Column ( $name,$type,$null,$length );
      +      #my($name, $type, $null, $length, $default, $local) =
      +      my @coldef = 
      +        splice @{$tables_hashref->{$_}{'columns'}}, 0, 6;
      +      my %hash = map { $_ => shift @coldef }
      +                     qw( name type null length default local );
      +
      +      unless ( defined $hash{'default'} ) {
      +        warn "$_:\n".
      +             join('', map "$_ => $hash{$_}\n", keys %hash) ;# $stop = ;
      +      }
      +
      +      push @columns, new DBIx::DBSchema::Column ( \%hash );
           }
           DBIx::DBSchema::Table->new(
             $_,
      @@ -239,15 +248,15 @@ sub tables_hashref {
       
           'agent' => {
             'columns' => [
      -        'agentnum', 'serial',            '',     '',
      -        'agent',    'varchar',           '',     $char_d,
      -        'typenum',  'int',            '',     '',
      -        'freq',     'int',       'NULL', '',
      -        'prog',     @perl_type,
      -        'disabled',     'char', 'NULL', 1,
      -        'username', 'varchar',       'NULL',     $char_d,
      -        '_password','varchar',       'NULL',     $char_d,
      -        'ticketing_queueid', 'int', 'NULL', '',
      +        'agentnum', 'serial',            '',     '', '', '', 
      +        'agent',    'varchar',           '',     $char_d, '', '', 
      +        'typenum',  'int',            '',     '', '', '', 
      +        'freq',     'int',       'NULL', '', '', '', 
      +        'prog',     @perl_type, '', '', 
      +        'disabled',     'char', 'NULL', 1, '', '', 
      +        'username', 'varchar',       'NULL',     $char_d, '', '', 
      +        '_password','varchar',       'NULL',     $char_d, '', '', 
      +        'ticketing_queueid', 'int', 'NULL', '', '', '', 
             ],
             'primary_key' => 'agentnum',
             'unique' => [],
      @@ -256,8 +265,8 @@ sub tables_hashref {
       
           'agent_type' => {
             'columns' => [
      -        'typenum',   'serial',  '', '',
      -        'atype',     'varchar', '', $char_d,
      +        'typenum',   'serial',  '', '', '', '', 
      +        'atype',     'varchar', '', $char_d, '', '', 
             ],
             'primary_key' => 'typenum',
             'unique' => [],
      @@ -266,9 +275,9 @@ sub tables_hashref {
       
           'type_pkgs' => {
             'columns' => [
      -        'typepkgnum', 'serial', '', '',
      -        'typenum',   'int',  '', '',
      -        'pkgpart',   'int',  '', '',
      +        'typepkgnum', 'serial', '', '', '', '', 
      +        'typenum',   'int',  '', '', '', '', 
      +        'pkgpart',   'int',  '', '', '', '', 
             ],
             'primary_key' => 'typepkgnum',
             'unique' => [ ['typenum', 'pkgpart'] ],
      @@ -277,12 +286,12 @@ sub tables_hashref {
       
           'cust_bill' => {
             'columns' => [
      -        'invnum',    'serial',  '', '',
      -        'custnum',   'int',  '', '',
      -        '_date',     @date_type,
      -        'charged',   @money_type,
      -        'printed',   'int',  '', '',
      -        'closed',    'char', 'NULL', 1,
      +        'invnum',    'serial',  '', '', '', '', 
      +        'custnum',   'int',  '', '', '', '', 
      +        '_date',     @date_type, '', '', 
      +        'charged',   @money_type, '', '', 
      +        'printed',   'int',  '', '', '', '', 
      +        'closed',    'char', 'NULL', 1, '', '', 
             ],
             'primary_key' => 'invnum',
             'unique' => [],
      @@ -291,12 +300,12 @@ sub tables_hashref {
       
           'cust_bill_event' => {
             'columns' => [
      -        'eventnum',    'serial',  '', '',
      -        'invnum',   'int',  '', '',
      -        'eventpart',   'int',  '', '',
      -        '_date',     @date_type,
      -        'status', 'varchar', '', $char_d,
      -        'statustext', 'text', 'NULL', '',
      +        'eventnum',    'serial',  '', '', '', '', 
      +        'invnum',   'int',  '', '', '', '', 
      +        'eventpart',   'int',  '', '', '', '', 
      +        '_date',     @date_type, '', '', 
      +        'status', 'varchar', '', $char_d, '', '', 
      +        'statustext', 'text', 'NULL', '', '', '', 
             ],
             'primary_key' => 'eventnum',
             #no... there are retries now #'unique' => [ [ 'eventpart', 'invnum' ] ],
      @@ -306,16 +315,16 @@ sub tables_hashref {
       
           'part_bill_event' => {
             'columns' => [
      -        'eventpart',    'serial',  '', '',
      -        'freq',        'varchar',       'NULL',     $char_d,
      -        'payby',       'char',  '', 4,
      -        'event',       'varchar',           '',     $char_d,
      -        'eventcode',    @perl_type,
      -        'seconds',     'int', 'NULL', '',
      -        'weight',      'int', '', '',
      -        'plan',       'varchar', 'NULL', $char_d,
      -        'plandata',   'text', 'NULL', '',
      -        'disabled',     'char', 'NULL', 1,
      +        'eventpart',    'serial',  '', '', '', '', 
      +        'freq',        'varchar',       'NULL',     $char_d, '', '', 
      +        'payby',       'char',  '', 4, '', '', 
      +        'event',       'varchar',           '',     $char_d, '', '', 
      +        'eventcode',    @perl_type, '', '', 
      +        'seconds',     'int', 'NULL', '', '', '', 
      +        'weight',      'int', '', '', '', '', 
      +        'plan',       'varchar', 'NULL', $char_d, '', '', 
      +        'plandata',   'text', 'NULL', '', '', '', 
      +        'disabled',     'char', 'NULL', 1, '', '', 
             ],
             'primary_key' => 'eventpart',
             'unique' => [],
      @@ -324,14 +333,14 @@ sub tables_hashref {
       
           'cust_bill_pkg' => {
             'columns' => [
      -        'billpkgnum', 'serial', '', '',
      -        'pkgnum',  'int', '', '',
      -        'invnum',  'int', '', '',
      -        'setup',   @money_type,
      -        'recur',   @money_type,
      -        'sdate',   @date_type,
      -        'edate',   @date_type,
      -        'itemdesc', 'varchar', 'NULL', $char_d,
      +        'billpkgnum', 'serial', '', '', '', '', 
      +        'pkgnum',  'int', '', '', '', '', 
      +        'invnum',  'int', '', '', '', '', 
      +        'setup',   @money_type, '', '', 
      +        'recur',   @money_type, '', '', 
      +        'sdate',   @date_type, '', '', 
      +        'edate',   @date_type, '', '', 
      +        'itemdesc', 'varchar', 'NULL', $char_d, '', '', 
             ],
             'primary_key' => 'billpkgnum',
             'unique' => [],
      @@ -340,10 +349,10 @@ sub tables_hashref {
       
           'cust_bill_pkg_detail' => {
             'columns' => [
      -        'detailnum', 'serial', '', '',
      -        'pkgnum',  'int', '', '',
      -        'invnum',  'int', '', '',
      -        'detail',  'varchar', '', $char_d,
      +        'detailnum', 'serial', '', '', '', '', 
      +        'pkgnum',  'int', '', '', '', '', 
      +        'invnum',  'int', '', '', '', '', 
      +        'detail',  'varchar', '', $char_d, '', '', 
             ],
             'primary_key' => 'detailnum',
             'unique' => [],
      @@ -352,13 +361,13 @@ sub tables_hashref {
       
           'cust_credit' => {
             'columns' => [
      -        'crednum',  'serial', '', '',
      -        'custnum',  'int', '', '',
      -        '_date',    @date_type,
      -        'amount',   @money_type,
      -        'otaker',   'varchar', '', 32,
      -        'reason',   'text', 'NULL', '',
      -        'closed',    'char', 'NULL', 1,
      +        'crednum',  'serial', '', '', '', '', 
      +        'custnum',  'int', '', '', '', '', 
      +        '_date',    @date_type, '', '', 
      +        'amount',   @money_type, '', '', 
      +        'otaker',   'varchar', '', 32, '', '', 
      +        'reason',   'text', 'NULL', '', '', '', 
      +        'closed',    'char', 'NULL', 1, '', '', 
             ],
             'primary_key' => 'crednum',
             'unique' => [],
      @@ -367,11 +376,11 @@ sub tables_hashref {
       
           'cust_credit_bill' => {
             'columns' => [
      -        'creditbillnum', 'serial', '', '',
      -        'crednum',  'int', '', '',
      -        'invnum',  'int', '', '',
      -        '_date',    @date_type,
      -        'amount',   @money_type,
      +        'creditbillnum', 'serial', '', '', '', '', 
      +        'crednum',  'int', '', '', '', '', 
      +        'invnum',  'int', '', '', '', '', 
      +        '_date',    @date_type, '', '', 
      +        'amount',   @money_type, '', '', 
             ],
             'primary_key' => 'creditbillnum',
             'unique' => [],
      @@ -380,54 +389,54 @@ sub tables_hashref {
       
           'cust_main' => {
             'columns' => [
      -        'custnum',  'serial',  '',     '',
      -        'agentnum', 'int',  '',     '',
      -#        'titlenum', 'int',  'NULL',   '',
      -        'last',     'varchar', '',     $char_d,
      -#        'middle',   'varchar', 'NULL', $char_d,
      -        'first',    'varchar', '',     $char_d,
      -        'ss',       'varchar', 'NULL', 11,
      -        'company',  'varchar', 'NULL', $char_d,
      -        'address1', 'varchar', '',     $char_d,
      -        'address2', 'varchar', 'NULL', $char_d,
      -        'city',     'varchar', '',     $char_d,
      -        'county',   'varchar', 'NULL', $char_d,
      -        'state',    'varchar', 'NULL', $char_d,
      -        'zip',      'varchar', 'NULL', 10,
      -        'country',  'char', '',     2,
      -        'daytime',  'varchar', 'NULL', 20,
      -        'night',    'varchar', 'NULL', 20,
      -        'fax',      'varchar', 'NULL', 12,
      -        'ship_last',     'varchar', 'NULL', $char_d,
      -#        'ship_middle',   'varchar', 'NULL', $char_d,
      -        'ship_first',    'varchar', 'NULL', $char_d,
      -        'ship_company',  'varchar', 'NULL', $char_d,
      -        'ship_address1', 'varchar', 'NULL', $char_d,
      -        'ship_address2', 'varchar', 'NULL', $char_d,
      -        'ship_city',     'varchar', 'NULL', $char_d,
      -        'ship_county',   'varchar', 'NULL', $char_d,
      -        'ship_state',    'varchar', 'NULL', $char_d,
      -        'ship_zip',      'varchar', 'NULL', 10,
      -        'ship_country',  'char', 'NULL', 2,
      -        'ship_daytime',  'varchar', 'NULL', 20,
      -        'ship_night',    'varchar', 'NULL', 20,
      -        'ship_fax',      'varchar', 'NULL', 12,
      -        'payby',    'char', '',     4,
      -        'payinfo',  'varchar', 'NULL', 512,
      -        'paycvv',   'varchar', 'NULL', 512,
      -	'paymask', 'varchar', 'NULL', $char_d,
      -        #'paydate',  @date_type,
      -        'paydate',  'varchar', 'NULL', 10,
      -        'paystart_month', 'int', 'NULL', '',
      -        'paystart_year',  'int', 'NULL', '',
      -        'payissue', 'varchar', 'NULL', 2,
      -        'payname',  'varchar', 'NULL', $char_d,
      -        'payip',    'varchar', 'NULL', 15,
      -        'tax',      'char', 'NULL', 1,
      -        'otaker',   'varchar', '',    32,
      -        'refnum',   'int',  '',     '',
      -        'referral_custnum', 'int',  'NULL', '',
      -        'comments', 'text', 'NULL', '',
      +        'custnum',  'serial',  '',     '', '', '', 
      +        'agentnum', 'int',  '',     '', '', '', 
      +#        'titlenum', 'int',  'NULL',   '', '', '', 
      +        'last',     'varchar', '',     $char_d, '', '', 
      +#        'middle',   'varchar', 'NULL', $char_d, '', '', 
      +        'first',    'varchar', '',     $char_d, '', '', 
      +        'ss',       'varchar', 'NULL', 11, '', '', 
      +        'company',  'varchar', 'NULL', $char_d, '', '', 
      +        'address1', 'varchar', '',     $char_d, '', '', 
      +        'address2', 'varchar', 'NULL', $char_d, '', '', 
      +        'city',     'varchar', '',     $char_d, '', '', 
      +        'county',   'varchar', 'NULL', $char_d, '', '', 
      +        'state',    'varchar', 'NULL', $char_d, '', '', 
      +        'zip',      'varchar', 'NULL', 10, '', '', 
      +        'country',  'char', '',     2, '', '', 
      +        'daytime',  'varchar', 'NULL', 20, '', '', 
      +        'night',    'varchar', 'NULL', 20, '', '', 
      +        'fax',      'varchar', 'NULL', 12, '', '', 
      +        'ship_last',     'varchar', 'NULL', $char_d, '', '', 
      +#        'ship_middle',   'varchar', 'NULL', $char_d, '', '', 
      +        'ship_first',    'varchar', 'NULL', $char_d, '', '', 
      +        'ship_company',  'varchar', 'NULL', $char_d, '', '', 
      +        'ship_address1', 'varchar', 'NULL', $char_d, '', '', 
      +        'ship_address2', 'varchar', 'NULL', $char_d, '', '', 
      +        'ship_city',     'varchar', 'NULL', $char_d, '', '', 
      +        'ship_county',   'varchar', 'NULL', $char_d, '', '', 
      +        'ship_state',    'varchar', 'NULL', $char_d, '', '', 
      +        'ship_zip',      'varchar', 'NULL', 10, '', '', 
      +        'ship_country',  'char', 'NULL', 2, '', '', 
      +        'ship_daytime',  'varchar', 'NULL', 20, '', '', 
      +        'ship_night',    'varchar', 'NULL', 20, '', '', 
      +        'ship_fax',      'varchar', 'NULL', 12, '', '', 
      +        'payby',    'char', '',     4, '', '', 
      +        'payinfo',  'varchar', 'NULL', 512, '', '', 
      +        'paycvv',   'varchar', 'NULL', 512, '', '', 
      +	'paymask', 'varchar', 'NULL', $char_d, '', '', 
      +        #'paydate',  @date_type, '', '', 
      +        'paydate',  'varchar', 'NULL', 10, '', '', 
      +        'paystart_month', 'int', 'NULL', '', '', '', 
      +        'paystart_year',  'int', 'NULL', '', '', '', 
      +        'payissue', 'varchar', 'NULL', 2, '', '', 
      +        'payname',  'varchar', 'NULL', $char_d, '', '', 
      +        'payip',    'varchar', 'NULL', 15, '', '', 
      +        'tax',      'char', 'NULL', 1, '', '', 
      +        'otaker',   'varchar', '',    32, '', '', 
      +        'refnum',   'int',  '',     '', '', '', 
      +        'referral_custnum', 'int',  'NULL', '', '', '', 
      +        'comments', 'text', 'NULL', '', '', '', 
             ],
             'primary_key' => 'custnum',
             'unique' => [],
      @@ -440,9 +449,9 @@ sub tables_hashref {
       
           'cust_main_invoice' => {
             'columns' => [
      -        'destnum',  'serial',  '',     '',
      -        'custnum',  'int',  '',     '',
      -        'dest',     'varchar', '',  $char_d,
      +        'destnum',  'serial',  '',     '', '', '', 
      +        'custnum',  'int',  '',     '', '', '', 
      +        'dest',     'varchar', '',  $char_d, '', '', 
             ],
             'primary_key' => 'destnum',
             'unique' => [],
      @@ -453,16 +462,16 @@ sub tables_hashref {
                                   #cust_main_county for validation and to provide
                                   # a tax rate.
             'columns' => [
      -        'taxnum',   'serial',   '',    '',
      -        'state',    'varchar',  'NULL',    $char_d,
      -        'county',   'varchar',  'NULL',    $char_d,
      -        'country',  'char',  '', 2, 
      -        'taxclass',   'varchar', 'NULL', $char_d,
      -        'exempt_amount', @money_type,
      -        'tax',      'real',  '',    '', #tax %
      -        'taxname',  'varchar',  'NULL',    $char_d,
      -        'setuptax',  'char', 'NULL', 1, # Y = setup tax exempt
      -        'recurtax',  'char', 'NULL', 1, # Y = recur tax exempt
      +        'taxnum',   'serial',   '',    '', '', '', 
      +        'state',    'varchar',  'NULL',    $char_d, '', '', 
      +        'county',   'varchar',  'NULL',    $char_d, '', '', 
      +        'country',  'char',  '', 2, '', '', 
      +        'taxclass',   'varchar', 'NULL', $char_d, '', '', 
      +        'exempt_amount', @money_type, '', '', 
      +        'tax',      'real',  '',    '', '', '', #tax %
      +        'taxname',  'varchar',  'NULL',    $char_d, '', '', 
      +        'setuptax',  'char', 'NULL', 1, '', '', # Y = setup tax exempt
      +        'recurtax',  'char', 'NULL', 1, '', '', # Y = recur tax exempt
             ],
             'primary_key' => 'taxnum',
             'unique' => [],
      @@ -472,16 +481,17 @@ sub tables_hashref {
       
           'cust_pay' => {
             'columns' => [
      -        'paynum',   'serial',    '',   '',
      -        #now cust_bill_pay #'invnum',   'int',    '',   '',
      -        'custnum',  'int',    '',   '',
      -        'paid',     @money_type,
      -        '_date',    @date_type,
      -        'payby',    'char',   '',     4, # CARD/BILL/COMP, should be index into
      -                                         # payment type table.
      -        'payinfo',  'varchar',   'NULL', $char_d,  #see cust_main above
      -        'paybatch', 'varchar',   'NULL', $char_d, #for auditing purposes.
      -        'closed',    'char', 'NULL', 1,
      +        'paynum',   'serial',    '',   '', '', '',
      +        #now cust_bill_pay #'invnum',   'int',    '',   '', '', '', 
      +        'custnum',  'int',    '',   '', '', '', 
      +        'paid',     @money_type, '', '', 
      +        '_date',    @date_type, '', '', 
      +        'payby',    'char',   '',     4, '', '', # CARD/BILL/COMP, should be
      +                                                 # index into payby table
      +                                                 # eventually
      +        'payinfo',  'varchar',   'NULL', $char_d, '', '', #see cust_main above
      +        'paybatch', 'varchar',   'NULL', $char_d, '', '', #for auditing purposes.
      +        'closed',    'char', 'NULL', 1, '', '', 
             ],
             'primary_key' => 'paynum',
             'unique' => [],
      @@ -490,18 +500,19 @@ sub tables_hashref {
       
           'cust_pay_void' => {
             'columns' => [
      -        'paynum',    'int',    '',   '',
      -        'custnum',   'int',    '',   '',
      -        'paid',      @money_type,
      -        '_date',     @date_type,
      -        'payby',     'char',   '',     4, # CARD/BILL/COMP, should be index into
      -                                          # payment type table.
      -        'payinfo',   'varchar',   'NULL', $char_d,  #see cust_main above
      -        'paybatch',  'varchar',   'NULL', $char_d, #for auditing purposes.
      -        'closed',    'char', 'NULL', 1,
      -        'void_date', @date_type,
      -        'reason',    'varchar',   'NULL', $char_d,
      -        'otaker',   'varchar', '', 32,
      +        'paynum',    'int',    '',   '', '', '', 
      +        'custnum',   'int',    '',   '', '', '', 
      +        'paid',      @money_type, '', '', 
      +        '_date',     @date_type, '', '', 
      +        'payby',     'char',   '',     4, '', '', # CARD/BILL/COMP, should be
      +                                                  # index into payby table
      +                                                  # eventually
      +        'payinfo',   'varchar',   'NULL', $char_d, '', '', #see cust_main above
      +        'paybatch',  'varchar',   'NULL', $char_d, '', '', #for auditing purposes.
      +        'closed',    'char', 'NULL', 1, '', '', 
      +        'void_date', @date_type, '', '', 
      +        'reason',    'varchar',   'NULL', $char_d, '', '', 
      +        'otaker',   'varchar', '', 32, '', '', 
             ],
             'primary_key' => 'paynum',
             'unique' => [],
      @@ -510,11 +521,11 @@ sub tables_hashref {
       
           'cust_bill_pay' => {
             'columns' => [
      -        'billpaynum', 'serial',     '',   '',
      -        'invnum',  'int',     '',   '',
      -        'paynum',  'int',     '',   '',
      -        'amount',  @money_type,
      -        '_date',   @date_type
      +        'billpaynum', 'serial',     '',   '', '', '', 
      +        'invnum',  'int',     '',   '', '', '', 
      +        'paynum',  'int',     '',   '', '', '', 
      +        'amount',  @money_type, '', '', 
      +        '_date',   @date_type, '', '', 
             ],
             'primary_key' => 'billpaynum',
             'unique' => [],
      @@ -524,23 +535,23 @@ sub tables_hashref {
           'cust_pay_batch' => { #what's this used for again?  list of customers
                                 #in current CARD batch? (necessarily CARD?)
             'columns' => [
      -        'paybatchnum',   'serial',    '',   '',
      -        'invnum',   'int',    '',   '',
      -        'custnum',   'int',    '',   '',
      -        'last',     'varchar', '',     $char_d,
      -        'first',    'varchar', '',     $char_d,
      -        'address1', 'varchar', '',     $char_d,
      -        'address2', 'varchar', 'NULL', $char_d,
      -        'city',     'varchar', '',     $char_d,
      -        'state',    'varchar', 'NULL', $char_d,
      -        'zip',      'varchar', 'NULL', 10,
      -        'country',  'char', '',     2,
      -#        'trancode', 'int', '', '',
      -        'cardnum',  'varchar', '',     16,
      -        #'exp',      @date_type,
      -        'exp',      'varchar', '',     11,
      -        'payname',  'varchar', 'NULL', $char_d,
      -        'amount',   @money_type,
      +        'paybatchnum',   'serial',    '',   '', '', '', 
      +        'invnum',   'int',    '',   '', '', '', 
      +        'custnum',   'int',    '',   '', '', '', 
      +        'last',     'varchar', '',     $char_d, '', '', 
      +        'first',    'varchar', '',     $char_d, '', '', 
      +        'address1', 'varchar', '',     $char_d, '', '', 
      +        'address2', 'varchar', 'NULL', $char_d, '', '', 
      +        'city',     'varchar', '',     $char_d, '', '', 
      +        'state',    'varchar', 'NULL', $char_d, '', '', 
      +        'zip',      'varchar', 'NULL', 10, '', '', 
      +        'country',  'char', '',     2, '', '', 
      +        #        'trancode', 'int', '', '', '', ''
      +        'cardnum',  'varchar', '',     16, '', '', 
      +        #'exp',      @date_type, '', ''
      +        'exp',      'varchar', '',     11, '', '', 
      +        'payname',  'varchar', 'NULL', $char_d, '', '', 
      +        'amount',   @money_type, '', '', 
             ],
             'primary_key' => 'paybatchnum',
             'unique' => [],
      @@ -549,17 +560,17 @@ sub tables_hashref {
       
           'cust_pkg' => {
             'columns' => [
      -        'pkgnum',    'serial',    '',   '',
      -        'custnum',   'int',    '',   '',
      -        'pkgpart',   'int',    '',   '',
      -        'otaker',    'varchar', '', 32,
      -        'setup',     @date_type,
      -        'bill',      @date_type,
      -        'last_bill', @date_type,
      -        'susp',      @date_type,
      -        'cancel',    @date_type,
      -        'expire',    @date_type,
      -        'manual_flag', 'char', 'NULL', 1,
      +        'pkgnum',    'serial',    '',   '', '', '', 
      +        'custnum',   'int',    '',   '', '', '', 
      +        'pkgpart',   'int',    '',   '', '', '', 
      +        'otaker',    'varchar', '', 32, '', '', 
      +        'setup',     @date_type, '', '', 
      +        'bill',      @date_type, '', '', 
      +        'last_bill', @date_type, '', '', 
      +        'susp',      @date_type, '', '', 
      +        'cancel',    @date_type, '', '', 
      +        'expire',    @date_type, '', '', 
      +        'manual_flag', 'char', 'NULL', 1, '', '', 
             ],
             'primary_key' => 'pkgnum',
             'unique' => [],
      @@ -568,18 +579,19 @@ sub tables_hashref {
       
           'cust_refund' => {
             'columns' => [
      -        'refundnum',    'serial',    '',   '',
      -        #now cust_credit_refund #'crednum',      'int',    '',   '',
      -        'custnum',  'int',    '',   '',
      -        '_date',        @date_type,
      -        'refund',       @money_type,
      -        'otaker',       'varchar',   '',   32,
      -        'reason',       'varchar',   '',   $char_d,
      -        'payby',        'char',   '',     4, # CARD/BILL/COMP, should be index
      -                                             # into payment type table.
      -        'payinfo',      'varchar',   'NULL', $char_d,  #see cust_main above
      -        'paybatch',     'varchar',   'NULL', $char_d,
      -        'closed',    'char', 'NULL', 1,
      +        'refundnum',    'serial',    '',   '', '', '', 
      +        #now cust_credit_refund #'crednum',      'int',    '',   '', '', '',
      +        'custnum',  'int',    '',   '', '', '', 
      +        '_date',        @date_type, '', '', 
      +        'refund',       @money_type, '', '', 
      +        'otaker',       'varchar',   '',   32, '', '', 
      +        'reason',       'varchar',   '',   $char_d, '', '', 
      +        'payby',        'char',   '',     4, '', '', # CARD/BILL/COMP, should
      +                                                     # be index into payby
      +                                                     # table eventually
      +        'payinfo',      'varchar',   'NULL', $char_d, '', '', #see cust_main above
      +        'paybatch',     'varchar',   'NULL', $char_d, '', '', 
      +        'closed',    'char', 'NULL', 1, '', '', 
             ],
             'primary_key' => 'refundnum',
             'unique' => [],
      @@ -588,11 +600,11 @@ sub tables_hashref {
       
           'cust_credit_refund' => {
             'columns' => [
      -        'creditrefundnum', 'serial',     '',   '',
      -        'crednum',  'int',     '',   '',
      -        'refundnum',  'int',     '',   '',
      -        'amount',  @money_type,
      -        '_date',   @date_type
      +        'creditrefundnum', 'serial',     '',   '', '', '', 
      +        'crednum',  'int',     '',   '', '', '', 
      +        'refundnum',  'int',     '',   '', '', '', 
      +        'amount',  @money_type, '', '', 
      +        '_date',   @date_type, '', '', 
             ],
             'primary_key' => 'creditrefundnum',
             'unique' => [],
      @@ -602,9 +614,9 @@ sub tables_hashref {
       
           'cust_svc' => {
             'columns' => [
      -        'svcnum',    'serial',    '',   '',
      -        'pkgnum',    'int',    'NULL',   '',
      -        'svcpart',   'int',    '',   '',
      +        'svcnum',    'serial',    '',   '', '', '', 
      +        'pkgnum',    'int',    'NULL',   '', '', '', 
      +        'svcpart',   'int',    '',   '', '', '', 
             ],
             'primary_key' => 'svcnum',
             'unique' => [],
      @@ -613,20 +625,20 @@ sub tables_hashref {
       
           'part_pkg' => {
             'columns' => [
      -        'pkgpart',    'serial',    '',   '',
      -        'pkg',        'varchar',   '',   $char_d,
      -        'comment',    'varchar',   '',   $char_d,
      -        'promo_code', 'varchar', 'NULL', $char_d,
      -        'setup',      @perl_type,
      -        'freq',       'varchar',   '',   $char_d,  #billing frequency
      -        'recur',      @perl_type,
      -        'setuptax',  'char', 'NULL', 1,
      -        'recurtax',  'char', 'NULL', 1,
      -        'plan',       'varchar', 'NULL', $char_d,
      -        'plandata',   'text', 'NULL', '',
      -        'disabled',   'char', 'NULL', 1,
      -        'taxclass',   'varchar', 'NULL', $char_d,
      -        'classnum',   'int',     'NULL', '',
      +        'pkgpart',    'serial',    '',   '', '', '', 
      +        'pkg',        'varchar',   '',   $char_d, '', '', 
      +        'comment',    'varchar',   '',   $char_d, '', '', 
      +        'promo_code', 'varchar', 'NULL', $char_d, '', '', 
      +        'setup',      @perl_type, '', '', 
      +        'freq',       'varchar',   '',   $char_d, '', '', #billing frequency
      +        'recur',      @perl_type, '', '', 
      +        'setuptax',  'char', 'NULL', 1, '', '', 
      +        'recurtax',  'char', 'NULL', 1, '', '', 
      +        'plan',       'varchar', 'NULL', $char_d, '', '', 
      +        'plandata',   'text', 'NULL', '', '', '', 
      +        'disabled',   'char', 'NULL', 1, '', '', 
      +        'taxclass',   'varchar', 'NULL', $char_d, '', '', 
      +        'classnum',   'int',     'NULL', '', '', '', 
             ],
             'primary_key' => 'pkgpart',
             'unique' => [],
      @@ -645,11 +657,11 @@ sub tables_hashref {
       
           'pkg_svc' => {
             'columns' => [
      -        'pkgsvcnum',  'serial', '',  '',
      -        'pkgpart',    'int',    '',   '',
      -        'svcpart',    'int',    '',   '',
      -        'quantity',   'int',    '',   '',
      -        'primary_svc','char', 'NULL',  1,
      +        'pkgsvcnum',  'serial', '',  '', '', '', 
      +        'pkgpart',    'int',    '',   '', '', '', 
      +        'svcpart',    'int',    '',   '', '', '', 
      +        'quantity',   'int',    '',   '', '', '', 
      +        'primary_svc','char', 'NULL',  1, '', '', 
             ],
             'primary_key' => 'pkgsvcnum',
             'unique' => [ ['pkgpart', 'svcpart'] ],
      @@ -658,9 +670,9 @@ sub tables_hashref {
       
           'part_referral' => {
             'columns' => [
      -        'refnum',   'serial',    '',   '',
      -        'referral', 'varchar',   '',   $char_d,
      -        'disabled',     'char', 'NULL', 1,
      +        'refnum',   'serial',    '',   '', '', '', 
      +        'referral', 'varchar',   '',   $char_d, '', '', 
      +        'disabled',     'char', 'NULL', 1, '', '', 
             ],
             'primary_key' => 'refnum',
             'unique' => [],
      @@ -669,10 +681,10 @@ sub tables_hashref {
       
           'part_svc' => {
             'columns' => [
      -        'svcpart',    'serial',    '',   '',
      -        'svc',        'varchar',   '',   $char_d,
      -        'svcdb',      'varchar',   '',   $char_d,
      -        'disabled',   'char',  'NULL',   1,
      +        'svcpart',    'serial',    '',   '', '', '', 
      +        'svc',        'varchar',   '',   $char_d, '', '', 
      +        'svcdb',      'varchar',   '',   $char_d, '', '', 
      +        'disabled',   'char',  'NULL',   1, '', '', 
             ],
             'primary_key' => 'svcpart',
             'unique' => [],
      @@ -681,11 +693,11 @@ sub tables_hashref {
       
           'part_svc_column' => {
             'columns' => [
      -        'columnnum',   'serial',         '', '',
      -        'svcpart',     'int',         '', '',
      -        'columnname',  'varchar',     '', 64,
      -        'columnvalue', 'varchar', 'NULL', $char_d,
      -        'columnflag',  'char',    'NULL', 1, 
      +        'columnnum',   'serial',         '', '', '', '', 
      +        'svcpart',     'int',         '', '', '', '', 
      +        'columnname',  'varchar',     '', 64, '', '', 
      +        'columnvalue', 'varchar', 'NULL', $char_d, '', '', 
      +        'columnflag',  'char',    'NULL', 1, '', '', 
             ],
             'primary_key' => 'columnnum',
             'unique' => [ [ 'svcpart', 'columnname' ] ],
      @@ -695,12 +707,12 @@ sub tables_hashref {
           #(this should be renamed to part_pop)
           'svc_acct_pop' => {
             'columns' => [
      -        'popnum',    'serial',    '',   '',
      -        'city',      'varchar',   '',   $char_d,
      -        'state',     'varchar',   '',   $char_d,
      -        'ac',        'char',   '',   3,
      -        'exch',      'char',   '',   3,
      -        'loc',       'char',   'NULL',   4, #NULL for legacy purposes
      +        'popnum',    'serial',    '',   '', '', '', 
      +        'city',      'varchar',   '',   $char_d, '', '', 
      +        'state',     'varchar',   '',   $char_d, '', '', 
      +        'ac',        'char',   '',   3, '', '', 
      +        'exch',      'char',   '',   3, '', '', 
      +        'loc',       'char',   'NULL',   4, '', '', #NULL for legacy purposes
             ],
             'primary_key' => 'popnum',
             'unique' => [],
      @@ -709,12 +721,12 @@ sub tables_hashref {
       
           'part_pop_local' => {
             'columns' => [
      -        'localnum',  'serial',     '',     '',
      -        'popnum',    'int',     '',     '',
      -        'city',      'varchar', 'NULL', $char_d,
      -        'state',     'char',    'NULL', 2,
      -        'npa',       'char',    '',     3,
      -        'nxx',       'char',    '',     3,
      +        'localnum',  'serial',     '',     '', '', '', 
      +        'popnum',    'int',     '',     '', '', '', 
      +        'city',      'varchar', 'NULL', $char_d, '', '', 
      +        'state',     'char',    'NULL', 2, '', '', 
      +        'npa',       'char',    '',     3, '', '', 
      +        'nxx',       'char',    '',     3, '', '', 
             ],
             'primary_key' => 'localnum',
             'unique' => [],
      @@ -723,20 +735,20 @@ sub tables_hashref {
       
           'svc_acct' => {
             'columns' => [
      -        'svcnum',    'int',    '',   '',
      -        'username',  'varchar',   '',   $username_len, #unique (& remove dup code)
      -        '_password', 'varchar',   '',   72, #13 for encryped pw's plus ' *SUSPENDED* (md5 passwords can be 34, blowfish 60)
      -        'sec_phrase', 'varchar',  'NULL',   $char_d,
      -        'popnum',    'int',    'NULL',   '',
      -        'uid',       'int', 'NULL',   '',
      -        'gid',       'int', 'NULL',   '',
      -        'finger',    'varchar',   'NULL',   $char_d,
      -        'dir',       'varchar',   'NULL',   $char_d,
      -        'shell',     'varchar',   'NULL',   $char_d,
      -        'quota',     'varchar',   'NULL',   $char_d,
      -        'slipip',    'varchar',   'NULL',   15, #four TINYINTs, bah.
      -        'seconds',   'int', 'NULL',   '', #uhhhh
      -        'domsvc',    'int', '',   '',
      +        'svcnum',    'int',    '',   '', '', '', 
      +        'username',  'varchar',   '',   $username_len, '', '', #unique (& remove dup code)
      +        '_password', 'varchar',   '',   72, '', '', #13 for encryped pw's plus ' *SUSPENDED* (md5 passwords can be 34, blowfish 60)
      +        'sec_phrase', 'varchar',  'NULL',   $char_d, '', '', 
      +        'popnum',    'int',    'NULL',   '', '', '', 
      +        'uid',       'int', 'NULL',   '', '', '', 
      +        'gid',       'int', 'NULL',   '', '', '', 
      +        'finger',    'varchar',   'NULL',   $char_d, '', '', 
      +        'dir',       'varchar',   'NULL',   $char_d, '', '', 
      +        'shell',     'varchar',   'NULL',   $char_d, '', '', 
      +        'quota',     'varchar',   'NULL',   $char_d, '', '', 
      +        'slipip',    'varchar',   'NULL',   15, '', '', #four TINYINTs, bah.
      +        'seconds',   'int', 'NULL',   '', '', '', #uhhhh
      +        'domsvc',    'int', '',   '', '', '', 
             ],
             'primary_key' => 'svcnum',
             #'unique' => [ [ 'username', 'domsvc' ] ],
      @@ -756,9 +768,9 @@ sub tables_hashref {
       
           'svc_domain' => {
             'columns' => [
      -        'svcnum',    'int',    '',   '',
      -        'domain',    'varchar',    '',   $char_d,
      -        'catchall',  'int', 'NULL',    '',
      +        'svcnum',    'int',    '',   '', '', '', 
      +        'domain',    'varchar',    '',   $char_d, '', '', 
      +        'catchall',  'int', 'NULL',    '', '', '', 
             ],
             'primary_key' => 'svcnum',
             'unique' => [ ['domain'] ],
      @@ -767,14 +779,12 @@ sub tables_hashref {
       
           'domain_record' => {
             'columns' => [
      -        'recnum',    'serial',     '',  '',
      -        'svcnum',    'int',     '',  '',
      -        #'reczone',   'varchar', '',  $char_d,
      -        'reczone',   'varchar', '',  255,
      -        'recaf',     'char',    '',  2,
      -        'rectype',   'varchar',    '',  5,
      -        #'recdata',   'varchar', '',  $char_d,
      -        'recdata',   'varchar', '',  255,
      +        'recnum',    'serial',     '',  '', '', '', 
      +        'svcnum',    'int',     '',  '', '', '', 
      +        'reczone',   'varchar', '',  255, '', '', 
      +        'recaf',     'char',    '',  2, '', '', 
      +        'rectype',   'varchar',    '',  5, '', '', 
      +        'recdata',   'varchar', '',  255, '', '', 
             ],
             'primary_key' => 'recnum',
             'unique'      => [],
      @@ -783,11 +793,11 @@ sub tables_hashref {
       
           'svc_forward' => {
             'columns' => [
      -        'svcnum',   'int',            '',   '',
      -        'srcsvc',   'int',        'NULL',   '',
      -        'src',      'varchar',    'NULL',  255,
      -        'dstsvc',   'int',        'NULL',   '',
      -        'dst',      'varchar',    'NULL',  255,
      +        'svcnum',   'int',            '',   '', '', '', 
      +        'srcsvc',   'int',        'NULL',   '', '', '', 
      +        'src',      'varchar',    'NULL',  255, '', '', 
      +        'dstsvc',   'int',        'NULL',   '', '', '', 
      +        'dst',      'varchar',    'NULL',  255, '', '', 
             ],
             'primary_key' => 'svcnum',
             'unique'      => [],
      @@ -796,9 +806,9 @@ sub tables_hashref {
       
           'svc_www' => {
             'columns' => [
      -        'svcnum',   'int',    '',  '',
      -        'recnum',   'int',    '',  '',
      -        'usersvc',  'int',    '',  '',
      +        'svcnum',   'int',    '',  '', '', '', 
      +        'recnum',   'int',    '',  '', '', '', 
      +        'usersvc',  'int',    '',  '', '', '', 
             ],
             'primary_key' => 'svcnum',
             'unique'      => [],
      @@ -820,11 +830,11 @@ sub tables_hashref {
       
           'prepay_credit' => {
             'columns' => [
      -        'prepaynum',   'serial',     '',   '',
      -        'identifier',  'varchar', '', $char_d,
      -        'amount',      @money_type,
      -        'seconds',     'int',     'NULL', '',
      -        'agentnum',    'int',     'NULL', '',
      +        'prepaynum',   'serial',     '',   '', '', '', 
      +        'identifier',  'varchar', '', $char_d, '', '', 
      +        'amount',      @money_type, '', '', 
      +        'seconds',     'int',     'NULL', '', '', '', 
      +        'agentnum',    'int',     'NULL', '', '', '', 
             ],
             'primary_key' => 'prepaynum',
             'unique'      => [ ['identifier'] ],
      @@ -833,10 +843,10 @@ sub tables_hashref {
       
           'port' => {
             'columns' => [
      -        'portnum',  'serial',     '',   '',
      -        'ip',       'varchar', 'NULL', 15,
      -        'nasport',  'int',     'NULL', '',
      -        'nasnum',   'int',     '',   '',
      +        'portnum',  'serial',     '',   '', '', '', 
      +        'ip',       'varchar', 'NULL', 15, '', '', 
      +        'nasport',  'int',     'NULL', '', '', '', 
      +        'nasnum',   'int',     '',   '', '', '', 
             ],
             'primary_key' => 'portnum',
             'unique'      => [],
      @@ -845,11 +855,11 @@ sub tables_hashref {
       
           'nas' => {
             'columns' => [
      -        'nasnum',   'serial',     '',    '',
      -        'nas',      'varchar', '',    $char_d,
      -        'nasip',    'varchar', '',    15,
      -        'nasfqdn',  'varchar', '',    $char_d,
      -        'last',     'int',     '',    '',
      +        'nasnum',   'serial',     '',    '', '', '', 
      +        'nas',      'varchar', '',    $char_d, '', '', 
      +        'nasip',    'varchar', '',    15, '', '', 
      +        'nasfqdn',  'varchar', '',    $char_d, '', '', 
      +        'last',     'int',     '',    '', '', '', 
             ],
             'primary_key' => 'nasnum',
             'unique'      => [ [ 'nas' ], [ 'nasip' ] ],
      @@ -858,11 +868,11 @@ sub tables_hashref {
       
           'session' => {
             'columns' => [
      -        'sessionnum', 'serial',       '',   '',
      -        'portnum',    'int',       '',   '',
      -        'svcnum',     'int',       '',   '',
      -        'login',      @date_type,
      -        'logout',     @date_type,
      +        'sessionnum', 'serial',       '',   '', '', '', 
      +        'portnum',    'int',       '',   '', '', '', 
      +        'svcnum',     'int',       '',   '', '', '', 
      +        'login',      @date_type, '', '', 
      +        'logout',     @date_type, '', '', 
             ],
             'primary_key' => 'sessionnum',
             'unique'      => [],
      @@ -871,12 +881,12 @@ sub tables_hashref {
       
           'queue' => {
             'columns' => [
      -        'jobnum', 'serial', '', '',
      -        'job', 'text', '', '',
      -        '_date', 'int', '', '',
      -        'status', 'varchar', '', $char_d,
      -        'statustext', 'text', 'NULL', '',
      -        'svcnum', 'int', 'NULL', '',
      +        'jobnum', 'serial', '', '', '', '', 
      +        'job', 'text', '', '', '', '', 
      +        '_date', 'int', '', '', '', '', 
      +        'status', 'varchar', '', $char_d, '', '', 
      +        'statustext', 'text', 'NULL', '', '', '', 
      +        'svcnum', 'int', 'NULL', '', '', '', 
             ],
             'primary_key' => 'jobnum',
             'unique'      => [],
      @@ -885,9 +895,9 @@ sub tables_hashref {
       
           'queue_arg' => {
             'columns' => [
      -        'argnum', 'serial', '', '',
      -        'jobnum', 'int', '', '',
      -        'arg', 'text', 'NULL', '',
      +        'argnum', 'serial', '', '', '', '', 
      +        'jobnum', 'int', '', '', '', '', 
      +        'arg', 'text', 'NULL', '', '', '', 
             ],
             'primary_key' => 'argnum',
             'unique'      => [],
      @@ -896,9 +906,9 @@ sub tables_hashref {
       
           'queue_depend' => {
             'columns' => [
      -        'dependnum', 'serial', '', '',
      -        'jobnum', 'int', '', '',
      -        'depend_jobnum', 'int', '', '',
      +        'dependnum', 'serial', '', '', '', '', 
      +        'jobnum', 'int', '', '', '', '', 
      +        'depend_jobnum', 'int', '', '', '', '', 
             ],
             'primary_key' => 'dependnum',
             'unique'      => [],
      @@ -907,9 +917,9 @@ sub tables_hashref {
       
           'export_svc' => {
             'columns' => [
      -        'exportsvcnum' => 'serial', '', '',
      -        'exportnum'    => 'int', '', '',
      -        'svcpart'      => 'int', '', '',
      +        'exportsvcnum' => 'serial', '', '', '', '', 
      +        'exportnum'    => 'int', '', '', '', '', 
      +        'svcpart'      => 'int', '', '', '', '', 
             ],
             'primary_key' => 'exportsvcnum',
             'unique'      => [ [ 'exportnum', 'svcpart' ] ],
      @@ -918,11 +928,10 @@ sub tables_hashref {
       
           'part_export' => {
             'columns' => [
      -        'exportnum', 'serial', '', '',
      -        #'svcpart',   'int', '', '',
      -        'machine', 'varchar', '', $char_d,
      -        'exporttype', 'varchar', '', $char_d,
      -        'nodomain',     'char', 'NULL', 1,
      +        'exportnum', 'serial', '', '', '', '', 
      +        'machine', 'varchar', '', $char_d, '', '', 
      +        'exporttype', 'varchar', '', $char_d, '', '', 
      +        'nodomain',     'char', 'NULL', 1, '', '', 
             ],
             'primary_key' => 'exportnum',
             'unique'      => [],
      @@ -931,10 +940,10 @@ sub tables_hashref {
       
           'part_export_option' => {
             'columns' => [
      -        'optionnum', 'serial', '', '',
      -        'exportnum', 'int', '', '',
      -        'optionname', 'varchar', '', $char_d,
      -        'optionvalue', 'text', 'NULL', '',
      +        'optionnum', 'serial', '', '', '', '', 
      +        'exportnum', 'int', '', '', '', '', 
      +        'optionname', 'varchar', '', $char_d, '', '', 
      +        'optionvalue', 'text', 'NULL', '', '', '', 
             ],
             'primary_key' => 'optionnum',
             'unique'      => [],
      @@ -943,9 +952,9 @@ sub tables_hashref {
       
           'radius_usergroup' => {
             'columns' => [
      -        'usergroupnum', 'serial', '', '',
      -        'svcnum',       'int', '', '',
      -        'groupname',    'varchar', '', $char_d,
      +        'usergroupnum', 'serial', '', '', '', '', 
      +        'svcnum',       'int', '', '', '', '', 
      +        'groupname',    'varchar', '', $char_d, '', '', 
             ],
             'primary_key' => 'usergroupnum',
             'unique'      => [],
      @@ -954,10 +963,10 @@ sub tables_hashref {
       
           'msgcat' => {
             'columns' => [
      -        'msgnum', 'serial', '', '',
      -        'msgcode', 'varchar', '', $char_d,
      -        'locale', 'varchar', '', 16,
      -        'msg', 'text', '', '',
      +        'msgnum', 'serial', '', '', '', '', 
      +        'msgcode', 'varchar', '', $char_d, '', '', 
      +        'locale', 'varchar', '', 16, '', '', 
      +        'msg', 'text', '', '', '', '', 
             ],
             'primary_key' => 'msgnum',
             'unique'      => [ [ 'msgcode', 'locale' ] ],
      @@ -966,12 +975,12 @@ sub tables_hashref {
       
           'cust_tax_exempt' => {
             'columns' => [
      -        'exemptnum', 'serial', '', '',
      -        'custnum',   'int', '', '',
      -        'taxnum',    'int', '', '',
      -        'year',      'int', '', '',
      -        'month',     'int', '', '',
      -        'amount',   @money_type,
      +        'exemptnum', 'serial', '', '', '', '', 
      +        'custnum',   'int', '', '', '', '', 
      +        'taxnum',    'int', '', '', '', '', 
      +        'year',      'int', '', '', '', '', 
      +        'month',     'int', '', '', '', '', 
      +        'amount',   @money_type, '', '', 
             ],
             'primary_key' => 'exemptnum',
             'unique'      => [ [ 'custnum', 'taxnum', 'year', 'month' ] ],
      @@ -980,13 +989,13 @@ sub tables_hashref {
       
           'cust_tax_exempt_pkg' => {
             'columns' => [
      -        'exemptpkgnum',  'serial', '', '',
      -        #'custnum',      'int', '', '',
      -        'billpkgnum',   'int', '', '',
      -        'taxnum',       'int', '', '',
      -        'year',         'int', '', '',
      -        'month',        'int', '', '',
      -        'amount',       @money_type,
      +        'exemptpkgnum',  'serial', '', '', '', '', 
      +        #'custnum',      'int', '', '', '', ''
      +        'billpkgnum',   'int', '', '', '', '', 
      +        'taxnum',       'int', '', '', '', '', 
      +        'year',         'int', '', '', '', '', 
      +        'month',        'int', '', '', '', '', 
      +        'amount',       @money_type, '', '', 
             ],
             'primary_key' => 'exemptpkgnum',
             'unique' => [],
      @@ -998,9 +1007,9 @@ sub tables_hashref {
       
           'router' => {
             'columns' => [
      -        'routernum', 'serial', '', '',
      -        'routername', 'varchar', '', $char_d,
      -        'svcnum', 'int', 'NULL', '',
      +        'routernum', 'serial', '', '', '', '', 
      +        'routername', 'varchar', '', $char_d, '', '', 
      +        'svcnum', 'int', 'NULL', '', '', '', 
             ],
             'primary_key' => 'routernum',
             'unique'      => [],
      @@ -1009,9 +1018,9 @@ sub tables_hashref {
       
           'part_svc_router' => {
             'columns' => [
      -        'svcrouternum', 'serial', '', '',
      -        'svcpart', 'int', '', '',
      -	'routernum', 'int', '', '',
      +        'svcrouternum', 'serial', '', '', '', '', 
      +        'svcpart', 'int', '', '', '', '', 
      +	'routernum', 'int', '', '', '', '', 
             ],
             'primary_key' => 'svcrouternum',
             'unique'      => [],
      @@ -1020,10 +1029,10 @@ sub tables_hashref {
       
           'addr_block' => {
             'columns' => [
      -        'blocknum', 'serial', '', '',
      -	'routernum', 'int', '', '',
      -        'ip_gateway', 'varchar', '', 15,
      -        'ip_netmask', 'int', '', '',
      +        'blocknum', 'serial', '', '', '', '', 
      +	'routernum', 'int', '', '', '', '', 
      +        'ip_gateway', 'varchar', '', 15, '', '', 
      +        'ip_netmask', 'int', '', '', '', '', 
             ],
             'primary_key' => 'blocknum',
             'unique'      => [ [ 'blocknum', 'routernum' ] ],
      @@ -1032,11 +1041,11 @@ sub tables_hashref {
       
           'svc_broadband' => {
             'columns' => [
      -        'svcnum', 'int', '', '',
      -        'blocknum', 'int', '', '',
      -        'speed_up', 'int', '', '',
      -        'speed_down', 'int', '', '',
      -        'ip_addr', 'varchar', '', 15,
      +        'svcnum', 'int', '', '', '', '', 
      +        'blocknum', 'int', '', '', '', '', 
      +        'speed_up', 'int', '', '', '', '', 
      +        'speed_down', 'int', '', '', '', '', 
      +        'ip_addr', 'varchar', '', 15, '', '', 
             ],
             'primary_key' => 'svcnum',
             'unique'      => [],
      @@ -1045,13 +1054,13 @@ sub tables_hashref {
       
           'part_virtual_field' => {
             'columns' => [
      -        'vfieldpart', 'int', '', '',
      -        'dbtable', 'varchar', '', 32,
      -        'name', 'varchar', '', 32,
      -        'check_block', 'text', 'NULL', '',
      -        'length', 'int', 'NULL', '',
      -        'list_source', 'text', 'NULL', '',
      -        'label', 'varchar', 'NULL', 80,
      +        'vfieldpart', 'int', '', '', '', '', 
      +        'dbtable', 'varchar', '', 32, '', '', 
      +        'name', 'varchar', '', 32, '', '', 
      +        'check_block', 'text', 'NULL', '', '', '', 
      +        'length', 'int', 'NULL', '', '', '', 
      +        'list_source', 'text', 'NULL', '', '', '', 
      +        'label', 'varchar', 'NULL', 80, '', '', 
             ],
             'primary_key' => 'vfieldpart',
             'unique' => [],
      @@ -1060,10 +1069,10 @@ sub tables_hashref {
       
           'virtual_field' => {
             'columns' => [
      -        'vfieldnum', 'serial', '', '',
      -        'recnum', 'int', '', '',
      -        'vfieldpart', 'int', '', '',
      -        'value', 'varchar', '', 128,
      +        'vfieldnum', 'serial', '', '', '', '', 
      +        'recnum', 'int', '', '', '', '', 
      +        'vfieldpart', 'int', '', '', '', '', 
      +        'value', 'varchar', '', 128, '', '', 
             ],
             'primary_key' => 'vfieldnum',
             'unique' => [ [ 'vfieldpart', 'recnum' ] ],
      @@ -1072,12 +1081,12 @@ sub tables_hashref {
       
           'acct_snarf' => {
             'columns' => [
      -        'snarfnum',  'int', '', '',
      -        'svcnum',    'int', '', '',
      -        'machine',   'varchar', '', 255,
      -        'protocol',  'varchar', '', $char_d,
      -        'username',  'varchar', '', $char_d,
      -        '_password', 'varchar', '', $char_d,
      +        'snarfnum',  'int', '', '', '', '', 
      +        'svcnum',    'int', '', '', '', '', 
      +        'machine',   'varchar', '', 255, '', '', 
      +        'protocol',  'varchar', '', $char_d, '', '', 
      +        'username',  'varchar', '', $char_d, '', '', 
      +        '_password', 'varchar', '', $char_d, '', '', 
             ],
             'primary_key' => 'snarfnum',
             'unique' => [],
      @@ -1086,9 +1095,9 @@ sub tables_hashref {
       
           'svc_external' => {
             'columns' => [
      -        'svcnum', 'int', '', '',
      -        'id',     'int', 'NULL', '',
      -        'title',  'varchar', 'NULL', $char_d,
      +        'svcnum', 'int', '', '', '', '', 
      +        'id',     'int', 'NULL', '', '', '', 
      +        'title',  'varchar', 'NULL', $char_d, '', '', 
             ],
             'primary_key' => 'svcnum',
             'unique'      => [],
      @@ -1097,11 +1106,11 @@ sub tables_hashref {
       
           'cust_pay_refund' => {
             'columns' => [
      -        'payrefundnum', 'serial', '', '',
      -        'paynum',  'int', '', '',
      -        'refundnum',  'int', '', '',
      -        '_date',    @date_type,
      -        'amount',   @money_type,
      +        'payrefundnum', 'serial', '', '', '', '', 
      +        'paynum',  'int', '', '', '', '', 
      +        'refundnum',  'int', '', '', '', '', 
      +        '_date',    @date_type, '', '', 
      +        'amount',   @money_type, '', '', 
             ],
             'primary_key' => 'payrefundnum',
             'unique' => [],
      @@ -1110,10 +1119,10 @@ sub tables_hashref {
       
           'part_pkg_option' => {
             'columns' => [
      -        'optionnum', 'serial', '', '',
      -        'pkgpart', 'int', '', '',
      -        'optionname', 'varchar', '', $char_d,
      -        'optionvalue', 'text', 'NULL', '',
      +        'optionnum', 'serial', '', '', '', '', 
      +        'pkgpart', 'int', '', '', '', '', 
      +        'optionname', 'varchar', '', $char_d, '', '', 
      +        'optionvalue', 'text', 'NULL', '', '', '', 
             ],
             'primary_key' => 'optionnum',
             'unique'      => [],
      @@ -1122,8 +1131,8 @@ sub tables_hashref {
       
           'rate' => {
             'columns' => [
      -        'ratenum',  'serial', '', '',
      -        'ratename', 'varchar', '', $char_d,
      +        'ratenum',  'serial', '', '', '', '', 
      +        'ratename', 'varchar', '', $char_d, '', '', 
             ],
             'primary_key' => 'ratenum',
             'unique'      => [],
      @@ -1132,13 +1141,13 @@ sub tables_hashref {
       
           'rate_detail' => {
             'columns' => [
      -        'ratedetailnum',   'serial', '', '',
      -        'ratenum',         'int',     '', '',
      -        'orig_regionnum',  'int', 'NULL', '',
      -        'dest_regionnum',  'int',     '', '',
      -        'min_included',    'int',     '', '',
      -        'min_charge',      @money_type,
      -        'sec_granularity', 'int',     '', '',
      +        'ratedetailnum',   'serial', '', '', '', '', 
      +        'ratenum',         'int',     '', '', '', '', 
      +        'orig_regionnum',  'int', 'NULL', '', '', '', 
      +        'dest_regionnum',  'int',     '', '', '', '', 
      +        'min_included',    'int',     '', '', '', '', 
      +        'min_charge',      @money_type, '', '', 
      +        'sec_granularity', 'int',     '', '', '', '', 
               #time period (link to table of periods)?
             ],
             'primary_key' => 'ratedetailnum',
      @@ -1148,8 +1157,8 @@ sub tables_hashref {
       
           'rate_region' => {
             'columns' => [
      -        'regionnum',   'serial',      '', '',
      -        'regionname',  'varchar',     '', $char_d,
      +        'regionnum',   'serial',      '', '', '', '', 
      +        'regionname',  'varchar',     '', $char_d, '', '', 
             ],
             'primary_key' => 'regionnum',
             'unique'      => [],
      @@ -1158,11 +1167,11 @@ sub tables_hashref {
       
           'rate_prefix' => {
             'columns' => [
      -        'prefixnum',   'serial',    '', '',
      -        'regionnum',   'int',       '', '',,
      -        'countrycode', 'varchar',     '', 3,
      -        'npa',         'varchar', 'NULL', 6,
      -        'nxx',         'varchar', 'NULL', 3,
      +        'prefixnum',   'serial',    '', '', '', '', 
      +        'regionnum',   'int',       '', '',, '', '', 
      +        'countrycode', 'varchar',     '', 3, '', '', 
      +        'npa',         'varchar', 'NULL', 6, '', '', 
      +        'nxx',         'varchar', 'NULL', 3, '', '', 
             ],
             'primary_key' => 'prefixnum',
             'unique'      => [],
      @@ -1171,9 +1180,9 @@ sub tables_hashref {
       
           'reg_code' => {
             'columns' => [
      -        'codenum',   'serial',    '', '',
      -        'code',      'varchar',   '', $char_d,
      -        'agentnum',  'int',       '', '',
      +        'codenum',   'serial',    '', '', '', '', 
      +        'code',      'varchar',   '', $char_d, '', '', 
      +        'agentnum',  'int',       '', '', '', '', 
             ],
             'primary_key' => 'codenum',
             'unique'      => [ [ 'agentnum', 'code' ] ],
      @@ -1182,9 +1191,9 @@ sub tables_hashref {
       
           'reg_code_pkg' => {
             'columns' => [
      -        'codepkgnum', 'serial', '', '',
      -        'codenum',   'int',    '', '',
      -        'pkgpart',   'int',    '', '',
      +        'codepkgnum', 'serial', '', '', '', '', 
      +        'codenum',   'int',    '', '', '', '', 
      +        'pkgpart',   'int',    '', '', '', '', 
             ],
             'primary_key' => 'codepkgnum',
             'unique'      => [ [ 'codenum', 'pkgpart' ] ],
      @@ -1193,9 +1202,9 @@ sub tables_hashref {
       
           'clientapi_session' => {
             'columns' => [
      -        'sessionnum',  'serial',  '', '',
      -        'sessionid',  'varchar',  '', $char_d,
      -        'namespace',  'varchar',  '', $char_d,
      +        'sessionnum',  'serial',  '', '', '', '', 
      +        'sessionid',  'varchar',  '', $char_d, '', '', 
      +        'namespace',  'varchar',  '', $char_d, '', '', 
             ],
             'primary_key' => 'sessionnum',
             'unique'      => [ [ 'sessionid', 'namespace' ] ],
      @@ -1204,10 +1213,10 @@ sub tables_hashref {
       
           'clientapi_session_field' => {
             'columns' => [
      -        'fieldnum',    'serial',     '', '',
      -        'sessionnum',     'int',     '', '',
      -        'fieldname',  'varchar',     '', $char_d,
      -        'fieldvalue',    'text', 'NULL', '',
      +        'fieldnum',    'serial',     '', '', '', '', 
      +        'sessionnum',     'int',     '', '', '', '', 
      +        'fieldname',  'varchar',     '', $char_d, '', '', 
      +        'fieldvalue',    'text', 'NULL', '', '', '', 
             ],
             'primary_key' => 'fieldnum',
             'unique'      => [ [ 'sessionnum', 'fieldname' ] ],
      @@ -1216,12 +1225,12 @@ sub tables_hashref {
       
           'payment_gateway' => {
             'columns' => [
      -        'gatewaynum',       'serial',   '',     '',
      -        'gateway_module',   'varchar',  '',     $char_d,
      -        'gateway_username', 'varchar',  'NULL', $char_d,
      -        'gateway_password', 'varchar',  'NULL', $char_d,
      -        'gateway_action',   'varchar',  'NULL', $char_d,
      -        'disabled',   'char',  'NULL',   1,
      +        'gatewaynum',       'serial',   '',     '', '', '', 
      +        'gateway_module',   'varchar',  '',     $char_d, '', '', 
      +        'gateway_username', 'varchar',  'NULL', $char_d, '', '', 
      +        'gateway_password', 'varchar',  'NULL', $char_d, '', '', 
      +        'gateway_action',   'varchar',  'NULL', $char_d, '', '', 
      +        'disabled',   'char',  'NULL',   1, '', '', 
             ],
             'primary_key' => 'gatewaynum',
             'unique' => [],
      @@ -1230,10 +1239,10 @@ sub tables_hashref {
       
           'payment_gateway_option' => {
             'columns' => [
      -        'optionnum',   'serial',  '',     '',
      -        'gatewaynum',  'int',     '',     '',
      -        'optionname',  'varchar', '',     $char_d,
      -        'optionvalue', 'text',    'NULL', '',
      +        'optionnum',   'serial',  '',     '', '', '', 
      +        'gatewaynum',  'int',     '',     '', '', '', 
      +        'optionname',  'varchar', '',     $char_d, '', '', 
      +        'optionvalue', 'text',    'NULL', '', '', '', 
             ],
             'primary_key' => 'optionnum',
             'unique'      => [],
      @@ -1242,11 +1251,11 @@ sub tables_hashref {
       
           'agent_payment_gateway' => {
             'columns' => [
      -        'agentgatewaynum', 'serial', '', '',
      -        'agentnum',        'int', '', '',
      -        'gatewaynum',      'int', '', '',
      -        'cardtype',        'varchar', 'NULL', $char_d,
      -        'taxclass',        'varchar', 'NULL', $char_d,
      +        'agentgatewaynum', 'serial', '', '', '', '', 
      +        'agentnum',        'int', '', '', '', '', 
      +        'gatewaynum',      'int', '', '', '', '', 
      +        'cardtype',        'varchar', 'NULL', $char_d, '', '', 
      +        'taxclass',        'varchar', 'NULL', $char_d, '', '', 
             ],
             'primary_key' => 'agentgatewaynum',
             'unique'      => [],
      @@ -1255,13 +1264,13 @@ sub tables_hashref {
       
           'banned_pay' => {
             'columns' => [
      -        'bannum',  'serial',   '',     '',
      -        'payby',   'char',     '',       4,
      -        'payinfo', 'varchar',  '',     128, #say, a 512-big digest _hex encoded
      -	#'paymask', 'varchar',  'NULL', $char_d,
      -        '_date',   @date_type,
      -        'otaker',  'varchar',  '',     32,
      -        'reason',  'varchar',  'NULL', $char_d,
      +        'bannum',  'serial',   '',     '', '', '', 
      +        'payby',   'char',     '',       4, '', '', 
      +        'payinfo', 'varchar',  '',     128, '', '', #say, a 512-big digest _hex encoded
      +	#'paymask', 'varchar',  'NULL', $char_d, '', ''
      +        '_date',   @date_type, '', '', 
      +        'otaker',  'varchar',  '',     32, '', '', 
      +        'reason',  'varchar',  'NULL', $char_d, '', '', 
             ],
             'primary_key' => 'bannum',
             'unique'      => [ [ 'payby', 'payinfo' ] ],
      @@ -1270,9 +1279,9 @@ sub tables_hashref {
       
           'cancel_reason' => {
             'columns' => [
      -        'reasonnum', 'serial',  '',     '',
      -        'reason',    'varchar', '',     $char_d,
      -        'disabled',  'char',    'NULL', 1,
      +        'reasonnum', 'serial',  '',     '', '', '', 
      +        'reason',    'varchar', '',     $char_d, '', '', 
      +        'disabled',  'char',    'NULL', 1, '', '', 
             ],
             'primary_key' => 'reasonnum',
             'unique' => [],
      @@ -1281,14 +1290,129 @@ sub tables_hashref {
       
           'pkg_class' => {
             'columns' => [
      -        'classnum',   'serial',  '', '',
      -        'classname',  'varchar', '', $char_d,
      +        'classnum',   'serial',  '', '', '', '', 
      +        'classname',  'varchar', '', $char_d, '', '', 
             ],
             'primary_key' => 'classnum',
             'unique' => [],
             'index' => [],
           },
       
      +    'cdr' => {
      +      'columns' => [
      +        # qw( name type null length default local );
      +
      +        ###
      +        #asterisk fields
      +        ###
      +
      +        'acctid',   'bigserial',  '', '', '', '', 
      +        'calldate', 'TIMESTAMP with time zone', '', '', \'now()', '',
      +        'clid',        'varchar',  '', $char_d, \"''", '', 
      +        'src',         'varchar',  '', $char_d, \"''", '', 
      +        'dst',         'varchar',  '', $char_d, \"''", '', 
      +        'dcontext',    'varchar',  '', $char_d, \"''", '', 
      +        'channel',     'varchar',  '', $char_d, \"''", '', 
      +        'dstchannel',  'varchar',  '', $char_d, \"''", '', 
      +        'lastapp',     'varchar',  '', $char_d, \"''", '', 
      +        'lastdata',    'varchar',  '', $char_d, \"''", '', 
      +
      +        #these don't seem to be logged by most of the SQL cdr_* modules
      +        #except tds under sql-illegal names, so;
      +        # ... don't rely on them for rating?
      +        # and, what they hey, i went ahead and changed the names and data types
      +        # to freeside-style dates...
      +          #'start',  'timestamp', 'NULL',  '',    '', '',
      +          #'answer', 'timestamp', 'NULL',  '',    '', '',
      +          #'end',    'timestamp', 'NULL',  '',    '', '',
      +        'startdate',  @date_type, '', '', 
      +        'answerdate', @date_type, '', '', 
      +        'enddate',    @date_type, '', '', 
      +        #
      +
      +        'duration',    'int',      '',      '',     0, '',
      +        'billsec',     'int',      '',      '',     0, '', 
      +        'disposition', 'varchar',  '',      45, \"''", '',
      +        'amaflags',    'int',      '',      '',     0, '',
      +        'accountcode', 'varchar',  '',      20, \"''", '',
      +        'uniqueid',    'varchar',  '',      32, \"''", '',
      +        'userfield',   'varchar',  '',     255, \"''", '',
      +
      +        ###
      +        # fields for unitel/RSLCOM/convergent that don't map well to asterisk
      +        # defaults
      +        ###
      +
      +        #cdr_type: Usage = 1, S&E = 7, OC&C = 8
      +        'cdrtypenum',              'int', 'NULL',      '', '', '',
      +
      +        'charged_party',       'varchar', 'NULL', $char_d, '', '',
      +
      +        'upstream_currency',      'char', 'NULL',       3, '', '',
      +        'upstream_price',      'decimal', 'NULL',  '10,2', '', '', 
      +        'upstream_rateplanid',     'int', 'NULL',      '', '', '', #?
      +
      +        'distance',            'decimal', 'NULL',      '', '', '',
      +        'islocal',                 'int', 'NULL',      '', '', '', # '',  '', 0, '' instead?
      +
      +        #cdr_calltype: the big list in appendix 2
      +        'calltypenum',             'int', 'NULL',      '', '', '',
      +
      +        'description',         'varchar', 'NULL', $char_d, '', '',
      +        'quantity',                'int', 'NULL',      '', '', '', 
      +
      +        #cdr_carrier: Telstra =1, Optus = 2, RSL COM = 3
      +        'carrierid',               'int', 'NULL',      '', '', '',
      +
      +        'upstream_rateid',         'int', 'NULL',      '', '', '',
      +        
      +        ###
      +        #and now for our own fields
      +        ###
      +
      +        # a svcnum... right..?
      +        'svcnum',             'int',   'NULL',     '',   '', '', 
      +
      +        #NULL, done, skipped, pushed_downstream (or something)
      +        'freesidestatus', 'varchar',   'NULL',     32,   '', '', 
      +
      +      ],
      +      'primary_key' => 'acctid',
      +      'unique' => [],
      +      'index' => [ [ 'calldate' ], [ 'dst' ], [ 'accountcode' ], [ 'freesidestatus' ] ],
      +    },
      +
      +    'cdr_calltype' => {
      +      'columns' => [
      +        'calltypenum',   'serial',  '', '', '', '', 
      +        'calltypename',  'varchar', '', $char_d, '', '', 
      +      ],
      +      'primary_key' => 'calltypenum',
      +      'unique'      => [],
      +      'index'       => [],
      +    },
      +
      +    'cdr_type' => {
      +      'columns' => [
      +        'cdrtypenum'  => 'serial',  '', '', '', '',
      +        'cdrtypename' => 'varchar', '', '', '', '',
      +      ],
      +      'primary_key' => 'cdrtypenum',
      +      'unique'      => [],
      +      'index'       => [],
      +    },
      +
      +    'cdr_carrier' => {
      +      'columns' => [
      +        'carrierid'   => 'serial',  '', '', '', '',
      +        'carriername' => 'varchar', '', '', '', '',
      +      ],
      +      'primary_key' => 'carrierid',
      +      'unique'      => [],
      +      'index'       => [],
      +    },
      +
      +
         };
       
       }
      diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm
      new file mode 100644
      index 000000000..2d40177f5
      --- /dev/null
      +++ b/FS/FS/cdr.pm
      @@ -0,0 +1,385 @@
      +package FS::cdr;
      +
      +use strict;
      +use vars qw( @ISA );
      +use Date::Parse;
      +use FS::UID qw( dbh );
      +use FS::Record qw( qsearch qsearchs );
      +use FS::cdr_type;
      +use FS::cdr_calltype;
      +use FS::cdr_carrier;
      +
      +@ISA = qw(FS::Record);
      +
      +=head1 NAME
      +
      +FS::cdr - Object methods for cdr records
      +
      +=head1 SYNOPSIS
      +
      +  use FS::cdr;
      +
      +  $record = new FS::cdr \%hash;
      +  $record = new FS::cdr { 'column' => 'value' };
      +
      +  $error = $record->insert;
      +
      +  $error = $new_record->replace($old_record);
      +
      +  $error = $record->delete;
      +
      +  $error = $record->check;
      +
      +=head1 DESCRIPTION
      +
      +An FS::cdr object represents an Call Data Record, typically from a telephony
      +system or provider of some sort.  FS::cdr inherits from FS::Record.  The
      +following fields are currently supported:
      +
      +=over 4
      +
      +=item acctid - primary key
      +
      +=item calldate - Call timestamp (SQL timestamp)
      +
      +=item clid - Caller*ID with text
      +
      +=item src - Caller*ID number / Source number
      +
      +=item dst - Destination extension
      +
      +=item dcontext - Destination context
      +
      +=item channel - Channel used
      +
      +=item dstchannel - Destination channel if appropriate
      +
      +=item lastapp - Last application if appropriate
      +
      +=item lastdata - Last application data
      +
      +=item startdate - Start of call (UNIX-style integer timestamp)
      +
      +=item answerdate - Answer time of call (UNIX-style integer timestamp)
      +
      +=item enddate - End time of call (UNIX-style integer timestamp)
      +
      +=item duration - Total time in system, in seconds
      +
      +=item billsec - Total time call is up, in seconds
      +
      +=item disposition - What happened to the call: ANSWERED, NO ANSWER, BUSY 
      +
      +=item amaflags - What flags to use: BILL, IGNORE etc, specified on a per channel basis like accountcode. 
      +
      +=cut
      +
      +  #ignore the "omit" and "documentation" AMAs??
      +  #AMA = Automated Message Accounting. 
      +  #default: Sets the system default. 
      +  #omit: Do not record calls. 
      +  #billing: Mark the entry for billing 
      +  #documentation: Mark the entry for documentation.
      +
      +=back
      +
      +=item accountcode - CDR account number to use: account
      +
      +=item uniqueid - Unique channel identifier (Unitel/RSLCOM Event ID)
      +
      +=item userfield - CDR user-defined field
      +
      +=item cdr_type - CDR type - see L (Usage = 1, S&E = 7, OC&C = 8)
      +
      +=item charged_party - Service number to be billed
      +
      +=item upstream_currency - Wholesale currency from upstream
      +
      +=item upstream_price - Wholesale price from upstream
      +
      +=item upstream_rateplanid - Upstream rate plan ID
      +
      +=item distance - km (need units field?)
      +
      +=item islocal - Local - 1, Non Local = 0
      +
      +=item calltypenum - Type of call - see L
      +
      +=item description - Description (cdr_type 7&8 only) (used for cust_bill_pkg.itemdesc)
      +
      +=item quantity - Number of items (cdr_type 7&8 only)
      +
      +=item carrierid - Upstream Carrier ID (see L) 
      +
      +=cut
      +
      +#Telstra =1, Optus = 2, RSL COM = 3
      +
      +=back
      +
      +=item upstream_rateid - Upstream Rate ID
      +
      +=item svcnum - Link to customer service (see L)
      +
      +=item freesidestatus - NULL, done, skipped, pushed_downstream (or something)
      +
      +=back
      +
      +=head1 METHODS
      +
      +=over 4
      +
      +=item new HASHREF
      +
      +Creates a new CDR.  To add the CDR to the database, see L<"insert">.
      +
      +Note that this stores the hash reference, not a distinct copy of the hash it
      +points to.  You can ask the object for a copy with the I method.
      +
      +=cut
      +
      +# the new method can be inherited from FS::Record, if a table method is defined
      +
      +sub table { 'cdr'; }
      +
      +=item insert
      +
      +Adds this record to the database.  If there is an error, returns the error,
      +otherwise returns false.
      +
      +=cut
      +
      +# the insert method can be inherited from FS::Record
      +
      +=item delete
      +
      +Delete this record from the database.
      +
      +=cut
      +
      +# the delete method can be inherited from FS::Record
      +
      +=item replace OLD_RECORD
      +
      +Replaces the OLD_RECORD with this one in the database.  If there is an error,
      +returns the error, otherwise returns false.
      +
      +=cut
      +
      +# the replace method can be inherited from FS::Record
      +
      +=item check
      +
      +Checks all fields to make sure this is a valid CDR.  If there is
      +an error, returns the error, otherwise returns false.  Called by the insert
      +and replace methods.
      +
      +Note: Unlike most types of records, we don't want to "reject" a CDR and we want
      +to process them as quickly as possible, so we allow the database to check most
      +of the data.
      +
      +=cut
      +
      +sub check {
      +  my $self = shift;
      +
      +# we don't want to "reject" a CDR like other sorts of input...
      +#  my $error = 
      +#    $self->ut_numbern('acctid')
      +##    || $self->ut_('calldate')
      +#    || $self->ut_text('clid')
      +#    || $self->ut_text('src')
      +#    || $self->ut_text('dst')
      +#    || $self->ut_text('dcontext')
      +#    || $self->ut_text('channel')
      +#    || $self->ut_text('dstchannel')
      +#    || $self->ut_text('lastapp')
      +#    || $self->ut_text('lastdata')
      +#    || $self->ut_numbern('startdate')
      +#    || $self->ut_numbern('answerdate')
      +#    || $self->ut_numbern('enddate')
      +#    || $self->ut_number('duration')
      +#    || $self->ut_number('billsec')
      +#    || $self->ut_text('disposition')
      +#    || $self->ut_number('amaflags')
      +#    || $self->ut_text('accountcode')
      +#    || $self->ut_text('uniqueid')
      +#    || $self->ut_text('userfield')
      +#    || $self->ut_numbern('cdrtypenum')
      +#    || $self->ut_textn('charged_party')
      +##    || $self->ut_n('upstream_currency')
      +##    || $self->ut_n('upstream_price')
      +#    || $self->ut_numbern('upstream_rateplanid')
      +##    || $self->ut_n('distance')
      +#    || $self->ut_numbern('islocal')
      +#    || $self->ut_numbern('calltypenum')
      +#    || $self->ut_textn('description')
      +#    || $self->ut_numbern('quantity')
      +#    || $self->ut_numbern('carrierid')
      +#    || $self->ut_numbern('upstream_rateid')
      +#    || $self->ut_numbern('svcnum')
      +#    || $self->ut_textn('freesidestatus')
      +#  ;
      +#  return $error if $error;
      +
      +  #check the foreign keys even?
      +  #do we want to outright *reject* the CDR?
      +  my $error =
      +       $self->ut_numbern('acctid')
      +
      +    #Usage = 1, S&E = 7, OC&C = 8
      +    || $self->ut_foreign_keyn('cdrtypenum',  'cdr_type',     'cdrtypenum' )
      +
      +    #the big list in appendix 2
      +    || $self->ut_foreign_keyn('calltypenum', 'cdr_calltype', 'calltypenum' )
      +
      +    # Telstra =1, Optus = 2, RSL COM = 3
      +    || $self->ut_foreign_keyn('carrierid', 'cdr_carrier', 'carrierid' )
      +  ;
      +  return $error if $error;
      +
      +  $self->SUPER::check;
      +}
      +
      +my %formats = (
      +  'asterisk' => [
      +    'accountcode',
      +    'src',
      +    'dst',
      +    'dcontext',
      +    'clid',
      +    'channel',
      +    'dstchannel',
      +    'lastapp',
      +    'lastdata',
      +    'startdate', # XXX will need massaging
      +    'answer',    # XXX same
      +    'end',       # XXX same
      +    'duration',
      +    'billsec',
      +    'disposition',
      +    'amaflags',
      +    'uniqueid',
      +    'userfield',
      +  ],
      +  'unitel' => [
      +    'uniqueid',
      +    'cdr_type',
      +    'calldate', # XXX may need massaging
      +    'billsec', #XXX duration and billsec?
      +               # sub { $_[0]->billsec(  $_[1] );
      +               #       $_[0]->duration( $_[1] );
      +               #     },
      +    'src',
      +    'dst',
      +    'charged_party',
      +    'upstream_currency',
      +    'upstream_price',
      +    'upstream_rateplanid',
      +    'distance',
      +    'islocal',
      +    'calltypenum',
      +    'startdate', # XXX will definitely need massaging
      +    'enddate',   # XXX same
      +    'description',
      +    'quantity',
      +    'carrierid',
      +    'upstream_rateid',
      +  ]
      +);
      +
      +sub batch_import {
      +  my $param = shift;
      +
      +  my $fh = $param->{filehandle};
      +  my $format = $param->{format};
      +
      +  return "Unknown format $format" unless exists $formats{$format};
      +
      +  eval "use Text::CSV_XS;";
      +  die $@ if $@;
      +
      +  my $csv = new Text::CSV_XS;
      +
      +  my $imported = 0;
      +  #my $columns;
      +
      +  local $SIG{HUP} = 'IGNORE';
      +  local $SIG{INT} = 'IGNORE';
      +  local $SIG{QUIT} = 'IGNORE';
      +  local $SIG{TERM} = 'IGNORE';
      +  local $SIG{TSTP} = 'IGNORE';
      +  local $SIG{PIPE} = 'IGNORE';
      +
      +  my $oldAutoCommit = $FS::UID::AutoCommit;
      +  local $FS::UID::AutoCommit = 0;
      +  my $dbh = dbh;
      +  
      +  my $line;
      +  while ( defined($line=<$fh>) ) {
      +
      +    $csv->parse($line) or do {
      +      $dbh->rollback if $oldAutoCommit;
      +      return "can't parse: ". $csv->error_input();
      +    };
      +
      +    my @columns = $csv->fields();
      +    #warn join('-',@columns);
      +
      +    my @later = ();
      +    my %cdr =
      +      map {
      +
      +        my $field_or_sub = $_;
      +        if ( ref($field_or_sub) ) {
      +          push @later, $field_or_sub, shift(@columns);
      +          ();
      +        } else {
      +          ( $field_or_sub => shift @columns );
      +        }
      +
      +      }
      +      @{ $formats{$format} }
      +    ;
      +
      +    my $cdr = new FS::cdr ( \%cdr );
      +
      +    while ( scalar(@later) ) {
      +      my $sub = shift @later;
      +      my $data = shift @later;
      +      &{$sub}($cdr, $data);  # $cdr->&{$sub}($data); 
      +    }
      +
      +    my $error = $cdr->insert;
      +    if ( $error ) {
      +      $dbh->rollback if $oldAutoCommit;
      +      return $error;
      +
      +      #or just skip?
      +      #next;
      +    }
      +
      +    $imported++;
      +  }
      +
      +  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
      +
      +  #might want to disable this if we skip records for any reason...
      +  return "Empty file!" unless $imported;
      +
      +  '';
      +
      +}
      +
      +=back
      +
      +=head1 BUGS
      +
      +=head1 SEE ALSO
      +
      +L, schema.html from the base documentation.
      +
      +=cut
      +
      +1;
      +
      diff --git a/FS/FS/cdr_calltype.pm b/FS/FS/cdr_calltype.pm
      new file mode 100644
      index 000000000..fe456086f
      --- /dev/null
      +++ b/FS/FS/cdr_calltype.pm
      @@ -0,0 +1,115 @@
      +package FS::cdr_calltype;
      +
      +use strict;
      +use vars qw( @ISA );
      +use FS::Record qw( qsearch qsearchs );
      +
      +@ISA = qw(FS::Record);
      +
      +=head1 NAME
      +
      +FS::cdr_calltype - Object methods for cdr_calltype records
      +
      +=head1 SYNOPSIS
      +
      +  use FS::cdr_calltype;
      +
      +  $record = new FS::cdr_calltype \%hash;
      +  $record = new FS::cdr_calltype { 'column' => 'value' };
      +
      +  $error = $record->insert;
      +
      +  $error = $new_record->replace($old_record);
      +
      +  $error = $record->delete;
      +
      +  $error = $record->check;
      +
      +=head1 DESCRIPTION
      +
      +An FS::cdr_calltype object represents an CDR call type.  FS::cdr_calltype
      +inherits from FS::Record.  The following fields are currently supported:
      +
      +=over 4
      +
      +=item calltypenum - primary key
      +
      +=item calltypename - CDR call type name
      +
      +=back
      +
      +=head1 METHODS
      +
      +=over 4
      +
      +=item new HASHREF
      +
      +Creates a new call type.  To add the call type to the database, see L<"insert">.
      +
      +Note that this stores the hash reference, not a distinct copy of the hash it
      +points to.  You can ask the object for a copy with the I method.
      +
      +=cut
      +
      +# the new method can be inherited from FS::Record, if a table method is defined
      +
      +sub table { 'cdr_calltype'; }
      +
      +=item insert
      +
      +Adds this record to the database.  If there is an error, returns the error,
      +otherwise returns false.
      +
      +=cut
      +
      +# the insert method can be inherited from FS::Record
      +
      +=item delete
      +
      +Delete this record from the database.
      +
      +=cut
      +
      +# the delete method can be inherited from FS::Record
      +
      +=item replace OLD_RECORD
      +
      +Replaces the OLD_RECORD with this one in the database.  If there is an error,
      +returns the error, otherwise returns false.
      +
      +=cut
      +
      +# the replace method can be inherited from FS::Record
      +
      +=item check
      +
      +Checks all fields to make sure this is a valid call type.  If there is
      +an error, returns the error, otherwise returns false.  Called by the insert
      +and replace methods.
      +
      +=cut
      +
      +sub check {
      +  my $self = shift;
      +
      +  my $error = 
      +    $self->ut_numbern('calltypenum')
      +    || $self->ut_text('calltypename')
      +  ;
      +  return $error if $error;
      +
      +  $self->SUPER::check;
      +}
      +
      +=back
      +
      +=head1 BUGS
      +
      +=head1 SEE ALSO
      +
      +L, schema.html from the base documentation.
      +
      +=cut
      +
      +1;
      +
      diff --git a/FS/FS/cdr_carrier.pm b/FS/FS/cdr_carrier.pm
      new file mode 100644
      index 000000000..609c93923
      --- /dev/null
      +++ b/FS/FS/cdr_carrier.pm
      @@ -0,0 +1,116 @@
      +package FS::cdr_carrier;
      +
      +use strict;
      +use vars qw( @ISA );
      +use FS::Record qw( qsearch qsearchs );
      +
      +@ISA = qw(FS::Record);
      +
      +=head1 NAME
      +
      +FS::cdr_carrier - Object methods for cdr_carrier records
      +
      +=head1 SYNOPSIS
      +
      +  use FS::cdr_carrier;
      +
      +  $record = new FS::cdr_carrier \%hash;
      +  $record = new FS::cdr_carrier { 'column' => 'value' };
      +
      +  $error = $record->insert;
      +
      +  $error = $new_record->replace($old_record);
      +
      +  $error = $record->delete;
      +
      +  $error = $record->check;
      +
      +=head1 DESCRIPTION
      +
      +An FS::cdr_carrier object represents an CDR carrier or upstream.
      +FS::cdr_carrier inherits from FS::Record.  The following fields are currently
      +supported:
      +
      +=over 4
      +
      +=item carrierid - primary key
      +
      +=item carriername - Carrier name
      +
      +=back
      +
      +=head1 METHODS
      +
      +=over 4
      +
      +=item new HASHREF
      +
      +Creates a new carrier.  To add the carrier to the database, see L<"insert">.
      +
      +Note that this stores the hash reference, not a distinct copy of the hash it
      +points to.  You can ask the object for a copy with the I method.
      +
      +=cut
      +
      +# the new method can be inherited from FS::Record, if a table method is defined
      +
      +sub table { 'cdr_carrier'; }
      +
      +=item insert
      +
      +Adds this record to the database.  If there is an error, returns the error,
      +otherwise returns false.
      +
      +=cut
      +
      +# the insert method can be inherited from FS::Record
      +
      +=item delete
      +
      +Delete this record from the database.
      +
      +=cut
      +
      +# the delete method can be inherited from FS::Record
      +
      +=item replace OLD_RECORD
      +
      +Replaces the OLD_RECORD with this one in the database.  If there is an error,
      +returns the error, otherwise returns false.
      +
      +=cut
      +
      +# the replace method can be inherited from FS::Record
      +
      +=item check
      +
      +Checks all fields to make sure this is a valid carrier.  If there is
      +an error, returns the error, otherwise returns false.  Called by the insert
      +and replace methods.
      +
      +=cut
      +
      +sub check {
      +  my $self = shift;
      +
      +  my $error = 
      +    $self->ut_numbern('carrierid')
      +    || $self->ut_text('carriername')
      +  ;
      +  return $error if $error;
      +
      +  $self->SUPER::check;
      +}
      +
      +=back
      +
      +=head1 BUGS
      +
      +=head1 SEE ALSO
      +
      +L, schema.html from the base documentation.
      +
      +=cut
      +
      +1;
      +
      diff --git a/FS/FS/cdr_type.pm b/FS/FS/cdr_type.pm
      new file mode 100644
      index 000000000..e258bf878
      --- /dev/null
      +++ b/FS/FS/cdr_type.pm
      @@ -0,0 +1,119 @@
      +package FS::cdr_type;
      +
      +use strict;
      +use vars qw( @ISA );
      +use FS::Record qw( qsearch qsearchs );
      +
      +@ISA = qw(FS::Record);
      +
      +=head1 NAME
      +
      +FS::cdr_type - Object methods for cdr_type records
      +
      +=head1 SYNOPSIS
      +
      +  use FS::cdr_type;
      +
      +  $record = new FS::cdr_type \%hash;
      +  $record = new FS::cdr_type { 'column' => 'value' };
      +
      +  $error = $record->insert;
      +
      +  $error = $new_record->replace($old_record);
      +
      +  $error = $record->delete;
      +
      +  $error = $record->check;
      +
      +=head1 DESCRIPTION
      +
      +An FS::cdr_type object represents an CDR type.  FS::cdr_type inherits from
      +FS::Record.  The following fields are currently supported:
      +
      +=over 4
      +
      +=item cdrtypenum - primary key
      +
      +=item typename - CDR type name
      +
      +
      +=back
      +
      +=head1 METHODS
      +
      +=over 4
      +
      +=item new HASHREF
      +
      +Creates a new CDR type.  To add the CDR type to the database, see L<"insert">.
      +
      +Note that this stores the hash reference, not a distinct copy of the hash it
      +points to.  You can ask the object for a copy with the I method.
      +
      +=cut
      +
      +# the new method can be inherited from FS::Record, if a table method is defined
      +
      +sub table { 'cdr_type'; }
      +
      +=item insert
      +
      +Adds this record to the database.  If there is an error, returns the error,
      +otherwise returns false.
      +
      +=cut
      +
      +# the insert method can be inherited from FS::Record
      +
      +=item delete
      +
      +Delete this record from the database.
      +
      +=cut
      +
      +# the delete method can be inherited from FS::Record
      +
      +=item replace OLD_RECORD
      +
      +Replaces the OLD_RECORD with this one in the database.  If there is an error,
      +returns the error, otherwise returns false.
      +
      +=cut
      +
      +# the replace method can be inherited from FS::Record
      +
      +=item check
      +
      +Checks all fields to make sure this is a valid CDR type.  If there is
      +an error, returns the error, otherwise returns false.  Called by the insert
      +and replace methods.
      +
      +=cut
      +
      +# the check method should currently be supplied - FS::Record contains some
      +# data checking routines
      +
      +sub check {
      +  my $self = shift;
      +
      +  my $error = 
      +    $self->ut_numbern('cdrtypenum')
      +    || $self->ut_text('typename')
      +  ;
      +  return $error if $error;
      +
      +  $self->SUPER::check;
      +}
      +
      +=back
      +
      +=head1 BUGS
      +
      +=head1 SEE ALSO
      +
      +L, schema.html from the base documentation.
      +
      +=cut
      +
      +1;
      +
      diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
      index 973fe7c81..50faeb422 100644
      --- a/FS/FS/cust_main.pm
      +++ b/FS/FS/cust_main.pm
      @@ -16,6 +16,7 @@ BEGIN {
       }
       use Digest::MD5 qw(md5_base64);
       use Date::Format;
      +use Date::Parse;
       #use Date::Manip;
       use String::Approx qw(amatch);
       use Business::CreditCard 0.28;
      @@ -3964,8 +3965,6 @@ sub batch_import {
         my $pkgpart = $param->{pkgpart};
         my @fields = @{$param->{fields}};
       
      -  eval "use Date::Parse;";
      -  die $@ if $@;
         eval "use Text::CSV_XS;";
         die $@ if $@;
       
      @@ -4071,8 +4070,6 @@ sub batch_charge {
         my $fh = $param->{filehandle};
         my @fields = @{$param->{fields}};
       
      -  eval "use Date::Parse;";
      -  die $@ if $@;
         eval "use Text::CSV_XS;";
         die $@ if $@;
       
      diff --git a/FS/FS/part_pkg/voip_sqlradacct.pm b/FS/FS/part_pkg/voip_sqlradacct.pm
      index fd9c1ddb5..bf18003ab 100644
      --- a/FS/FS/part_pkg/voip_sqlradacct.pm
      +++ b/FS/FS/part_pkg/voip_sqlradacct.pm
      @@ -41,6 +41,7 @@ sub calc_setup {
         $self->option('setup_fee');
       }
       
      +#false laziness w/voip_cdr... resolve it if this one ever gets used again
       sub calc_recur {
         my($self, $cust_pkg, $sdate, $details ) = @_;
       
      diff --git a/FS/MANIFEST b/FS/MANIFEST
      index 452041e35..f6f833589 100644
      --- a/FS/MANIFEST
      +++ b/FS/MANIFEST
      @@ -122,6 +122,7 @@ FS/part_pkg/sql_generic.pm
       FS/part_pkg/sqlradacct_hour.pm
       FS/part_pkg/subscription.pm
       FS/part_pkg/voip_sqlradacct.pm
      +FS/part_pkg/voip_cdr.pm
       FS/part_pop_local.pm
       FS/part_referral.pm
       FS/part_svc.pm
      @@ -261,7 +262,7 @@ t/part_pkg-sql_external.t
       t/part_pkg-sql_generic.t
       t/part_pkg-sqlradacct_hour.t
       t/part_pkg-subscription.t
      -t/part_pkg-voip_sqlradacct.t
      +t/part_pkg-voip_cdr.t
       t/part_pop_local.t
       t/part_referral.t
       t/part_svc.t
      @@ -308,3 +309,11 @@ t/banned_pay.t
       FS/cancel_reason.pm
       t/cancel_reason.t
       bin/freeside-prepaidd
      +FS/cdr.pm
      +t/cdr.t
      +FS/cdr_calltype.pm
      +t/cdr_calltype.t
      +FS/cdr_type.pm
      +t/cdr_type.t
      +FS/cdr_carrier.pm
      +t/cdr_carrier.t
      diff --git a/FS/t/cdr.t b/FS/t/cdr.t
      new file mode 100644
      index 000000000..1d1f3eb4e
      --- /dev/null
      +++ b/FS/t/cdr.t
      @@ -0,0 +1,5 @@
      +BEGIN { $| = 1; print "1..1\n" }
      +END {print "not ok 1\n" unless $loaded;}
      +use FS::cdr;
      +$loaded=1;
      +print "ok 1\n";
      diff --git a/FS/t/cdr_calltype.t b/FS/t/cdr_calltype.t
      new file mode 100644
      index 000000000..d4e13943e
      --- /dev/null
      +++ b/FS/t/cdr_calltype.t
      @@ -0,0 +1,5 @@
      +BEGIN { $| = 1; print "1..1\n" }
      +END {print "not ok 1\n" unless $loaded;}
      +use FS::cdr_calltype;
      +$loaded=1;
      +print "ok 1\n";
      diff --git a/FS/t/cdr_carrier.t b/FS/t/cdr_carrier.t
      new file mode 100644
      index 000000000..1e2161558
      --- /dev/null
      +++ b/FS/t/cdr_carrier.t
      @@ -0,0 +1,5 @@
      +BEGIN { $| = 1; print "1..1\n" }
      +END {print "not ok 1\n" unless $loaded;}
      +use FS::cdr_carrier;
      +$loaded=1;
      +print "ok 1\n";
      diff --git a/FS/t/cdr_type.t b/FS/t/cdr_type.t
      new file mode 100644
      index 000000000..9dff15a32
      --- /dev/null
      +++ b/FS/t/cdr_type.t
      @@ -0,0 +1,5 @@
      +BEGIN { $| = 1; print "1..1\n" }
      +END {print "not ok 1\n" unless $loaded;}
      +use FS::cdr_type;
      +$loaded=1;
      +print "ok 1\n";
      diff --git a/FS/t/part_pkg-voip_cdr.t b/FS/t/part_pkg-voip_cdr.t
      new file mode 100644
      index 000000000..2d988a34f
      --- /dev/null
      +++ b/FS/t/part_pkg-voip_cdr.t
      @@ -0,0 +1,5 @@
      +BEGIN { $| = 1; print "1..1\n" }
      +END {print "not ok 1\n" unless $loaded;}
      +use FS::part_pkg::voip_cdr;
      +$loaded=1;
      +print "ok 1\n";
      diff --git a/README.1.7.0 b/README.1.7.0
      new file mode 100644
      index 000000000..834972ae2
      --- /dev/null
      +++ b/README.1.7.0
      @@ -0,0 +1,19 @@
      +
      +install DBIx::DBSchema 0.29 (or later)
      +
      +make install-perl-modules
      +run "freeside-upgrade username" to uprade your database schema
      +
      +(if freeside-upgrade hangs, try stopping Apache, all Freeside processes, and
      + anything else connected to your database, especially on older Pg versions)
      +
      +If you have any records in the cust_tax_exempt table, you *MUST* migrate them
      +to the new cust_tax_exempt_pkg table.  An example script to get you started is
      +in bin/fs-migrate-cust_tax_exempt - it may need to be customized for your
      +specific data.
      +
      +------
      +
      +make install-docs
      + (or "make deploy" if you've got everything setup in the Makefile)
      +
      diff --git a/README.2.0.0 b/README.2.0.0
      deleted file mode 100644
      index 3f5f1158f..000000000
      --- a/README.2.0.0
      +++ /dev/null
      @@ -1,17 +0,0 @@
      -
      -make install-perl-modules
      -run "freeside-upgrade username" to uprade your database schema
      -
      -(if freeside-upgrade hangs, try stopping Apache, all Freeside processes, and
      - anything else connected to your database, especially on older Pg versions)
      -
      -If you have any records in the cust_tax_exempt table, you *MUST* migrate them
      -to the new cust_tax_exempt_pkg table.  An example script to get you started is
      -in bin/fs-migrate-cust_tax_exempt - it may need to be customized for your
      -specific data.
      -
      -------
      -
      -make install-docs
      - (or "make deploy" if you've got everything setup in the Makefile)
      -
      diff --git a/bin/cdr_calltype.import b/bin/cdr_calltype.import
      new file mode 100755
      index 000000000..a998284f6
      --- /dev/null
      +++ b/bin/cdr_calltype.import
      @@ -0,0 +1,41 @@
      +#!/usr/bin/perl -w
      +#
      +# bin/cdr_calltype.import ivan ~ivan/convergent/newspecs/fixed_inbound/calltypes.csv
      +
      +use strict;
      +use FS::UID qw(dbh adminsuidsetup);
      +use FS::cdr_calltype;
      +
      +my $user = shift or die &usage;
      +adminsuidsetup $user;
      +
      +while (<>) {
      +
      +  chomp;
      +  my $line = $_;
      +
      +  #$line =~ /^(\d+),"([^"]+)"$/ or do {
      +  $line =~ /^(\d+),"([^"]+)"/ or do {
      +    warn "unparsable line: $line\n";
      +    next;
      +  };
      +
      +  my $cdr_calltype = new FS::cdr_calltype {
      +    'calltypenum'  => $1,
      +    'calltypename' => $2,
      +  };
      +  
      +  #my $error = $cdr_calltype->check;
      +  my $error = $cdr_calltype->insert;
      +  if ( $error ) {
      +    warn "********** $error FOR LINE: $line\n";
      +    dbh->commit;
      +    #my $wait = scalar();
      +  }
      +
      +}
      +
      +sub usage {
      +  "Usage:\n\ncdr_calltype.import username filename ...\n";
      +}
      +
      diff --git a/htetc/handler.pl b/htetc/handler.pl
      index 06060b20a..1bbea16d1 100644
      --- a/htetc/handler.pl
      +++ b/htetc/handler.pl
      @@ -88,7 +88,7 @@ sub handler
           #rar
           { package HTML::Mason::Commands;
             use strict;
      -      use vars qw( $cgi $p );
      +      use vars qw( $cgi $p $fsurl);
             use vars qw( %session );
             use CGI 2.47 qw(-private_tempfiles);
             #use CGI::Carp qw(fatalsToBrowser);
      @@ -118,8 +118,8 @@ sub handler
             use FS::UID qw(cgisuidsetup dbh getotaker datasrc driver_name);
             use FS::Record qw(qsearch qsearchs fields dbdef);
             use FS::Conf;
      -      use FS::CGI qw(header menubar popurl table itable ntable idiot eidiot
      -                     small_custview myexit http_header);
      +      use FS::CGI qw(header menubar popurl rooturl table itable ntable idiot
      +                     eidiot small_custview myexit http_header);
             use FS::UI::Web;
             use FS::Msgcat qw(gettext geterror);
             use FS::Misc qw( send_email send_fax );
      @@ -172,6 +172,7 @@ sub handler
             use FS::agent_payment_gateway;
             use FS::XMLRPC;
             use FS::payby;
      +      use FS::cdr;
       
             if ( %%%RT_ENABLED%%% ) {
               eval '
      @@ -234,9 +235,9 @@ sub handler
               &cgisuidsetup($cgi);
               #&cgisuidsetup($r);
               $p = popurl(2);
      +        $fsurl = rooturl();
             }
       
      -
             sub include {
               use vars qw($m);
               $m->scomp(@_);
      diff --git a/httemplate/misc/cdr-import.html b/httemplate/misc/cdr-import.html
      new file mode 100644
      index 000000000..dc1733249
      --- /dev/null
      +++ b/httemplate/misc/cdr-import.html
      @@ -0,0 +1,15 @@
      +<%= include("/elements/header.html",'Call Detail Record Import') %>
      +
      +Import a CSV file containing Call Detail Records (CDRs).

      +CDR Format:

      + +Filename:

      + + + + +<%= include('/elements/footer.html') %> + diff --git a/httemplate/misc/process/cdr-import.html b/httemplate/misc/process/cdr-import.html new file mode 100644 index 000000000..381b07820 --- /dev/null +++ b/httemplate/misc/process/cdr-import.html @@ -0,0 +1,26 @@ +<% + + my $fh = $cgi->upload('csvfile'); + + my $error = defined($fh) + ? FS::cdr::batch_import( { + 'filehandle' => $fh, + 'format' => $cgi->param('format'), + } ) + : 'No file'; + + if ( $error ) { + %> + + <% + eidiot($error); +# $cgi->param('error', $error); +# print $cgi->redirect( "${p}cust_main-import.cgi + } else { + %> + + <%= include("/elements/header.html",'Import sucessful') %> + + <%= include("/elements/footer.html",'Import sucessful') %> <% + } +%> diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html new file mode 100644 index 000000000..75049b443 --- /dev/null +++ b/httemplate/search/cdr.html @@ -0,0 +1,20 @@ +<% + +my $hashref = {}; +#process params for CDR search, populate $hashref... + +my $count_query = 'SELECT COUNT(*) FROM cdr'; +# and fixup $count_query + +%><%= include( 'elements/search.html', + 'title' => 'Call Detail Records', + 'name' => 'call detail records', + 'query' => { 'table' => 'cdr', + 'hashref' => $hashref + }, + 'count_query' => $count_query, + 'header' => [ fields('cdr') ], #XXX fill in some nice names + 'fields' => [ fields('cdr') ], #XXX fill in some pretty-print + # processing, etc. + ) +%> diff --git a/httemplate/search/report_cdr.html b/httemplate/search/report_cdr.html new file mode 100644 index 000000000..b9ad55e10 --- /dev/null +++ b/httemplate/search/report_cdr.html @@ -0,0 +1,7 @@ +<%= include('/elements/header.html', 'Call Detail Record Search' ) %> + +
      + + +<%= include('/elements/footer.html') %> + -- cgit v1.2.1 From 53bf3dff4149408fde4f1d0d87fd18c6033bc71f Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 22 Feb 2006 07:12:28 +0000 Subject: a better CGI::rooturl(), will have to do for now --- FS/FS/CGI.pm | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/FS/FS/CGI.pm b/FS/FS/CGI.pm index 9dc635ad2..f1f2a3dca 100644 --- a/FS/FS/CGI.pm +++ b/FS/FS/CGI.pm @@ -9,7 +9,7 @@ use URI::URL; use FS::UID; @ISA = qw(Exporter); -@EXPORT_OK = qw(header menubar idiot eidiot popurl table itable ntable +@EXPORT_OK = qw(header menubar idiot eidiot popurl rooturl table itable ntable small_custview myexit http_header); =head1 NAME @@ -225,6 +225,34 @@ sub popurl { $x; } +=item rooturl + +=cut + +sub rooturl { + #this doesn't work so well... + #'%%%FREESIDE_URL%%%'; + + # better to start with the client-provided URL + my $cgi = &FS::UID::cgi; + my $url_string = $cgi->isa('Apache') ? $cgi->uri : $cgi->url; + $url_string =~ s/\?.*//; + + #even though this is kludgy + $url_string =~ + s{ + (browse|config|docs|edit|graph|misc|search|view) + / + (process/)? + ([\w\-\.]+) + $ + } + {}x; + + $url_string; + +} + =item table Returns HTML tag for beginning a table. -- cgit v1.2.1 From e9b7648aa4f838e45de95128dc22053aa8eafdb4 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 22 Feb 2006 13:07:48 +0000 Subject: add vonage click2call feature --- FS/FS/Conf.pm | 20 ++++++++++++++++++ httemplate/elements/phonenumber.html | 23 +++++++++++++++++++++ httemplate/images/red_telephone_mimooh_01.png | Bin 0 -> 921 bytes httemplate/view/cust_main/contacts.html | 28 ++++++++++++++++++++------ 4 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 httemplate/elements/phonenumber.html create mode 100644 httemplate/images/red_telephone_mimooh_01.png diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 88dbdf082..a5add28d8 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1661,6 +1661,26 @@ httemplate/docs/config.html 'type' => 'checkbox', }, + #these should become per-user... + { + 'key' => 'vonage-username', + 'section' => '', + 'description' => 'Vonage Click2Call username (see https://secure.click2callu.com/)', + 'type' => 'text', + }, + { + 'key' => 'vonage-password', + 'section' => '', + 'description' => 'Vonage Click2Call username (see https://secure.click2callu.com/)', + 'type' => 'text', + }, + { + 'key' => 'vonage-fromnumber', + 'section' => '', + 'description' => 'Vonage Click2Call number (see https://secure.click2callu.com/)', + 'type' => 'text', + }, + ); 1; diff --git a/httemplate/elements/phonenumber.html b/httemplate/elements/phonenumber.html new file mode 100644 index 000000000..1330ca109 --- /dev/null +++ b/httemplate/elements/phonenumber.html @@ -0,0 +1,23 @@ +<% + my( $number, %opt ) = @_; + my $conf = new FS::Conf; + ( my $snumber = $number ) =~ s/\D//g; +%> + + + + +<% if ( length($number) ) { %> + <%= $number %> + <% if ( $opt{'callable'} && $conf->config('vonage-username') ) { %> + Call this number + <% } %> +<% } else { %> +   +<% } %> diff --git a/httemplate/images/red_telephone_mimooh_01.png b/httemplate/images/red_telephone_mimooh_01.png new file mode 100644 index 000000000..2212ff0e8 Binary files /dev/null and b/httemplate/images/red_telephone_mimooh_01.png differ diff --git a/httemplate/view/cust_main/contacts.html b/httemplate/view/cust_main/contacts.html index 456d117a6..89926ea64 100644 --- a/httemplate/view/cust_main/contacts.html +++ b/httemplate/view/cust_main/contacts.html @@ -51,14 +51,22 @@ Billing address %> <%= $daytime_label %> - - <%= $cust_main->daytime || ' ' %> + + <%= include('/elements/phonenumber.html', + $cust_main->daytime, + 'callable'=>1 + ) + %> <%= $night_label %> - - <%= $cust_main->night || ' ' %> + + <%= include('/elements/phonenumber.html', + $cust_main->night, + 'callable'=>1 + ) + %> @@ -111,13 +119,21 @@ Service address <%= $daytime_label %> - <%= $cust_main->get("${pre}daytime") || ' ' %> + <%= include('/elements/phonenumber.html', + $cust_main->get("${pre}daytime"), + 'callable'=>1 + ) + %> <%= $night_label %> - <%= $cust_main->get("${pre}night") || ' ' %> + <%= include('/elements/phonenumber.html', + $cust_main->get("${pre}night"), + 'callable'=>1 + ) + %> -- cgit v1.2.1 From ec3dc4ea9ac60ba99adb6c067452e5aac30957c5 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 28 Feb 2006 19:34:01 +0000 Subject: update POD docs regarding new price plans --- FS/FS/part_pkg.pm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index a5e3c2170..30a97227e 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -759,15 +759,16 @@ sub plan_info { =head1 NEW PLAN CLASSES -A module should be added in FS/FS/part_pkg/ (an example may be found in -eg/plan_template.pm) +A module should be added in FS/FS/part_pkg/ Eventually, an example may be +found in eg/plan_template.pm. Until then, it is suggested that you use the +other modules in FS/FS/part_pkg/ as a guide. =head1 BUGS The delete method is unimplemented. setup and recur semantics are not yet defined (and are implemented in -FS::cust_bill. hmm.). +FS::cust_bill. hmm.). now they're deprecated and need to go. plandata should go -- cgit v1.2.1 From 02ffd747f8cbc05815c0d96f437c507cfac04ba6 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 3 Mar 2006 15:02:33 +0000 Subject: agent-specific sales/credit/receipts summary --- FS/FS/Report/Table/Monthly.pm | 102 ++++++++++++++++++++-------------- httemplate/graph/money_time-graph.cgi | 7 +++ httemplate/graph/money_time.cgi | 29 +++++++--- 3 files changed, 89 insertions(+), 49 deletions(-) diff --git a/FS/FS/Report/Table/Monthly.pm b/FS/FS/Report/Table/Monthly.pm index 89d44f92f..9ef108c44 100644 --- a/FS/FS/Report/Table/Monthly.pm +++ b/FS/FS/Report/Table/Monthly.pm @@ -45,6 +45,7 @@ sub data { my $syear = $self->{'start_year'}; my $emonth = $self->{'end_month'}; my $eyear = $self->{'end_year'}; + my $agentnum = $self->{'agentnum'}; my %data; @@ -59,7 +60,7 @@ sub data { push @{$data{eperiod}}, $eperiod; foreach my $item ( @{$self->{'items'}} ) { - push @{$data{$item}}, $self->$item($speriod, $eperiod); + push @{$data{$item}}, $self->$item($speriod, $eperiod, $agentnum); } } @@ -69,53 +70,60 @@ sub data { } sub invoiced { #invoiced - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" - SELECT SUM(charged) FROM cust_bill - WHERE ". $self->in_time_period($speriod, $eperiod) + SELECT SUM(charged) + FROM cust_bill + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum) ); } sub netsales { #net sales - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; my $credited = $self->scalar_sql(" SELECT SUM(cust_credit_bill.amount) - FROM cust_credit_bill, cust_bill - WHERE cust_bill.invnum = cust_credit_bill.invnum - AND ". $self->in_time_period($speriod, $eperiod, 'cust_bill') + FROM cust_credit_bill + LEFT JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum, 'cust_bill') ); #horrible local kludge my $expenses = !$expenses_kludge ? 0 : $self->scalar_sql(" SELECT SUM(cust_bill_pkg.setup) - FROM cust_bill_pkg, cust_bill, cust_pkg, part_pkg - WHERE cust_bill.invnum = cust_bill_pkg.invnum - AND ". $self->in_time_period($speriod, $eperiod, 'cust_bill'). " - AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum - AND cust_pkg.pkgpart = part_pkg.pkgpart - AND LOWER(part_pkg.pkg) LIKE 'expense _%' + FROM cust_bill_pkg + LEFT JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) + LEFT JOIN cust_pkg USING ( pkgnum ) + LEFT JOIN part_pkg USING ( pkgpart ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum, 'cust_bill'). " + AND LOWER(part_pkg.pkg) LIKE 'expense _%' "); - $self->invoiced($speriod,$eperiod) - $credited - $expenses; + $self->invoiced($speriod,$eperiod,$agentnum) - $credited - $expenses; } #deferred revenue sub receipts { #cashflow - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; my $refunded = $self->scalar_sql(" - SELECT SUM(refund) FROM cust_refund - WHERE ". $self->in_time_period($speriod, $eperiod) + SELECT SUM(refund) + FROM cust_refund + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum) ); #horrible local kludge that doesn't even really work right my $expenses = !$expenses_kludge ? 0 : $self->scalar_sql(" SELECT SUM(cust_bill_pay.amount) - FROM cust_bill_pay, cust_bill - WHERE cust_bill_pay.invnum = cust_bill.invnum - AND ". $self->in_time_period($speriod, $eperiod, 'cust_bill_pay'). " + FROM cust_bill_pay + LEFT JOIN cust_bill USING ( invnum ) + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum, 'cust_bill_pay'). " AND 0 < ( SELECT COUNT(*) from cust_bill_pkg, cust_pkg, part_pkg WHERE cust_bill.invnum = cust_bill_pkg.invnum AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum @@ -125,40 +133,48 @@ sub receipts { #cashflow "); # my $expenses_sql2 = "SELECT SUM(cust_bill_pay.amount) FROM cust_bill_pay, cust_bill_pkg, cust_bill, cust_pkg, part_pkg WHERE cust_bill_pay.invnum = cust_bill.invnum AND cust_bill.invnum = cust_bill_pkg.invnum AND cust_bill_pay._date >= $speriod AND cust_bill_pay._date < $eperiod AND cust_pkg.pkgnum = cust_bill_pkg.pkgnum AND cust_pkg.pkgpart = part_pkg.pkgpart AND LOWER(part_pkg.pkg) LIKE 'expense _%'"; - $self->payments($speriod, $eperiod) - $refunded - $expenses; + $self->payments($speriod, $eperiod, $agentnum) - $refunded - $expenses; } sub payments { - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" - SELECT SUM(paid) FROM cust_pay - WHERE ". $self->in_time_period($speriod, $eperiod) + SELECT SUM(paid) + FROM cust_pay + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum) ); } sub credits { - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" - SELECT SUM(amount) FROM cust_credit - WHERE ". $self->in_time_period($speriod, $eperiod) + SELECT SUM(amount) + FROM cust_credit + LEFT JOIN cust_main USING ( custnum ) + WHERE ". $self->in_time_period_and_agent($speriod, $eperiod, $agentnum) ); } +# NEEDS TO BE AGENTNUM-capable sub canceled { #active - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" - SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND 0 = ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) - ) - AND cust_pkg.cancel > $speriod AND cust_pkg.cancel < $eperiod + SELECT COUNT(*) + FROM cust_pkg + LEFT JOIN cust_main USING ( custnum ) + WHERE 0 = ( SELECT COUNT(*) + FROM cust_pkg + WHERE cust_pkg.custnum = cust_main.custnum + AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) + ) + AND cust_pkg.cancel > $speriod AND cust_pkg.cancel < $eperiod "); } +# NEEDS TO BE AGENTNUM-capable sub newaccount { #newaccount - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.custnum = cust_main.custnum @@ -168,8 +184,9 @@ sub newaccount { #newaccount "); } +# NEEDS TO BE AGENTNUM-capable sub suspended { #suspended - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); + my( $self, $speriod, $eperiod, $agentnum ) = @_; $self->scalar_sql(" SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.custnum = cust_main.custnum @@ -182,10 +199,13 @@ sub suspended { #suspended "); } -sub in_time_period { - my( $self, $speriod, $eperiod ) = ( shift, shift, shift ); +sub in_time_period_and_agent { + my( $self, $speriod, $eperiod, $agentnum ) = splice(@_, 0, 4); my $table = @_ ? shift().'.' : ''; - "${table}_date >= $speriod AND ${table}_date < $eperiod"; + my $sql = "${table}_date >= $speriod AND ${table}_date < $eperiod"; + $sql .= " AND agentnum = $agentnum" + if $agentnum; + $sql; } sub scalar_sql { diff --git a/httemplate/graph/money_time-graph.cgi b/httemplate/graph/money_time-graph.cgi index bb3d23aae..fc8207a81 100755 --- a/httemplate/graph/money_time-graph.cgi +++ b/httemplate/graph/money_time-graph.cgi @@ -12,6 +12,12 @@ my $eyear = $cgi->param('eyear') || 1900+$curyear; my $emonth = $cgi->param('emonth') || $curmon+1; #if ( $emonth++>12 ) { $emonth-=12; $eyear++; } +# XXX or virtual +my $agentnum = ''; +if ( $cgi->param('agentnum') =~ /^(\d*)$/ ) { + $agentnum = $1; +} + #my @labels; #my %data; @@ -37,6 +43,7 @@ my $report = new FS::Report::Table::Monthly ( 'start_year' => $syear, 'end_month' => $emonth, 'end_year' => $eyear, + 'agentnum' => $agentnum, ); my %data = %{$report->data}; diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi index 1c7d54266..874f58b09 100644 --- a/httemplate/graph/money_time.cgi +++ b/httemplate/graph/money_time.cgi @@ -12,13 +12,23 @@ my $smonth = $cgi->param('smonth') || $curmon+1; my $eyear = $cgi->param('eyear') || 1900+$curyear; my $emonth = $cgi->param('emonth') || $curmon+1; +#XXX or virtual +my( $agentnum, $agent ) = ('', ''); +if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { + $agentnum = $1; + $agent = qsearchs('agent', { 'agentnum' => $agentnum } ); + die "agentnum $agentnum not found!" unless $agent; +} +my $agentname = $agent ? $agent->agent.' ' : ''; +warn $agentname; + +%> + +<%= include('/elements/header.html', + $agentname. 'Sales, Credits and Receipts Summary' + ) %> - - - Sales, Credits and Receipts Summary - -
      @@ -41,9 +51,9 @@ my %color = ( 'receipts' => '00cc00', #green ); my %link = ( - 'invoiced' => "${p}search/cust_bill.html?", - 'credits' => "${p}search/cust_credit.html?", - 'payments' => "${p}search/cust_pay.cgi?magic=_date;", + 'invoiced' => "${p}search/cust_bill.html?agentnum=$agentnum;", + 'credits' => "${p}search/cust_credit.html?agentnum=$agentnum;", + 'payments' => "${p}search/cust_pay.cgi?magic=_date;agentnum=$agentnum;", ); my $report = new FS::Report::Table::Monthly ( @@ -52,6 +62,7 @@ my $report = new FS::Report::Table::Monthly ( 'start_year' => $syear, 'end_month' => $emonth, 'end_year' => $eyear, + 'agentnum' => $agentnum, ); my $data = $report->data; @@ -119,6 +130,8 @@ From +for agent: <%= include('/elements/select-agent.html', $agentnum) %> + -- cgit v1.2.1 From 5809a99d862a6bd0da6742479f18728aae8216a2 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 8 Mar 2006 08:21:37 +0000 Subject: add space in error msg --- FS/FS/agent.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm index 83f0ce5b5..849dafa2a 100644 --- a/FS/FS/agent.pm +++ b/FS/FS/agent.pm @@ -195,7 +195,7 @@ sub num_sql { my( $self, $sql ) = @_; my $statement = "SELECT COUNT(*) FROM cust_main WHERE agentnum = ? AND $sql"; my $sth = dbh->prepare($statement) or die dbh->errstr." preparing $statement"; - $sth->execute($self->agentnum) or die $sth->errstr. "executing $statement"; + $sth->execute($self->agentnum) or die $sth->errstr. " executing $statement"; $sth->fetchrow_arrayref->[0]; } -- cgit v1.2.1 From 600a0939e7e7e589dae4f4f5bfef3650728940b7 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 8 Mar 2006 10:05:01 +0000 Subject: Add a new table for inventory with for DIDs/serials/etc., and an additional new table for inventory category (i.e. to distinguish DIDs, serials, MACs, etc.) --- FS/FS/Schema.pm | 41 ++++++- FS/FS/inventory_class.pm | 164 ++++++++++++++++++++++++++ FS/FS/inventory_item.pm | 126 ++++++++++++++++++++ FS/MANIFEST | 5 + FS/t/inventory_class.t | 5 + FS/t/inventory_item.t | 5 + bin/generate-table-module | 1 + htetc/handler.pl | 10 +- httemplate/edit/elements/edit.html | 118 ++++++++++++++++++ httemplate/edit/inventory_class.html | 9 ++ httemplate/edit/process/elements/process.html | 46 ++++++++ httemplate/edit/process/inventory_class.html | 4 + httemplate/search/elements/search.html | 80 ++++++++++++- httemplate/search/inventory_class.html | 58 +++++++++ httemplate/search/inventory_item.html | 35 ++++++ 15 files changed, 700 insertions(+), 7 deletions(-) create mode 100644 FS/FS/inventory_class.pm create mode 100644 FS/FS/inventory_item.pm create mode 100644 FS/t/inventory_class.t create mode 100644 FS/t/inventory_item.t create mode 100644 httemplate/edit/elements/edit.html create mode 100644 httemplate/edit/inventory_class.html create mode 100644 httemplate/edit/process/elements/process.html create mode 100644 httemplate/edit/process/inventory_class.html create mode 100644 httemplate/search/inventory_class.html create mode 100644 httemplate/search/inventory_item.html diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 3ca599b49..a049b8bad 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -443,7 +443,7 @@ sub tables_hashref { #'index' => [ ['last'], ['company'] ], 'index' => [ ['last'], [ 'company' ], [ 'referral_custnum' ], [ 'daytime' ], [ 'night' ], [ 'fax' ], [ 'refnum' ], - [ 'county' ], [ 'state' ], [ 'country' ] + [ 'county' ], [ 'state' ], [ 'country' ], [ 'zip' ], ], }, @@ -1352,6 +1352,10 @@ sub tables_hashref { 'upstream_price', 'decimal', 'NULL', '10,2', '', '', 'upstream_rateplanid', 'int', 'NULL', '', '', '', #? + # how it was rated internally... + 'ratedetailnum', 'int', 'NULL', '', '', '', + 'rated_price', 'decimal', 'NULL', '10,2', '', '', + 'distance', 'decimal', 'NULL', '', '', '', 'islocal', 'int', 'NULL', '', '', '', # '', '', 0, '' instead? @@ -1373,7 +1377,7 @@ sub tables_hashref { # a svcnum... right..? 'svcnum', 'int', 'NULL', '', '', '', - #NULL, done, skipped, pushed_downstream (or something) + #NULL, done (or something) 'freesidestatus', 'varchar', 'NULL', 32, '', '', ], @@ -1412,6 +1416,39 @@ sub tables_hashref { 'index' => [], }, + #map upstream rateid (XXX or rateplanid?) to ours... + 'cdr_upstream_rate' => { # XXX or 'cdr_upstream_rateplan' ?? + 'columns' => [ + # XXX or 'upstream_rateplanid' ?? + 'upstream_rateid', 'int', 'NULL', '', '', '', + 'ratedetailnum', 'int', 'NULL', '', '', '', + ], + 'primary_key' => '', #XXX need a primary key + 'unique' => [ [ 'upstream_rateid' ] ], #unless we add another field, yeah + 'index' => [], + }, + + 'inventory_item' => { + 'columns' => [ + 'itemnum', 'serial', '', '', '', '', + 'classnum', 'int', '', '', '', '', + 'item', 'varchar', '', $char_d, '', '', + 'svcnum', 'int', 'NULL', '', '', '', + ], + 'primary_key' => 'itemnum', + 'unique' => [ [ 'classnum', 'item' ] ], + 'index' => [ [ 'classnum' ], [ 'svcnum' ] ], + }, + + 'inventory_class' => { + 'columns' => [ + 'classnum', 'serial', '', '', '', '', + 'classname', 'varchar', $char_d, '', '', '', + ], + 'primary_key' => 'classnum', + 'unique' => [], + 'index' => [], + }, }; diff --git a/FS/FS/inventory_class.pm b/FS/FS/inventory_class.pm new file mode 100644 index 000000000..04ee207d3 --- /dev/null +++ b/FS/FS/inventory_class.pm @@ -0,0 +1,164 @@ +package FS::inventory_class; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( dbh qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::inventory_class - Object methods for inventory_class records + +=head1 SYNOPSIS + + use FS::inventory_class; + + $record = new FS::inventory_class \%hash; + $record = new FS::inventory_class { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::inventory_class object represents a class of inventory, such as "DID +numbers" or "physical equipment serials". FS::inventory_class inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item classnum - primary key + +=item classname - Name of this class + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new inventory class. To add the class to the database, see +L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'inventory_class'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid inventory class. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('classnum') + || $self->ut_textn('classname') + ; + return $error if $error; + + $self->SUPER::check; +} + +=item num_avail + +Returns the number of available (unused/unallocated) inventory items of this +class (see L). + +=cut + +sub num_avail { + shift->num_sql('( svcnum IS NULL OR svcnum = 0 )'); +} + +sub num_sql { + my( $self, $sql ) = @_; + my $sql = "AND $sql" if length($sql); + my $statement = + "SELECT COUNT(*) FROM inventory_item WHERE classnum = ? $sql"; + my $sth = dbh->prepare($statement) or die dbh->errstr. " preparing $statement"; + $sth->execute($self->classnum) or die $sth->errstr. " executing $statement"; + $sth->fetchrow_arrayref->[0]; +} + +=item num_used + +Returns the number of used (allocated) inventory items of this class (see +L). + +=cut + +sub num_used { + shift->num_sql("svcnum IS NOT NULL AND svcnum > 0 "); +} + +=item num_total + +Returns the total number of inventory items of this class (see +L). + +=cut + +sub num_total { + shift->num_sql(''); +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L, L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/inventory_item.pm b/FS/FS/inventory_item.pm new file mode 100644 index 000000000..5312d95f1 --- /dev/null +++ b/FS/FS/inventory_item.pm @@ -0,0 +1,126 @@ +package FS::inventory_item; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); +use FS::inventory_class; + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::inventory_item - Object methods for inventory_item records + +=head1 SYNOPSIS + + use FS::inventory_item; + + $record = new FS::inventory_item \%hash; + $record = new FS::inventory_item { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::inventory_item object represents a specific piece of (real or virtual) +inventory, such as a specific DID or serial number. FS::inventory_item +inherits from FS::Record. The following fields are currently supported: + +=over 4 + +=item itemnum - primary key + +=item classnum - Inventory class (see L) + +=item item - Item identifier (unique within its inventory class) + +=item svcnum - Customer servcie (see L) + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new item. To add the item to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'inventory_item'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid item. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('itemnum') + || $self->ut_foreign_key('classnum', 'inventory_class', 'classnum' ) + || $self->ut_text('item') + || $self->ut_numbern('svcnum') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index f6f833589..6360d5303 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -262,6 +262,7 @@ t/part_pkg-sql_external.t t/part_pkg-sql_generic.t t/part_pkg-sqlradacct_hour.t t/part_pkg-subscription.t +t/part_pkg-voip_sqlradacct.t t/part_pkg-voip_cdr.t t/part_pop_local.t t/part_referral.t @@ -317,3 +318,7 @@ FS/cdr_type.pm t/cdr_type.t FS/cdr_carrier.pm t/cdr_carrier.t +FS/inventory_class.pm +t/inventory_class.t +FS/inventory_item.pm +t/inventory_item.t diff --git a/FS/t/inventory_class.t b/FS/t/inventory_class.t new file mode 100644 index 000000000..80b2fa210 --- /dev/null +++ b/FS/t/inventory_class.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::inventory_class; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/inventory_item.t b/FS/t/inventory_item.t new file mode 100644 index 000000000..8ce9d677c --- /dev/null +++ b/FS/t/inventory_item.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::inventory_item; +$loaded=1; +print "ok 1\n"; diff --git a/bin/generate-table-module b/bin/generate-table-module index fcc3f1d1f..b3204fa06 100755 --- a/bin/generate-table-module +++ b/bin/generate-table-module @@ -13,6 +13,7 @@ my %ut = ( #just guesses 'number' => 'float', 'varchar' => 'text', 'text' => 'text', + 'serial' => 'number', ); my $dbdef_table = dbdef_dist->table($table) diff --git a/htetc/handler.pl b/htetc/handler.pl index 1bbea16d1..15f9203f8 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -173,6 +173,8 @@ sub handler use FS::XMLRPC; use FS::payby; use FS::cdr; + use FS::inventory_class; + use FS::inventory_item; if ( %%%RT_ENABLED%%% ) { eval ' @@ -245,8 +247,10 @@ sub handler sub redirect { my( $location ) = @_; + warn 'redir1 $m='.$m; use vars qw($m); $m->clear_buffer; + warn 'redir3-prof'; #false laziness w/above if ( defined(@DBIx::Profile::ISA) ) { #profiling redirect @@ -263,10 +267,14 @@ sub handler ); dbh->{'private_profile'} = {}; - $m->abort(200); + warn 'redir9-prof'; + my $rv = $m->abort(200); + warn "redir10-prof: $rv"; + $rv; } else { #normal redirect + warn 'redir9-redirect'; $m->redirect($location); } diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html new file mode 100644 index 000000000..ce6e2dbb1 --- /dev/null +++ b/httemplate/edit/elements/edit.html @@ -0,0 +1,118 @@ +<% + + # options example... + # + # 'name' => + # 'table' => + # #? 'primary_key' => #required when the dbdef doesn't know...??? + # 'labels' => { + # 'column' => 'Label', + # } + # + # listref - each item is a literal column name (or method) or (notyet) coderef + # if not specified all columns (except for the primary key) will be editable + # 'fields' => [ + # ] + # + # 'menubar' => '', #menubar arrayref + + my(%opt) = @_; + + #false laziness w/process.html + my $table = $opt{'table'}; + my $class = "FS::$table"; + my $pkey = dbdef->table($table)->primary_key; #? $opt{'primary_key'} || + my $fields = $opt{'fields'} + #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ]; + || [ grep { $_ ne $pkey } fields($table) ]; + + my $object; + if ( $cgi->param('error') ) { + + $object = $class->new( { + map { $_ => scalar($cgi->param($_)) } fields($table) + }); + + } elsif ( $cgi->keywords ) { #editing + + my( $query ) = $cgi->keywords; + $query =~ /^(\d+)$/; + $object = qsearchs( $table, { $pkey => $1 } ); + + } else { #adding + + $object = $class->new( {} ); + + } + + my $action = $object->$pkey() ? 'Edit' : 'Add'; + + my $title = "$action $opt{'name'}"; + + my @menubar = (); + if ( $opt{'menubar'} ) { + @menubar = @{ $opt{'menubar'} }; + } else { + @menubar = ( + 'Main menu' => $p, #eventually get rid of this when the ACL/UI update is done + "View all $opt{'name'}s" => "${p}search/$table.html", #eventually use Lingua::bs to pluralize + ); + } + +%> + + +<%= include("/elements/header.html", $title, + include( '/elements/menubar.html', @menubar ) + ) +%> + +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +

      +<% } %> + +
      + +<%= ( $opt{labels} && exists $opt{labels}->{$pkey} ) + ? $opt{labels}->{$pkey} + : $pkey +%> +#<%= $object->$pkey() || "(NEW)" %> + +<%= ntable("#cccccc",2) %> + +<% foreach my $field ( @$fields ) { %> + + + + + <%= ( $opt{labels} && exists $opt{labels}->{$field} ) + ? $opt{labels}->{$field} + : $field + %> + + + <% + #just text in one size for now... eventually more options for + # uneditable, hidden, + + + + +<% } %> + + + +
      + +"> + +
      + +<%= include("/elements/footer.html") %> + diff --git a/httemplate/edit/inventory_class.html b/httemplate/edit/inventory_class.html new file mode 100644 index 000000000..5dde2e595 --- /dev/null +++ b/httemplate/edit/inventory_class.html @@ -0,0 +1,9 @@ +<%= include( 'elements/edit.html', + 'name' => 'Inventory Class', + 'table' => 'inventory_class', + 'labels' => { + 'classnum' => 'Class number', + 'classname' => 'Class name', + }, + ) +%> diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html new file mode 100644 index 000000000..52c876720 --- /dev/null +++ b/httemplate/edit/process/elements/process.html @@ -0,0 +1,46 @@ +<% + + # options example... + # + # 'table' => + # #? 'primary_key' => #required when the dbdef doesn't know...??? + # #? 'fields' => [] + + my(%opt) = @_; + + #false laziness w/edit.html + my $table = $opt{'table'}; + my $class = "FS::$table"; + my $pkey = dbdef->table($table)->primary_key; #? $opt{'primary_key'} || + my $fields = $opt{'fields'} + #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ]; + || [ fields($table) ]; + + my $pkeyvalue = $cgi->param($pkey); + + my $old = qsearchs( $table, { $pkey => $pkeyvalue } ) if $pkeyvalue; + + my $new = $class->new( { + map { + $_, scalar($cgi->param($_)); + } @$fields + } ); + + my $error; + if ( $pkeyvalue ) { + $error = $new->replace($old); + } else { + warn $new; + $error = $new->insert; + warn $error; + $pkeyvalue = $new->getfield($pkey); + } + + if ( $error ) { + $cgi->param('error', $error); + print $cgi->redirect(popurl(2). "$table.html?". $cgi->query_string ); + } else { + print $cgi->redirect(popurl(3). "search/$table.html"); + } + +%> diff --git a/httemplate/edit/process/inventory_class.html b/httemplate/edit/process/inventory_class.html new file mode 100644 index 000000000..e30e74e7b --- /dev/null +++ b/httemplate/edit/process/inventory_class.html @@ -0,0 +1,4 @@ +<%= include( 'elements/process.html', + 'table' => 'inventory_class', + ) +%> diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html index d19fb4acd..b14bded10 100644 --- a/httemplate/search/elements/search.html +++ b/httemplate/search/elements/search.html @@ -1,5 +1,73 @@ <% + # options example... + # (everything not commented required is optional) + # + # # basic options, required + # 'title' => 'Page title', + # 'name' => 'items', #name for the records returned + # + # # some HTML callbacks... + # 'menubar' => '', #menubar arrayref + # 'html_init' => '', #after the header/menubar and before the pager + # + # #literal SQL query string or qsearch hashref, required + # 'query' => { + # 'table' => 'tablename', + # #everything else is optional... + # 'hashref' => { 'field' => 'value', + # 'field' => { 'op' => '<', + # 'value' => '54', + # }, + # }, + # 'select' => '*', + # 'addl_from' => '', #'LEFT JOIN othertable USING ( key )', + # 'extra_sql' => '', #'AND otherstuff', #'WHERE onlystuff', + # + # + # }, + # # "select * from tablename"; + # + # #required unless 'query' is an SQL query string (shouldn't be...) + # 'count_query' => 'SELECT COUNT(*) FROM tablename', + # + # 'count_addl' => [], #additional count fields listref of sprintf strings + # # [ $money_char.'%.2f total paid', ], + # + # #listref of column labels, + # #required unless 'query' is an SQL query string + # # (if not specified the database column names will be used) + # 'header' => [ '#', 'Item' ], + # + # #listref - each item is a literal column name (or method) or coderef + # #if not specified all columns will be shown + # 'fields' => [ + # 'column', + # sub { my $row = shift; $row->column; }, + # ], + # + # #listref of column footers + # 'footer' => [], + # + # #listref - each item is the empty string, or a listref of ... + # 'links' => + # + # + # 'align' => 'lrc.', #one letter for each column, left/right/center/none + # # can also pass a listref with full values: + # # [ 'left', 'right', 'center', '' ] + # + # #listrefs... + # #currently only HTML, maybe eventually Excel too + # 'color' => [], + # 'size' => [], + # 'style' => [], + # + # #redirect if there's only one item... + # # listref of URL base and column name (or method) + # # or a coderef that returns the same + # 'redirect' => + my(%opt) = @_; #warn join(' / ', map { "$_ => $opt{$_}" } keys %opt ). "\n"; @@ -189,7 +257,9 @@ redirect( $url. $rows->[0]->$method() ); } else { ( my $xlsname = $opt{'name'} ) =~ s/\W//g; - $opt{'name'} =~ s/s$// if $total == 1; + #$opt{'name'} =~ s/s$// if $total == 1; + $opt{'name'} =~ s/((s)e)?s$/$2/ if $total == 1; #should use Lingua::bs + # to "depluralize" my @menubar = (); if ( $opt{'menubar'} ) { @@ -197,6 +267,8 @@ } else { @menubar = ( 'Main menu' => $p ); } + + %> <%= include( '/elements/header.html', $opt{'title'}, include( '/elements/menubar.html', @menubar ) @@ -239,7 +311,8 @@ <%= include('/elements/table-grid.html') %> - <% foreach my $header ( @$header ) { %> + <% + foreach my $header ( @$header ) { %> <%= $header %> <% } %> @@ -386,7 +459,6 @@ <% } %> - - + <%= include( '/elements/footer.html' ) %> <% } %> <% } %> diff --git a/httemplate/search/inventory_class.html b/httemplate/search/inventory_class.html new file mode 100644 index 000000000..1bf1bcbce --- /dev/null +++ b/httemplate/search/inventory_class.html @@ -0,0 +1,58 @@ +<% + +tie my %labels, 'Tie::IxHash', + 'num_avail' => 'Available', # (upload batch)', + 'num_used' => 'In use', #'Used', #'Allocated', + 'num_total' => 'Total', +; +my %inv_action_link = ( + 'num_avail' => 'eventually' +); +my %inv_action_label = ( + 'num_avail' => 'upload_batch' +); + +my $link = [ "${p}edit/inventory_class.html?", 'classnum' ]; + +%><%= include( 'elements/search.html', + 'title' => 'Inventory Classes', + 'name' => 'inventory classes', + 'menubar' => [ 'Add a new inventory class' => + $p.'edit/inventory_class.html', + ], + 'query' => { 'table' => 'inventory_class', }, + 'count_query' => 'SELECT COUNT(*) FROM inventory_class', + 'header' => [ '#', 'Inventory class', 'Inventory' ], + 'fields' => [ 'classnum', + 'classname', + sub { + #my $inventory_class = shift; + my $i_c = shift; + + [ map { + [ + { + 'data' => ''. $i_c->$_(). '', + 'align' => 'right', + }, + { + 'data' => $labels{$_}, + 'align' => 'left', + }, + { 'data' => ( exists($inv_action_link{$_}) + ? '('. $inv_action_label{$_}. ')' + : '' + ), + 'align' => 'left', + }, + ] + } keys %labels + ]; + }, + ], + 'links' => [ $link, + $link, + '', + ], + ) +%> diff --git a/httemplate/search/inventory_item.html b/httemplate/search/inventory_item.html new file mode 100644 index 000000000..ff7f1fadf --- /dev/null +++ b/httemplate/search/inventory_item.html @@ -0,0 +1,35 @@ +<% + +my $classnum = $cgi->param('classnum'); +$classnum =~ /^(\d+)$/ or eidiot "illegal agentnum $agentnum"; +$classnum = $1; +my $inventory_class = qsearchs('inventory_class', { 'classnum' => $classnum } ); + +my $count_query = + "SELECT COUNT(*) FROM inventory_class WHERE classnum = $classnum"; + +%><%= include( 'elements/search.html', + 'title' => $inventory_class->classname. ' Inventory', + + #less lame to use Lingua:: something to pluralize + 'name' => $inventory_class->classname. 's', + + 'query' => { + 'table' => 'inventory_item', + 'hashref' => { 'classnum' => $classnum }, + }, + + 'count_query' => $count_query, + + # XXX proper full service/customer link ala svc_acct + 'header' => [ '#', $inventory_class->classname, 'svcnum' ], + + 'fields' => [ + 'itemnum', + 'item', + 'svcnum', #XXX proper full service customer link ala svc_acct + # "unallocated" ? "available" ? + ], + + ) +%> -- cgit v1.2.1 From 6d2cd8e6fc566b4fccd0075156e3e1ddd59fa042 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 8 Mar 2006 12:14:04 +0000 Subject: Add an option to the web interface to batch upload new entries to the inventory_item table. --- FS/FS/inventory_item.pm | 83 +++++++++++++++++++++- httemplate/misc/inventory_item-import.html | 20 ++++++ httemplate/misc/process/inventory_item-import.html | 27 +++++++ httemplate/search/inventory_class.html | 49 ++++++++++--- httemplate/search/inventory_item.html | 25 +++++-- 5 files changed, 188 insertions(+), 16 deletions(-) create mode 100644 httemplate/misc/inventory_item-import.html create mode 100644 httemplate/misc/process/inventory_item-import.html diff --git a/FS/FS/inventory_item.pm b/FS/FS/inventory_item.pm index 5312d95f1..23bacf040 100644 --- a/FS/FS/inventory_item.pm +++ b/FS/FS/inventory_item.pm @@ -2,8 +2,9 @@ package FS::inventory_item; use strict; use vars qw( @ISA ); -use FS::Record qw( qsearch qsearchs ); +use FS::Record qw( dbh qsearch qsearchs ); use FS::inventory_class; +use FS::cust_svc; @ISA = qw(FS::Record); @@ -105,20 +106,96 @@ sub check { $self->ut_numbern('itemnum') || $self->ut_foreign_key('classnum', 'inventory_class', 'classnum' ) || $self->ut_text('item') - || $self->ut_numbern('svcnum') + || $self->ut_foreign_keyn('svcnum', 'cust_svc', 'svcnum' ) ; return $error if $error; $self->SUPER::check; } +=item cust_svc + +Returns the customer service associated with this inventory item, if the +item has been used (see L). + +=cut + +sub cust_svc { + my $self = shift; + return '' unless $self->svcnum; + qsearchs( 'cust_svc', { 'svcnum' => $self->svcnum } ); +} + +=back + +=head1 CLASS METHODS + +=over 4 + +=item batch_import + +=cut + +sub batch_import { + my $param = shift; + + my $fh = $param->{filehandle}; + + my $imported = 0; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $line; + while ( defined($line=<$fh>) ) { + + chomp $line; + + my $inventory_item = new FS::inventory_item { + 'classnum' => $param->{'classnum'}, + 'item' => $line, + }; + + my $error = $inventory_item->insert; + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + + #or just skip? + #next; + } + + $imported++; + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + #might want to disable this if we skip records for any reason... + return "Empty file!" unless $imported; + + ''; + +} + =back =head1 BUGS +maybe batch_import should be a regular method in FS::inventory_class + =head1 SEE ALSO -L, schema.html from the base documentation. +L, L, L, schema.html from the base +documentation. =cut diff --git a/httemplate/misc/inventory_item-import.html b/httemplate/misc/inventory_item-import.html new file mode 100644 index 000000000..a702fbc49 --- /dev/null +++ b/httemplate/misc/inventory_item-import.html @@ -0,0 +1,20 @@ +<% + +my $classnum = $cgi->param('classnum'); +$classnum =~ /^(\d+)$/ or eidiot "illegal classnum $classnum"; +$classnum = $1; +my $inventory_class = qsearchs('inventory_class', { 'classnum' => $classnum } ); + +%><%= include("/elements/header.html", $inventory_class->classname. 's') %> + +
      + +Import a file containing <%= $inventory_class->classname %>s, one per line.

      + +Filename:

      + + +
      + +<%= include('/elements/footer.html') %> + diff --git a/httemplate/misc/process/inventory_item-import.html b/httemplate/misc/process/inventory_item-import.html new file mode 100644 index 000000000..8a58203c2 --- /dev/null +++ b/httemplate/misc/process/inventory_item-import.html @@ -0,0 +1,27 @@ +<% + + my $fh = $cgi->upload('filename'); + + my $error = defined($fh) + ? FS::inventory_item::batch_import( { + 'filehandle' => $fh, + 'classnum' => $cgi->param('classnum'), + } ) + : 'No file'; + + if ( $error ) { + %> + + <% + eidiot($error); +# $cgi->param('error', $error); +# print $cgi->redirect( "${p}cust_main-import.cgi + } else { + %> + + <%= include("/elements/header.html",'Import sucessful') %> + + <%= include("/elements/footer.html",'Import sucessful') %> <% + } +%> + diff --git a/httemplate/search/inventory_class.html b/httemplate/search/inventory_class.html index 1bf1bcbce..37735f3c9 100644 --- a/httemplate/search/inventory_class.html +++ b/httemplate/search/inventory_class.html @@ -5,11 +5,18 @@ tie my %labels, 'Tie::IxHash', 'num_used' => 'In use', #'Used', #'Allocated', 'num_total' => 'Total', ; -my %inv_action_link = ( - 'num_avail' => 'eventually' + +my %link = ( + 'num_avail' => ';avail=1', + 'num_used' => ';avail=1', + 'num_total' => '', ); -my %inv_action_label = ( - 'num_avail' => 'upload_batch' + +my %inv_action_link = ( + 'num_avail' => [ 'upload batch', + $p.'misc/inventory_item-import.html?classnum=', + 'classnum' + ], ); my $link = [ "${p}edit/inventory_class.html?", 'classnum' ]; @@ -29,20 +36,44 @@ my $link = [ "${p}edit/inventory_class.html?", 'classnum' ]; #my $inventory_class = shift; my $i_c = shift; + my $link = + $p. 'search/inventory_item.html?'. + 'classnum='. $i_c->classnum; + + my %actioncol = (); + foreach ( keys %inv_action_link ) { + my($label, $baseurl, $method) = + @{ $inv_action_link{$_} }; + my $url = $baseurl. $i_c->$method(); + $actioncol{$_} = + ''. + '('. + ''. + $label. + ''. + ')'. + ''; + } + + my %num = map { + $_ => $i_c->$_(); + } keys %labels; + [ map { [ { - 'data' => ''. $i_c->$_(). '', + 'data' => ''. $num{$_}. '', 'align' => 'right', }, { 'data' => $labels{$_}, 'align' => 'left', - }, - { 'data' => ( exists($inv_action_link{$_}) - ? '('. $inv_action_label{$_}. ')' - : '' + 'link' => ( $num{$_} + ? $link.$link{$_} + : '' ), + }, + { 'data' => $actioncol{$_}, 'align' => 'left', }, ] diff --git a/httemplate/search/inventory_item.html b/httemplate/search/inventory_item.html index ff7f1fadf..bd74f5619 100644 --- a/httemplate/search/inventory_item.html +++ b/httemplate/search/inventory_item.html @@ -1,15 +1,31 @@ <% my $classnum = $cgi->param('classnum'); -$classnum =~ /^(\d+)$/ or eidiot "illegal agentnum $agentnum"; +$classnum =~ /^(\d+)$/ or eidiot "illegal classnum $classnum"; $classnum = $1; -my $inventory_class = qsearchs('inventory_class', { 'classnum' => $classnum } ); + +my $inventory_class = qsearchs( { + 'table' => 'inventory_class', + 'hashref' => { 'classnum' => $classnum }, +} ); + +my $title = $inventory_class->classname. ' Inventory'; + +#little false laziness with SQL fragments in inventory_class.pm +my $extra_sql = ''; +if ( $cgi->param('avail') ) { + $extra_sql = 'AND ( svcnum IS NULL OR svcnum = 0 )'; + $title .= ' - Available'; +} elsif ( $cgi->param('used') ) { + $extra_sql = 'AND svcnum IS NOT NULL AND svcnum > 0'; + $title .= ' - In use'; +} my $count_query = - "SELECT COUNT(*) FROM inventory_class WHERE classnum = $classnum"; + "SELECT COUNT(*) FROM inventory_item WHERE classnum = $classnum $extra_sql"; %><%= include( 'elements/search.html', - 'title' => $inventory_class->classname. ' Inventory', + 'title' => $title, #less lame to use Lingua:: something to pluralize 'name' => $inventory_class->classname. 's', @@ -17,6 +33,7 @@ my $count_query = 'query' => { 'table' => 'inventory_item', 'hashref' => { 'classnum' => $classnum }, + 'extra_sql' => $extra_sql, }, 'count_query' => $count_query, -- cgit v1.2.1 From b3d608b436cdf673a5552acf9e4b4601f4f79b9d Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 9 Mar 2006 11:48:06 +0000 Subject: don't use a table with WIDTH="100%", it shoves the custnum and "billing information" boxes way out to the right --- httemplate/view/cust_main.cgi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi index 4975c52fd..082a93bb7 100755 --- a/httemplate/view/cust_main.cgi +++ b/httemplate/view/cust_main.cgi @@ -92,12 +92,12 @@ print "This customer's signup URL: ". %> -<%= &itable() %> + - +<% my $conf = new FS::Conf; if ( $conf->exists('voip-cust_cdr_spools') ) { %> + + + + +<% } %>
      <%= include('cust_main/contacts.html', $cust_main ) %> + <%= include('cust_main/misc.html', $cust_main ) %> <% if ( $conf->config('payby-default') ne 'HIDE' ) { %>
      -- cgit v1.2.1 From 9f69daa6b11920b84840c17bf95955dee959367e Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 9 Mar 2006 13:42:40 +0000 Subject: fix that blank-page-instead-of-profiling-redirect-when-called-from-an-include bug triggered by mason 1.32 :) --- htetc/handler.pl | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/htetc/handler.pl b/htetc/handler.pl index 15f9203f8..9b808e68c 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -247,10 +247,8 @@ sub handler sub redirect { my( $location ) = @_; - warn 'redir1 $m='.$m; use vars qw($m); $m->clear_buffer; - warn 'redir3-prof'; #false laziness w/above if ( defined(@DBIx::Profile::ISA) ) { #profiling redirect @@ -267,14 +265,13 @@ sub handler ); dbh->{'private_profile'} = {}; - warn 'redir9-prof'; - my $rv = $m->abort(200); - warn "redir10-prof: $rv"; - $rv; + #whew. removing this is all that's needed to fix the annoying + #blank-page-instead-of-profiling-redirect-when-called-from-an-include + #bug triggered by mason 1.32 + #my $rv = $m->abort(200); } else { #normal redirect - warn 'redir9-redirect'; $m->redirect($location); } -- cgit v1.2.1 From b29780b983dd91fb679e8d01b91902b0ce186e9f Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 10 Mar 2006 22:28:17 +0000 Subject: want to know who *called* this without the required second arg --- FS/FS/UI/Web.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm index 49e3fbf7e..dc45e0188 100644 --- a/FS/FS/UI/Web.pm +++ b/FS/FS/UI/Web.pm @@ -131,6 +131,7 @@ package FS::UI::Web::JSRPC; use strict; use vars qw($DEBUG); +use Carp; use Storable qw(nfreeze); use MIME::Base64; use JSON; @@ -150,7 +151,7 @@ sub new { bless $self, $class; - die "CGI object required as second argument" unless $self->{'cgi'}; + croak "CGI object required as second argument" unless $self->{'cgi'}; return $self; } -- cgit v1.2.1 From 5f155263a2c9837640d2fab0817d1f36b8cb3f8c Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 10 Mar 2006 22:30:48 +0000 Subject: fix to (hopefully) allow multiple progress-init's in a page, also add second $cgi arg to all these progressbar calls... --- httemplate/elements/progress-init.html | 8 ++++---- httemplate/misc/email_invoice_events.cgi | 2 +- httemplate/misc/email_invoices.cgi | 2 +- httemplate/misc/fax_invoice_events.cgi | 2 +- httemplate/misc/fax_invoices.cgi | 2 +- httemplate/misc/print_invoice_events.cgi | 2 +- httemplate/misc/print_invoices.cgi | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html index 7844f5678..ba9f6edd6 100644 --- a/httemplate/elements/progress-init.html +++ b/httemplate/elements/progress-init.html @@ -14,7 +14,7 @@ <%= include('/elements/xmlhttp.html', 'method' => 'POST', 'url' => $action, - 'subs' => [ 'start_job' ], + 'subs' => [ $key.'start_job' ], ) %> @@ -32,7 +32,7 @@ function <%=$key%>process () { document.<%=$formname%>.submit.disabled=true; - overlib( 'Submitting job to server...', WIDTH, 432, HEIGHT, 136, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); + overlib( 'Submitting job to server...', WIDTH, 444, HEIGHT, 168, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); var Hash = new Array(); var x = 0; @@ -68,13 +68,13 @@ function <%=$key%>process () { //alert('start_job( ' + Hash + ', <%=$key%>myCallback )' ); //alert('start_job()' ); - start_job( Hash, <%=$key%>myCallback ); + <%=$key%>start_job( Hash, <%=$key%>myCallback ); } function <%=$key%>myCallback( jobnum ) { - overlib( OLiframeContent('<%=$p%>elements/progress-popup.html?jobnum=' + jobnum + ';<%=$url_or_message_link%>;formname=<%=$formname%>' , 432, 136, 'progress_popup'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); + overlib( OLiframeContent('<%=$p%>elements/progress-popup.html?jobnum=' + jobnum + ';<%=$url_or_message_link%>;formname=<%=$formname%>' , 444, 168, 'progress_popup'), CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); } diff --git a/httemplate/misc/email_invoice_events.cgi b/httemplate/misc/email_invoice_events.cgi index 12d58d608..b5e66d5ac 100644 --- a/httemplate/misc/email_invoice_events.cgi +++ b/httemplate/misc/email_invoice_events.cgi @@ -1,6 +1,6 @@ <% -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reemail'; +my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reemail', $cgi; $server->process; %> diff --git a/httemplate/misc/email_invoices.cgi b/httemplate/misc/email_invoices.cgi index 0a3978395..bd231261c 100644 --- a/httemplate/misc/email_invoices.cgi +++ b/httemplate/misc/email_invoices.cgi @@ -1,6 +1,6 @@ <% -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reemail'; +my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reemail', $cgi; $server->process; %> diff --git a/httemplate/misc/fax_invoice_events.cgi b/httemplate/misc/fax_invoice_events.cgi index a8ded0550..9a5e8aaea 100644 --- a/httemplate/misc/fax_invoice_events.cgi +++ b/httemplate/misc/fax_invoice_events.cgi @@ -1,6 +1,6 @@ <% -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_refax'; +my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_refax', $cgi; $server->process; %> diff --git a/httemplate/misc/fax_invoices.cgi b/httemplate/misc/fax_invoices.cgi index f16ba8b5e..24bee7da9 100644 --- a/httemplate/misc/fax_invoices.cgi +++ b/httemplate/misc/fax_invoices.cgi @@ -1,6 +1,6 @@ <% -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_refax'; +my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_refax', $cgi; $server->process; %> diff --git a/httemplate/misc/print_invoice_events.cgi b/httemplate/misc/print_invoice_events.cgi index c6a7885a4..3cf4cf7c8 100644 --- a/httemplate/misc/print_invoice_events.cgi +++ b/httemplate/misc/print_invoice_events.cgi @@ -1,6 +1,6 @@ <% -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reprint'; +my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reprint', $cgi; $server->process; %> diff --git a/httemplate/misc/print_invoices.cgi b/httemplate/misc/print_invoices.cgi index d7b271c37..6d32eaaac 100644 --- a/httemplate/misc/print_invoices.cgi +++ b/httemplate/misc/print_invoices.cgi @@ -1,6 +1,6 @@ <% -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reprint'; +my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reprint', $cgi; $server->process; %> -- cgit v1.2.1 From 36d9b47e5c20ae3bc71c0bd0eaf289b566d0cf7c Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 11 Mar 2006 05:21:20 +0000 Subject: fix the progressbar bug with multiple progressbar forms on a page --- httemplate/elements/progress-init.html | 3 ++- httemplate/elements/xmlhttp.html | 5 +++-- httemplate/misc/email_invoice_events.cgi | 5 +---- httemplate/misc/email_invoices.cgi | 5 +---- httemplate/misc/fax_invoice_events.cgi | 7 ++----- httemplate/misc/fax_invoices.cgi | 7 ++----- httemplate/misc/print_invoice_events.cgi | 7 ++----- httemplate/misc/print_invoices.cgi | 7 ++----- 8 files changed, 15 insertions(+), 31 deletions(-) diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html index ba9f6edd6..efebe48e9 100644 --- a/httemplate/elements/progress-init.html +++ b/httemplate/elements/progress-init.html @@ -14,7 +14,8 @@ <%= include('/elements/xmlhttp.html', 'method' => 'POST', 'url' => $action, - 'subs' => [ $key.'start_job' ], + 'subs' => [ 'start_job' ], + 'key' => $key, ) %> diff --git a/httemplate/elements/xmlhttp.html b/httemplate/elements/xmlhttp.html index 28130e501..e03438822 100644 --- a/httemplate/elements/xmlhttp.html +++ b/httemplate/elements/xmlhttp.html @@ -4,6 +4,7 @@ my $url = $opt{'url'}; my $method = exists($opt{'method'}) ? $opt{'method'} : 'GET'; #my @subs = @{ $opt{'subs'}; + my $key = exists($opt{'key'}) ? $opt{'key'} : ''; $url .= ( ($url =~ /\?/) ? '&' : '?' ) if $method eq 'GET'; @@ -38,10 +39,10 @@ %> - function <%=$func%>() { + function <%=$key%><%=$func%>() { // count args; build URL var url = "<%=$furl%>"; - var a = <%=$func%>.arguments; + var a = <%=$key%><%=$func%>.arguments; var args; var len; diff --git a/httemplate/misc/email_invoice_events.cgi b/httemplate/misc/email_invoice_events.cgi index b5e66d5ac..3a39bcd19 100644 --- a/httemplate/misc/email_invoice_events.cgi +++ b/httemplate/misc/email_invoice_events.cgi @@ -1,6 +1,3 @@ <% - my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reemail', $cgi; -$server->process; - -%> +%><%= $server->process %> diff --git a/httemplate/misc/email_invoices.cgi b/httemplate/misc/email_invoices.cgi index bd231261c..490c42f52 100644 --- a/httemplate/misc/email_invoices.cgi +++ b/httemplate/misc/email_invoices.cgi @@ -1,6 +1,3 @@ <% - my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reemail', $cgi; -$server->process; - -%> +%><%= $server->process %> diff --git a/httemplate/misc/fax_invoice_events.cgi b/httemplate/misc/fax_invoice_events.cgi index 9a5e8aaea..778c4291e 100644 --- a/httemplate/misc/fax_invoice_events.cgi +++ b/httemplate/misc/fax_invoice_events.cgi @@ -1,6 +1,3 @@ -<% - +<% my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_refax', $cgi; -$server->process; - -%> +%><%= $server->process %> diff --git a/httemplate/misc/fax_invoices.cgi b/httemplate/misc/fax_invoices.cgi index 24bee7da9..b3238885f 100644 --- a/httemplate/misc/fax_invoices.cgi +++ b/httemplate/misc/fax_invoices.cgi @@ -1,6 +1,3 @@ -<% - +<% my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_refax', $cgi; -$server->process; - -%> +%><%= $server->process %> diff --git a/httemplate/misc/print_invoice_events.cgi b/httemplate/misc/print_invoice_events.cgi index 3cf4cf7c8..897c39ede 100644 --- a/httemplate/misc/print_invoice_events.cgi +++ b/httemplate/misc/print_invoice_events.cgi @@ -1,6 +1,3 @@ <% - -my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reprint', $cgi; -$server->process; - -%> +my $server = new FS::UI::Web::JSRPC 'FS::cust_bill_event::process_reprint', $cgi; %> +<%= $server->process %> diff --git a/httemplate/misc/print_invoices.cgi b/httemplate/misc/print_invoices.cgi index 6d32eaaac..218262077 100644 --- a/httemplate/misc/print_invoices.cgi +++ b/httemplate/misc/print_invoices.cgi @@ -1,6 +1,3 @@ -<% - +<% my $server = new FS::UI::Web::JSRPC 'FS::cust_bill::process_reprint', $cgi; -$server->process; - -%> +%><%= $server->process %> -- cgit v1.2.1 From d6a8b9a62765dbfe757e97c35a1c3ba2e0a32b42 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 11 Mar 2006 07:27:40 +0000 Subject: some pages from ui hoohaw have leaked footer include, need something here for now --- httemplate/elements/footer.html | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 httemplate/elements/footer.html diff --git a/httemplate/elements/footer.html b/httemplate/elements/footer.html new file mode 100644 index 000000000..6029d7637 --- /dev/null +++ b/httemplate/elements/footer.html @@ -0,0 +1,2 @@ + + -- cgit v1.2.1 From c1bc54f7523d2af6114d7445bbbe5618f1429794 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 13 Mar 2006 22:32:51 +0000 Subject: fix progress hoohaw for internet exploder again, whew. also make sure error/finish messages are centered, looks better --- httemplate/elements/progress-init.html | 4 +++- httemplate/elements/progress-popup.html | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/httemplate/elements/progress-init.html b/httemplate/elements/progress-init.html index efebe48e9..ec485f438 100644 --- a/httemplate/elements/progress-init.html +++ b/httemplate/elements/progress-init.html @@ -31,7 +31,9 @@ function <%=$key%>process () { //alert('<%=$key%>process for form <%=$formname%>'); - document.<%=$formname%>.submit.disabled=true; + if ( document.<%=$formname%>.submit.disabled == false ) { + document.<%=$formname%>.submit.disabled=true; + } overlib( 'Submitting job to server...', WIDTH, 444, HEIGHT, 168, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', CLOSECLICK, MIDX, 0, MIDY, 0 ); diff --git a/httemplate/elements/progress-popup.html b/httemplate/elements/progress-popup.html index 200f97d9b..544440138 100644 --- a/httemplate/elements/progress-popup.html +++ b/httemplate/elements/progress-popup.html @@ -45,7 +45,9 @@ function updateStatus( status_statustext ) { document.getElementById("progress_bar").innerHTML = ''; document.getElementById("progress_percent").innerHTML = ''; document.getElementById("progress_jobnum").innerHTML = ''; - parent.document.<%=$formname%>.submit.disabled=false; + if ( parent.document.<%=$formname%>.submit.disabled == true ) { + parent.document.<%=$formname%>.submit.disabled=false; + } <% } elsif ( $url ) { %> window.top.location.href = '<%= $url %>'; <% } else { %> @@ -56,7 +58,9 @@ function updateStatus( status_statustext ) { document.getElementById("progress_bar").innerHTML = ''; document.getElementById("progress_percent").innerHTML = ''; document.getElementById("progress_jobnum").innerHTML = ''; - parent.document.<%=$formname%>.submit.disabled=false; + if ( parent.document.<%=$formname%>.submit.disabled == true ) { + parent.document.<%=$formname%>.submit.disabled=false; + } } else { alert('XXX unknown status returned from server: ' + status); } @@ -64,7 +68,7 @@ function updateStatus( status_statustext ) { } - +
      + <% if ( $conf->exists('voip-cust_cdr_spools') ) { %> + + + + <% } else { %> + + <% } %> +
      Server processing job... -- cgit v1.2.1 From a1f6d8a9fde8eb15e0c0e3161f4e2e740d45d47b Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 15 Mar 2006 04:17:08 +0000 Subject: handle BIGSERIAL like SERIAL for the cdr table, and normalize canadian zip codes as well as us ones --- FS/FS/Record.pm | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 4a0fe3fd2..a7af708a3 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -285,7 +285,7 @@ sub qsearch { if ( $op eq '=' ) { if ( driver_name eq 'Pg' ) { my $type = dbdef->table($table)->column($column)->type; - if ( $type =~ /(int|serial)/i ) { + if ( $type =~ /(int|(big)?serial)/i ) { qq-( $column IS NULL )-; } else { qq-( $column IS NULL OR $column = '' )-; @@ -296,7 +296,7 @@ sub qsearch { } elsif ( $op eq '!=' ) { if ( driver_name eq 'Pg' ) { my $type = dbdef->table($table)->column($column)->type; - if ( $type =~ /(int|serial)/i ) { + if ( $type =~ /(int|(big)?serial)/i ) { qq-( $column IS NOT NULL )-; } else { qq-( $column IS NOT NULL AND $column != '' )-; @@ -365,7 +365,7 @@ sub qsearch { grep defined( $record->{$_} ) && $record->{$_} ne '', @real_fields ) { if ( $record->{$field} =~ /^\d+(\.\d+)?$/ - && dbdef->table($table)->column($field)->type =~ /(int|serial)/i + && dbdef->table($table)->column($field)->type =~ /(int|(big)?serial)/i ) { $sth->bind_param($bind++, $record->{$field}, { TYPE => SQL_INTEGER } ); } else { @@ -695,7 +695,7 @@ sub insert { my $col = $self->dbdef_table->column($primary_key); $db_seq = - uc($col->type) eq 'SERIAL' + uc($col->type) =~ /^(BIG)?SERIAL\d?/ || ( driver_name eq 'Pg' && defined($col->default) && $col->default =~ /^nextval\(/i @@ -1026,7 +1026,7 @@ sub replace { #false laziness w/qsearch if ( driver_name eq 'Pg' ) { my $type = $old->dbdef_table->column($_)->type; - if ( $type =~ /(int|serial)/i ) { + if ( $type =~ /(int|(big)?serial)/i ) { qq-( $_ IS NULL )-; } else { qq-( $_ IS NULL OR $_ = '' )-; @@ -1498,12 +1498,23 @@ my @zip_reqd_countries = qw( CA ); #US implicit... sub ut_zip { my( $self, $field, $country ) = @_; + if ( $country eq 'US' ) { - $self->getfield($field) =~ /\s*(\d{5}(\-\d{4})?)\s*$/ + + $self->getfield($field) =~ /^\s*(\d{5}(\-\d{4})?)\s*$/ + or return gettext('illegal_zip'). " $field for country $country: ". + $self->getfield($field); + $self->setfield($field, $1); + + } elsif ( $country eq 'CA' ) { + + $self->getfield($field) =~ /^\s*([A-Z]\d[A-Z])\s*(\d[A-Z]\d)\s*$/i or return gettext('illegal_zip'). " $field for country $country: ". $self->getfield($field); - $self->setfield($field,$1); + $self->setfield($field, "$1 $2"); + } else { + if ( $self->getfield($field) =~ /^\s*$/ && ( !$country || ! grep { $_ eq $country } @zip_reqd_countries ) ) @@ -1514,7 +1525,9 @@ sub ut_zip { or return gettext('illegal_zip'). " $field: ". $self->getfield($field); $self->setfield($field,$1); } + } + ''; } -- cgit v1.2.1 From 28dce0fc53d43134dcfcbfab1d8805094ae89789 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 15 Mar 2006 07:34:58 +0000 Subject: initial commit of this just cause i want a revision history --- FS/FS/part_pkg/voip_cdr.pm | 258 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 FS/FS/part_pkg/voip_cdr.pm diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm new file mode 100644 index 000000000..385fcb2e5 --- /dev/null +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -0,0 +1,258 @@ +package FS::part_pkg::voip_cdr; + +use strict; +use vars qw(@ISA $DEBUG %info); +use Date::Format; +use Tie::IxHash; +use FS::Record qw(qsearchs qsearch); +use FS::part_pkg::flat; +#use FS::rate; +use FS::rate_prefix; + +@ISA = qw(FS::part_pkg::flat); + +$DEBUG = 1; + +tie my %region_method, 'Tie::IxHash', + 'prefix' => 'Rate calls by using destination prefix to look up a region and rate according to the internal prefix and rate tables', + 'upstream_rateid' => 'Rate calls by mapping the upstream rate ID (# rate plan ID?) directly to an internal rate (rate_detail)', #upstream_rateplanid +; + +#tie my %cdr_location, 'Tie::IxHash', +# 'internal' => 'Internal: CDR records imported into the internal CDR table', +# 'external' => 'External: CDR records queried directly from an external '. +# 'Asterisk (or other?) CDR table', +#; + +%info = ( + 'name' => 'VoIP rating by plan of CDR records in an internal (or external?) SQL table', + 'fields' => { + 'setup_fee' => { 'name' => 'Setup fee for this package', + 'default' => 0, + }, + 'recur_flat' => { 'name' => 'Base recurring fee for this package', + 'default' => 0, + }, + 'unused_credit' => { 'name' => 'Credit the customer for the unused portion'. + ' of service at cancellation', + 'type' => 'checkbox', + }, + 'ratenum' => { 'name' => 'Rate plan', + 'type' => 'select', + 'select_table' => 'rate', + 'select_key' => 'ratenum', + 'select_label' => 'ratename', + }, + 'region_method' => { 'name' => 'Region rating method', + 'type' => 'select', + 'select_options' => \%region_method, + }, + + #XXX also have option for an external db?? +# 'cdr_location' => { 'name' => 'CDR database location' +# 'type' => 'select', +# 'select_options' => \%cdr_location, +# 'select_callback' => { +# 'external' => { +# 'enable' => [ 'datasrc', 'username', 'password' ], +# }, +# 'internal' => { +# 'disable' => [ 'datasrc', 'username', 'password' ], +# } +# }, +# }, +# 'datasrc' => { 'name' => 'DBI data source for external CDR table', +# 'disabled' => 'Y', +# }, +# 'username' => { 'name' => 'External database username', +# 'disabled' => 'Y', +# }, +# 'password' => { 'name' => 'External database password', +# 'disabled' => 'Y', +# }, + + }, + 'fieldorder' => [qw( setup_fee recur_flat unused_credit ratenum ignore_unrateable )], + 'weight' => 40, +); + +sub calc_setup { + my($self, $cust_pkg ) = @_; + $self->option('setup_fee'); +} + +#false laziness w/voip_sqlradacct... resolve it if that one ever gets used again +sub calc_recur { + my($self, $cust_pkg, $sdate, $details ) = @_; + + my $last_bill = $cust_pkg->last_bill; + + my $ratenum = $cust_pkg->part_pkg->option('ratenum'); + + my %included_min = (); + + my $charges = 0; + + # also look for a specific domain??? (username@telephonedomain) + foreach my $cust_svc ( + grep { $_->part_svc->svcdb eq 'svc_acct' } $cust_pkg->cust_svc + ) { + + foreach my $cdr ( + $cust_svc->get_cdrs( $last_bill, $$sdate ) + ) { + if ( $DEBUG > 1 ) { + warn "rating CDR $cdr\n". + join('', map { " $_ => ". $cdr->{$_}. "\n" } keys %$cdr ); + } + + my( $regionnum, $rate_detail ); + if ( $self->option('region_method') eq 'prefix' + || ! $self->option('region_method') + ) + { + + ### + # look up rate details based on called station id + ### + + my $dest = $cdr->{'calledstationid'}; # XXX + + #remove non-phone# stuff and whitespace + $dest =~ s/\s//g; + my $proto = ''; + $dest =~ s/^(\w+):// and $proto = $1; #sip: + my $siphost = ''; + $dest =~ s/\@(.*)$// and $siphost = $1; # @10.54.32.1, @sip.example.com + + #determine the country code + my $countrycode; + if ( $dest =~ /^011(((\d)(\d))(\d))(\d+)$/ + || $dest =~ /^\+(((\d)(\d))(\d))(\d+)$/ + ) + { + + my( $three, $two, $one, $u1, $u2, $rest ) = ( $1,$2,$3,$4,$5,$6 ); + #first look for 1 digit country code + if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) { + $countrycode = $one; + $dest = $u1.$u2.$rest; + } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2 + $countrycode = $two; + $dest = $u2.$rest; + } else { #3 digit country code + $countrycode = $three; + $dest = $rest; + } + + } else { + $countrycode = '1'; + $dest =~ s/^1//;# if length($dest) > 10; + } + + warn "rating call to +$countrycode $dest\n" if $DEBUG; + + #find a rate prefix, first look at most specific (4 digits) then 3, etc., + # finally trying the country code only + my $rate_prefix = ''; + for my $len ( reverse(1..6) ) { + $rate_prefix = qsearchs('rate_prefix', { + 'countrycode' => $countrycode, + #'npa' => { op=> 'LIKE', value=> substr($dest, 0, $len) } + 'npa' => substr($dest, 0, $len), + } ) and last; + } + $rate_prefix ||= qsearchs('rate_prefix', { + 'countrycode' => $countrycode, + 'npa' => '', + }); + + die "Can't find rate for call to +$countrycode $dest\n" + unless $rate_prefix; + + $regionnum = $rate_prefix->regionnum; + $rate_detail = qsearchs('rate_detail', { + 'ratenum' => $ratenum, + 'dest_regionnum' => $regionnum, + } ); + + warn " found rate for regionnum $regionnum ". + "and rate detail $rate_detail\n" + if $DEBUG; + + } elsif ( $self->option('region_method') eq 'upstream_rateid' ) { #upstream_rateplanid + + $regionnum = ''; #XXXXX regionnum should be something + + $rate_detail = $cdr->cdr_upstream_rate->rate_detail; + + warn " found rate for ". #regionnum $regionnum and ". + "rate detail $rate_detail\n" + if $DEBUG; + + } else { + die "don't know how to rate CDRs using method: ". + $self->option('region_method'). "\n"; + } + + ### + # find the price and add detail to the invoice + ### + + $included_min{$regionnum} = $rate_detail->min_included + unless exists $included_min{$regionnum}; + + my $granularity = $rate_detail->sec_granularity; + my $seconds = $cdr->{'acctsessiontime'}; # XXX + $seconds += $granularity - ( $seconds % $granularity ); + my $minutes = sprintf("%.1f", $seconds / 60); + $minutes =~ s/\.0$// if $granularity == 60; + + $included_min{$regionnum} -= $minutes; + + my $charge = 0; + if ( $included_min{$regionnum} < 0 ) { + my $charge_min = 0 - $included_min{$regionnum}; + $included_min{$regionnum} = 0; + $charge = sprintf('%.2f', $rate_detail->min_charge * $charge_min ); + $charges += $charge; + } + + # XXXXXXX +# my $rate_region = $rate_prefix->rate_region; +# warn " (rate region $rate_region)\n" if $DEBUG; +# +# my @call_details = ( +# #time2str("%Y %b %d - %r", $session->{'acctstarttime'}), +# time2str("%c", $cdr->{'acctstarttime'}), #XXX +# $minutes.'m', +# '$'.$charge, +# "+$countrycode $dest", +# $rate_region->regionname, +# ); +# +# warn " adding details on charge to invoice: ". +# join(' - ', @call_details ) +# if $DEBUG; +# +# push @$details, join(' - ', @call_details); #\@call_details, + + } # $cdr + + } # $cust_svc + + $self->option('recur_flat') + $charges; + +} + +sub is_free { + 0; +} + +sub base_recur { + my($self, $cust_pkg) = @_; + $self->option('recur_flat'); +} + +1; + -- cgit v1.2.1 From 96fde39e747fdc5c618b5a92ec354384931cf317 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 17 Mar 2006 14:56:27 +0000 Subject: use IO::File, lucky this never threw an error... --- FS/FS/Daemon.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/FS/FS/Daemon.pm b/FS/FS/Daemon.pm index 3e64f79e9..7e0d45c20 100644 --- a/FS/FS/Daemon.pm +++ b/FS/FS/Daemon.pm @@ -5,6 +5,7 @@ use vars qw( $pid_dir $me $pid_file $sigint $sigterm $logfile ); use Exporter; use Fcntl qw(:flock); use POSIX qw(setsid); +use IO::File; use Date::Format; #this is a simple refactoring of the stuff from freeside-queued, just to -- cgit v1.2.1 From ff24bc786a5fd479f2252260e0da580a736f97be Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 20 Mar 2006 19:13:27 +0000 Subject: add price plan to bill on internal or external CDRs directly, add option to export CDRs to a per-customer downstream file --- FS/FS/Conf.pm | 14 ++ FS/FS/Schema.pm | 14 +- FS/FS/cdr.pm | 208 +++++++++++++++++++-- FS/FS/cdr_upstream_rate.pm | 138 ++++++++++++++ FS/FS/cust_main.pm | 34 +++- FS/FS/cust_svc.pm | 45 +++++ FS/FS/part_pkg/voip_cdr.pm | 331 +++++++++++++++++++++------------ FS/FS/rate_detail.pm | 30 ++- FS/FS/svc_acct.pm | 64 ++++++- FS/MANIFEST | 2 + FS/t/cdr_upstream_rate.t | 5 + bin/cdr_upstream_rate.import | 142 ++++++++++++++ httemplate/edit/cust_main/billing.html | 10 +- httemplate/edit/part_pkg.cgi | 39 +++- httemplate/edit/rate.cgi | 7 +- httemplate/search/cdr.html | 21 ++- httemplate/search/report_cdr.html | 5 + httemplate/view/cust_main/billing.html | 6 + 18 files changed, 951 insertions(+), 164 deletions(-) create mode 100644 FS/FS/cdr_upstream_rate.pm create mode 100644 FS/t/cdr_upstream_rate.t create mode 100755 bin/cdr_upstream_rate.import diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index a5add28d8..6be6db5c5 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1681,6 +1681,20 @@ httemplate/docs/config.html 'type' => 'text', }, + { + 'key' => 'echeck-nonus', + 'section' => 'billing', + 'description' => 'Disable ABA-format account checking for Electronic Check payment info', + 'type' => 'checkbox', + }, + + { + 'key' => 'voip-cust_cdr_spools', + 'section' => '', + 'description' => 'Enable the per-customer option for individual CDR spools.', + 'type' => 'checkbox', + }, + ); 1; diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index a049b8bad..9125758d0 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -437,6 +437,7 @@ sub tables_hashref { 'refnum', 'int', '', '', '', '', 'referral_custnum', 'int', 'NULL', '', '', '', 'comments', 'text', 'NULL', '', '', '', + 'spool_cdr','char', 'NULL', 1, '', '', ], 'primary_key' => 'custnum', 'unique' => [], @@ -1146,7 +1147,8 @@ sub tables_hashref { 'orig_regionnum', 'int', 'NULL', '', '', '', 'dest_regionnum', 'int', '', '', '', '', 'min_included', 'int', '', '', '', '', - 'min_charge', @money_type, '', '', + #'min_charge', @money_type, '', '', + 'min_charge', 'decimal', '', '10,5', '', '', 'sec_granularity', 'int', '', '', '', '', #time period (link to table of periods)? ], @@ -1416,14 +1418,14 @@ sub tables_hashref { 'index' => [], }, - #map upstream rateid (XXX or rateplanid?) to ours... - 'cdr_upstream_rate' => { # XXX or 'cdr_upstream_rateplan' ?? + #map upstream rateid to ours... + 'cdr_upstream_rate' => { 'columns' => [ - # XXX or 'upstream_rateplanid' ?? - 'upstream_rateid', 'int', 'NULL', '', '', '', + 'upstreamratenum', 'serial', '', '', '', '', + 'upstream_rateid', 'varchar', '', $char_d, '', '', 'ratedetailnum', 'int', 'NULL', '', '', '', ], - 'primary_key' => '', #XXX need a primary key + 'primary_key' => 'upstreamratenum', #XXX need a primary key 'unique' => [ [ 'upstream_rateid' ] ], #unless we add another field, yeah 'index' => [], }, diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm index 2d40177f5..70c8a0f09 100644 --- a/FS/FS/cdr.pm +++ b/FS/FS/cdr.pm @@ -3,11 +3,13 @@ package FS::cdr; use strict; use vars qw( @ISA ); use Date::Parse; +use Date::Format; use FS::UID qw( dbh ); use FS::Record qw( qsearch qsearchs ); use FS::cdr_type; use FS::cdr_calltype; use FS::cdr_carrier; +use FS::cdr_upstream_rate; @ISA = qw(FS::Record); @@ -99,6 +101,8 @@ following fields are currently supported: =item upstream_rateplanid - Upstream rate plan ID +=item rated_price - Rated (or re-rated) price + =item distance - km (need units field?) =item islocal - Local - 1, Non Local = 0 @@ -121,7 +125,7 @@ following fields are currently supported: =item svcnum - Link to customer service (see L) -=item freesidestatus - NULL, done, skipped, pushed_downstream (or something) +=item freesidestatus - NULL, done (or something) =back @@ -241,7 +245,184 @@ sub check { $self->SUPER::check; } -my %formats = ( +=item set_status_and_rated_price STATUS [ RATED_PRICE ] + +Sets the status to the provided string. If there is an error, returns the +error, otherwise returns false. + +=cut + +sub set_status_and_rated_price { + my($self, $status, $rated_price) = @_; + $self->status($status); + $self->rated_price($rated_price); + $self->replace(); +} + +=item calldate_unix + +Parses the calldate in SQL string format and returns a UNIX timestamp. + +=cut + +sub calldate_unix { + str2time(shift->calldate); +} + +=item cdr_carrier + +Returns the FS::cdr_carrier object associated with this CDR, or false if no +carrierid is defined. + +=cut + +my %carrier_cache = (); + +sub cdr_carrier { + my $self = shift; + return '' unless $self->carrierid; + $carrier_cache{$self->carrierid} ||= + qsearchs('cdr_carrier', { 'carrierid' => $self->carrierid } ); +} + +=item carriername + +Returns the carrier name (see L), or the empty string if +no FS::cdr_carrier object is assocated with this CDR. + +=cut + +sub carriername { + my $self = shift; + my $cdr_carrier = $self->cdr_carrier; + $cdr_carrier ? $cdr_carrier->carriername : ''; +} + +=item cdr_calltype + +Returns the FS::cdr_calltype object associated with this CDR, or false if no +calltypenum is defined. + +=cut + +my %calltype_cache = (); + +sub cdr_calltype { + my $self = shift; + return '' unless $self->calltypenum; + $calltype_cache{$self->calltypenum} ||= + qsearchs('cdr_calltype', { 'calltypenum' => $self->calltypenum } ); +} + +=item calltypename + +Returns the call type name (see L), or the empty string if +no FS::cdr_calltype object is assocated with this CDR. + +=cut + +sub calltypename { + my $self = shift; + my $cdr_calltype = $self->cdr_calltype; + $cdr_calltype ? $cdr_calltype->calltypename : ''; +} + +=item cdr_upstream_rate + +Returns the upstream rate mapping (see L), or the empty +string if no FS::cdr_upstream_rate object is associated with this CDR. + +=cut + +sub cdr_upstream_rate { + my $self = shift; + return '' unless $self->upstream_rateid; + qsearchs('cdr_upstream_rate', { 'upstream_rateid' => $self->upstream_rateid }) + or ''; +} + +=item _convergent_format COLUMN [ COUNTRYCODE ] + +Returns the number in COLUMN formatted as follows: + +If the country code does not match COUNTRYCODE (default "61"), it is returned +unchanged. + +If the country code does match COUNTRYCODE (default "61"), it is removed. In +addiiton, "0" is prepended unless the number starts with 13, 18 or 19. (???) + +=cut + +sub _convergent_format { + my( $self, $field ) = ( shift, shift ); + my $countrycode = scalar(@_) ? shift : '61'; #+61 = australia + #my $number = $self->$field(); + my $number = $self->get($field); + #if ( $number =~ s/^(\+|011)$countrycode// ) { + if ( $number =~ s/^\+$countrycode// ) { + $number = "0$number" + unless $number =~ /^1[389]/; #??? + } + $number; +} + +=item downstream_csv [ OPTION => VALUE, ... ] + +=cut + +my %export_formats = ( + 'convergent' => [ + 'carriername', #CARRIER + sub { shift->_convergent_format('src') }, #SERVICE_NUMBER + sub { shift->_convergent_format('charged_party') }, #CHARGED_NUMBER + sub { time2str('%Y-%m-%d', shift->calldate_unix ) }, #DATE + sub { time2str('%T', shift->calldate_unix ) }, #TIME + 'billsec', #'duration', #DURATION + sub { shift->_convergent_format('dst') }, #NUMBER_DIALED + '', #XXX add (from prefixes in most recent email) #FROM_DESC + '', #XXX add (from prefixes in most recent email) #TO_DESC + 'calltypename', #CLASS_CODE + 'rated_price', #PRICE + sub { shift->rated_price ? 'Y' : 'N' }, #RATED + '', #OTHER_INFO + ], +); + +sub downstream_csv { + my( $self, %opt ) = @_; + + my $format = $opt{'format'}; # 'convergent'; + return "Unknown format $format" unless exists $export_formats{$format}; + + eval "use Text::CSV_XS;"; + die $@ if $@; + my $csv = new Text::CSV_XS; + + my @columns = + map { + ref($_) ? &{$_}($self) : $self->$_(); + } + @{ $export_formats{$format} }; + + my $status = $csv->combine(@columns); + die "FS::CDR: error combining ". $csv->error_input(). "into downstream CSV" + unless $status; + + $csv->string; + +} + +=back + +=head1 CLASS METHODS + +=over 4 + +=item batch_import + +=cut + +my %import_formats = ( 'asterisk' => [ 'accountcode', 'src', @@ -264,14 +445,15 @@ my %formats = ( ], 'unitel' => [ 'uniqueid', - 'cdr_type', - 'calldate', # XXX may need massaging - 'billsec', #XXX duration and billsec? - # sub { $_[0]->billsec( $_[1] ); - # $_[0]->duration( $_[1] ); - # }, + #'cdr_type', + 'cdrtypenum', + 'calldate', # may need massaging? huh maybe not... + #'billsec', #XXX duration and billsec? + sub { $_[0]->billsec( $_[1] ); + $_[0]->duration( $_[1] ); + }, 'src', - 'dst', + 'dst', # XXX needs to have "+61" prepended unless /^\+/ ??? 'charged_party', 'upstream_currency', 'upstream_price', @@ -279,8 +461,8 @@ my %formats = ( 'distance', 'islocal', 'calltypenum', - 'startdate', # XXX will definitely need massaging - 'enddate', # XXX same + 'startdate', #XXX needs massaging + 'enddate', #XXX same 'description', 'quantity', 'carrierid', @@ -294,7 +476,7 @@ sub batch_import { my $fh = $param->{filehandle}; my $format = $param->{format}; - return "Unknown format $format" unless exists $formats{$format}; + return "Unknown format $format" unless exists $import_formats{$format}; eval "use Text::CSV_XS;"; die $@ if $@; @@ -339,7 +521,7 @@ sub batch_import { } } - @{ $formats{$format} } + @{ $import_formats{$format} } ; my $cdr = new FS::cdr ( \%cdr ); diff --git a/FS/FS/cdr_upstream_rate.pm b/FS/FS/cdr_upstream_rate.pm new file mode 100644 index 000000000..2fd978203 --- /dev/null +++ b/FS/FS/cdr_upstream_rate.pm @@ -0,0 +1,138 @@ +package FS::cdr_upstream_rate; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); +use FS::rate_detail; + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::cdr_upstream_rate - Object methods for cdr_upstream_rate records + +=head1 SYNOPSIS + + use FS::cdr_upstream_rate; + + $record = new FS::cdr_upstream_rate \%hash; + $record = new FS::cdr_upstream_rate { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::cdr_upstream_rate object represents an upstream rate mapping to +internal rate detail (see L). FS::cdr_upstream_rate inherits +from FS::Record. The following fields are currently supported: + +=over 4 + +=item upstreamratenum - primary key + +=item upstream_rateid - CDR upstream Rate ID (cdr.upstream_rateid - see L) + +=item ratedetailnum - Rate detail - see L + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new upstream rate mapping. To add the upstream rate to the database, +see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'cdr_upstream_rate'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=item check + +Checks all fields to make sure this is a valid upstream rate. If there is +an error, returns the error, otherwise returns false. Called by the insert +and replace methods. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('upstreamratenum') + #|| $self->ut_number('upstream_rateid') + || $self->ut_alpha('upstream_rateid') + #|| $self->ut_text('upstream_rateid') + || $self->ut_foreign_key('ratedetailnum', 'rate_detail', 'ratedetailnum' ) + ; + return $error if $error; + + $self->SUPER::check; +} + +=item rate_detail + +Returns the internal rate detail object for this upstream rate (see +L). + +=cut + +sub rate_detail { + my $self = shift; + qsearchs('rate_detail', { 'ratedetailnum' => $self->ratedetailnum } ); +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 50faeb422..99d27dd5e 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -284,6 +284,8 @@ sub paymask { =item referral_custnum - referring customer number +=item spool_cdr - Enable individual CDR spooling, empty or `Y' + =back =head1 METHODS @@ -1257,7 +1259,11 @@ sub check { my $payinfo = $self->payinfo; $payinfo =~ s/[^\d\@]//g; - $payinfo =~ /^(\d+)\@(\d{9})$/ or return 'invalid echeck account@aba'; + if ( $conf->exists('echeck-nonus') ) { + $payinfo =~ /^(\d+)\@(\d+)$/ or return 'invalid echeck account@aba'; + } else { + $payinfo =~ /^(\d+)\@(\d{9})$/ or return 'invalid echeck account@aba'; + } $payinfo = "$1\@$2"; $self->payinfo($payinfo); $self->paycvv('') if $self->dbdef_table->column('paycvv'); @@ -1336,8 +1342,10 @@ sub check { $self->payname($1); } - $self->tax =~ /^(Y?)$/ or return "Illegal tax: ". $self->tax; - $self->tax($1); + foreach my $flag (qw( tax spool_cdr )) { + $self->$flag() =~ /^(Y?)$/ or return "Illegal $flag: ". $self->$flag(); + $self->$flag($1); + } $self->otaker(getotaker) unless $self->otaker; @@ -1640,6 +1648,7 @@ sub bill { my( $total_setup, $total_recur ) = ( 0, 0 ); my %tax; + my @precommit_hooks = (); foreach my $cust_pkg ( qsearch('cust_pkg', { 'custnum' => $self->custnum } ) @@ -1673,7 +1682,7 @@ sub bill { $setup = eval { $cust_pkg->calc_setup( $time ) }; if ( $@ ) { $dbh->rollback if $oldAutoCommit; - return $@; + return "$@ running calc_setup for $cust_pkg\n"; } $cust_pkg->setfield('setup', $time) unless $cust_pkg->setup; @@ -1695,10 +1704,13 @@ sub bill { # XXX shared with $recur_prog $sdate = $cust_pkg->bill || $cust_pkg->setup || $time; - $recur = eval { $cust_pkg->calc_recur( \$sdate, \@details ) }; + #over two params! lets at least switch to a hashref for the rest... + my %param = ( 'precommit_hooks' => \@precommit_hooks, ); + + $recur = eval { $cust_pkg->calc_recur( \$sdate, \@details, \%param ) }; if ( $@ ) { $dbh->rollback if $oldAutoCommit; - return $@; + return "$@ running calc_recur for $cust_pkg\n"; } #change this bit to use Date::Manip? CAREFUL with timezones (see @@ -1970,6 +1982,16 @@ sub bill { $dbh->rollback if $oldAutoCommit; return "can't update charged for invoice #$invnum: $error"; } + + foreach my $hook ( @precommit_hooks ) { + eval { + &{$hook}; #($self) ? + }; + if ( $@ ) { + $dbh->rollback if $oldAutoCommit; + return "$@ running precommit hook $hook\n"; + } + } $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; #no error diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index ad87cab7e..e7afa77ea 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -16,6 +16,7 @@ use FS::svc_broadband; use FS::svc_external; use FS::domain_record; use FS::part_export; +use FS::cdr; @ISA = qw( FS::Record ); @@ -570,6 +571,50 @@ sub get_session_history { } +=item get_cdrs_for_update + +Returns (and SELECTs "FOR UPDATE") all unprocessed (freesidestatus NULL) CDR +objects (see L) associated with this service. + +Currently CDRs are associated with svc_acct services via a DID in the +username. This part is rather tenative and still subject to change... + +=cut + +sub get_cdrs_for_update { + my($self, %options) = @_; + + my $default_prefix = $options{'default_prefix'}; + + #Currently CDRs are associated with svc_acct services via a DID in the + #username. This part is rather tenative and still subject to change... + #return () unless $self->svc_x->isa('FS::svc_acct'); + return () unless $self->part_svc->svcdb eq 'svc_acct'; + my $number = $self->svc_x->username; + + my @cdrs = + qsearch( + 'table' => 'cdr', + 'hashref' => { 'freesidestatus' => '', + 'charged_party' => $number + }, + 'extra_sql' => 'FOR UPDATE', + ); + + if ( length($default_prefix) ) { + push @cdrs, + qsearch( + 'table' => 'cdr', + 'hashref' => { 'freesidestatus' => '', + 'charged_party' => "$default_prefix$number", + }, + 'extra_sql' => 'FOR UPDATE', + ); + } + + @cdrs; +} + =item pkg_svc Returns the pkg_svc record for for this service, if applicable. diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm index 385fcb2e5..15af77b4f 100644 --- a/FS/FS/part_pkg/voip_cdr.pm +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -4,18 +4,19 @@ use strict; use vars qw(@ISA $DEBUG %info); use Date::Format; use Tie::IxHash; +use FS::Conf; use FS::Record qw(qsearchs qsearch); use FS::part_pkg::flat; #use FS::rate; -use FS::rate_prefix; +#use FS::rate_prefix; @ISA = qw(FS::part_pkg::flat); $DEBUG = 1; -tie my %region_method, 'Tie::IxHash', +tie my %rating_method, 'Tie::IxHash', 'prefix' => 'Rate calls by using destination prefix to look up a region and rate according to the internal prefix and rate tables', - 'upstream_rateid' => 'Rate calls by mapping the upstream rate ID (# rate plan ID?) directly to an internal rate (rate_detail)', #upstream_rateplanid + 'upstream' => 'Rate calls based on upstream data: If the call type is "1", map the upstream rate ID directly to an internal rate (rate_detail), otherwise, pass the upstream price through directly.', ; #tie my %cdr_location, 'Tie::IxHash', @@ -43,11 +44,15 @@ tie my %region_method, 'Tie::IxHash', 'select_key' => 'ratenum', 'select_label' => 'ratename', }, - 'region_method' => { 'name' => 'Region rating method', + 'rating_method' => { 'name' => 'Region rating method', 'type' => 'select', - 'select_options' => \%region_method, + 'select_options' => \%rating_method, }, + 'default_prefix' => { 'name' => 'Default prefix optionally prepended to customer DID numbers when searching for CDR records', + 'default' => '+1', + }, + #XXX also have option for an external db?? # 'cdr_location' => { 'name' => 'CDR database location' # 'type' => 'select', @@ -72,7 +77,7 @@ tie my %region_method, 'Tie::IxHash', # }, }, - 'fieldorder' => [qw( setup_fee recur_flat unused_credit ratenum ignore_unrateable )], + 'fieldorder' => [qw( setup_fee recur_flat unused_credit ratenum rating_method default_prefix )], 'weight' => 40, ); @@ -83,164 +88,248 @@ sub calc_setup { #false laziness w/voip_sqlradacct... resolve it if that one ever gets used again sub calc_recur { - my($self, $cust_pkg, $sdate, $details ) = @_; + my($self, $cust_pkg, $sdate, $details, $param ) = @_; my $last_bill = $cust_pkg->last_bill; my $ratenum = $cust_pkg->part_pkg->option('ratenum'); + my $spool_cdr = $cust_pkg->cust_main->spool_cdr; + my %included_min = (); my $charges = 0; + my $downstream_cdr = ''; + # also look for a specific domain??? (username@telephonedomain) foreach my $cust_svc ( grep { $_->part_svc->svcdb eq 'svc_acct' } $cust_pkg->cust_svc ) { foreach my $cdr ( - $cust_svc->get_cdrs( $last_bill, $$sdate ) + $cust_svc->get_cdrs_for_update() # $last_bill, $$sdate ) ) { if ( $DEBUG > 1 ) { warn "rating CDR $cdr\n". join('', map { " $_ => ". $cdr->{$_}. "\n" } keys %$cdr ); } - my( $regionnum, $rate_detail ); - if ( $self->option('region_method') eq 'prefix' - || ! $self->option('region_method') + my $rate_detail; + my( $rate_region, $regionnum ); + my $pretty_destnum; + my $charge = 0; + my @call_details = (); + if ( $self->option('rating_method') eq 'prefix' + || ! $self->option('rating_method') ) { - ### - # look up rate details based on called station id - ### - - my $dest = $cdr->{'calledstationid'}; # XXX - - #remove non-phone# stuff and whitespace - $dest =~ s/\s//g; - my $proto = ''; - $dest =~ s/^(\w+):// and $proto = $1; #sip: - my $siphost = ''; - $dest =~ s/\@(.*)$// and $siphost = $1; # @10.54.32.1, @sip.example.com - - #determine the country code - my $countrycode; - if ( $dest =~ /^011(((\d)(\d))(\d))(\d+)$/ - || $dest =~ /^\+(((\d)(\d))(\d))(\d+)$/ - ) - { - - my( $three, $two, $one, $u1, $u2, $rest ) = ( $1,$2,$3,$4,$5,$6 ); - #first look for 1 digit country code - if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) { - $countrycode = $one; - $dest = $u1.$u2.$rest; - } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2 - $countrycode = $two; - $dest = $u2.$rest; - } else { #3 digit country code - $countrycode = $three; - $dest = $rest; - } - - } else { - $countrycode = '1'; - $dest =~ s/^1//;# if length($dest) > 10; - } - - warn "rating call to +$countrycode $dest\n" if $DEBUG; - - #find a rate prefix, first look at most specific (4 digits) then 3, etc., - # finally trying the country code only - my $rate_prefix = ''; - for my $len ( reverse(1..6) ) { - $rate_prefix = qsearchs('rate_prefix', { - 'countrycode' => $countrycode, - #'npa' => { op=> 'LIKE', value=> substr($dest, 0, $len) } - 'npa' => substr($dest, 0, $len), - } ) and last; - } - $rate_prefix ||= qsearchs('rate_prefix', { - 'countrycode' => $countrycode, - 'npa' => '', - }); - - die "Can't find rate for call to +$countrycode $dest\n" - unless $rate_prefix; - - $regionnum = $rate_prefix->regionnum; - $rate_detail = qsearchs('rate_detail', { - 'ratenum' => $ratenum, - 'dest_regionnum' => $regionnum, - } ); - - warn " found rate for regionnum $regionnum ". - "and rate detail $rate_detail\n" - if $DEBUG; + die "rating_method 'prefix' not yet supported"; + +# ### +# # look up rate details based on called station id +# ### +# +# my $dest = $cdr->dst; +# +# #remove non-phone# stuff and whitespace +# $dest =~ s/\s//g; +# my $proto = ''; +# $dest =~ s/^(\w+):// and $proto = $1; #sip: +# my $siphost = ''; +# $dest =~ s/\@(.*)$// and $siphost = $1; # @10.54.32.1, @sip.example.com +# +# #determine the country code +# my $countrycode; +# if ( $dest =~ /^011(((\d)(\d))(\d))(\d+)$/ +# || $dest =~ /^\+(((\d)(\d))(\d))(\d+)$/ +# ) +# { +# +# my( $three, $two, $one, $u1, $u2, $rest ) = ( $1,$2,$3,$4,$5,$6 ); +# #first look for 1 digit country code +# if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) { +# $countrycode = $one; +# $dest = $u1.$u2.$rest; +# } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2 +# $countrycode = $two; +# $dest = $u2.$rest; +# } else { #3 digit country code +# $countrycode = $three; +# $dest = $rest; +# } +# +# } else { +# $countrycode = '1'; +# $dest =~ s/^1//;# if length($dest) > 10; +# } +# +# warn "rating call to +$countrycode $dest\n" if $DEBUG; +# $pretty_destnum = "+$countrycode $dest"; +# +# #find a rate prefix, first look at most specific (4 digits) then 3, etc., +# # finally trying the country code only +# my $rate_prefix = ''; +# for my $len ( reverse(1..6) ) { +# $rate_prefix = qsearchs('rate_prefix', { +# 'countrycode' => $countrycode, +# #'npa' => { op=> 'LIKE', value=> substr($dest, 0, $len) } +# 'npa' => substr($dest, 0, $len), +# } ) and last; +# } +# $rate_prefix ||= qsearchs('rate_prefix', { +# 'countrycode' => $countrycode, +# 'npa' => '', +# }); +# +# die "Can't find rate for call to +$countrycode $dest\n" +# unless $rate_prefix; +# +# $regionnum = $rate_prefix->regionnum; +# $rate_detail = qsearchs('rate_detail', { +# 'ratenum' => $ratenum, +# 'dest_regionnum' => $regionnum, +# } ); +# +# $rate_region = $rate_prefix->rate_region; +# +# warn " found rate for regionnum $regionnum ". +# "and rate detail $rate_detail\n" +# if $DEBUG; - } elsif ( $self->option('region_method') eq 'upstream_rateid' ) { #upstream_rateplanid + } elsif ( $self->option('rating_method') eq 'upstream' ) { - $regionnum = ''; #XXXXX regionnum should be something + if ( $cdr->cdrtypenum == 1 ) { #rate based on upstream rateid - $rate_detail = $cdr->cdr_upstream_rate->rate_detail; + $rate_detail = $cdr->cdr_upstream_rate->rate_detail; - warn " found rate for ". #regionnum $regionnum and ". - "rate detail $rate_detail\n" - if $DEBUG; + $regionnum = $rate_detail->dest_regionnum; + $rate_region = $rate_detail->dest_region; + + $pretty_destnum = $cdr->dst; + + warn " found rate for regionnum $regionnum and ". + "rate detail $rate_detail\n" + if $DEBUG; + + } else { #pass upstream price through + + $charge = sprintf('%.2f', $cdr->upstream_price); + + @call_details = ( + #time2str("%Y %b %d - %r", $cdr->calldate_unix ), + time2str("%c", $cdr->calldate_unix), #XXX this should probably be a config option dropdown so they can select US vs- rest of world dates or whatnot + 'N/A', #minutes... + '$'.$charge, + #$pretty_destnum, + $cdr->description, #$rate_region->regionname, + ); + + } } else { die "don't know how to rate CDRs using method: ". - $self->option('region_method'). "\n"; + $self->option('rating_method'). "\n"; } ### # find the price and add detail to the invoice ### - $included_min{$regionnum} = $rate_detail->min_included - unless exists $included_min{$regionnum}; - - my $granularity = $rate_detail->sec_granularity; - my $seconds = $cdr->{'acctsessiontime'}; # XXX - $seconds += $granularity - ( $seconds % $granularity ); - my $minutes = sprintf("%.1f", $seconds / 60); - $minutes =~ s/\.0$// if $granularity == 60; - - $included_min{$regionnum} -= $minutes; + # if $rate_detail is not found, skip this CDR... i.e. + # don't add it to invoice, don't set its status to NULL, + # don't call downstream_csv or something on it... + # but DO emit a warning... + if ( ! $rate_detail && ! scalar(@call_details) ) { + + warn "no rate_detail found for CDR.acctid: ". $cdr->acctid. + "; skipping\n" + + } else { # there *is* a rate_detail (or call_details), proceed... + + unless ( @call_details ) { + + $included_min{$regionnum} = $rate_detail->min_included + unless exists $included_min{$regionnum}; + + my $granularity = $rate_detail->sec_granularity; + my $seconds = $cdr->billsec; # |ength($cdr->billsec) ? $cdr->billsec : $cdr->duration; + $seconds += $granularity - ( $seconds % $granularity ); + my $minutes = sprintf("%.1f", $seconds / 60); + $minutes =~ s/\.0$// if $granularity == 60; + + $included_min{$regionnum} -= $minutes; + + if ( $included_min{$regionnum} < 0 ) { + my $charge_min = 0 - $included_min{$regionnum}; + $included_min{$regionnum} = 0; + $charge = sprintf('%.2f', $rate_detail->min_charge * $charge_min ); + $charges += $charge; + } + + # this is why we need regionnum/rate_region.... + warn " (rate region $rate_region)\n" if $DEBUG; + + @call_details = ( + #time2str("%Y %b %d - %r", $cdr->calldate_unix ), + time2str("%c", $cdr->calldate_unix), #XXX this should probably be a config option dropdown so they can select US vs- rest of world dates or whatnot + $minutes.'m', + '$'.$charge, + $pretty_destnum, + $rate_region->regionname, + ); - my $charge = 0; - if ( $included_min{$regionnum} < 0 ) { - my $charge_min = 0 - $included_min{$regionnum}; - $included_min{$regionnum} = 0; - $charge = sprintf('%.2f', $rate_detail->min_charge * $charge_min ); - $charges += $charge; + } + + warn " adding details on charge to invoice: ". + join(' - ', @call_details ) + if $DEBUG; + + push @$details, join(' - ', @call_details); #\@call_details, + + # if the customer flag is on, call "downstream_csv" or something + # like it to export the call downstream! + # XXX price plan option to pick format, or something... + $downstream_cdr .= $cdr->downstream_csv( 'format' => 'convergent' ) + if $spool_cdr; + + my $error = $cdr->set_status_and_rated_price('done', $charge); + die $error if $error; + } - - # XXXXXXX -# my $rate_region = $rate_prefix->rate_region; -# warn " (rate region $rate_region)\n" if $DEBUG; -# -# my @call_details = ( -# #time2str("%Y %b %d - %r", $session->{'acctstarttime'}), -# time2str("%c", $cdr->{'acctstarttime'}), #XXX -# $minutes.'m', -# '$'.$charge, -# "+$countrycode $dest", -# $rate_region->regionname, -# ); -# -# warn " adding details on charge to invoice: ". -# join(' - ', @call_details ) -# if $DEBUG; -# -# push @$details, join(' - ', @call_details); #\@call_details, - + } # $cdr } # $cust_svc + if ( $spool_cdr && length($downstream_cdr) ) { + + use FS::UID qw(datasrc); + my $dir = '/usr/local/etc/freeside/export.'. datasrc. '/cdr'; + mkdir $dir, 0700 unless -d $dir; + $dir .= '/'. $cust_pkg->custnum. + mkdir $dir, 0700 unless -d $dir; + my $filename = time2str("$dir/CDR%Y%m%d-spool.CSV", time); #XXX invoice date instead? would require changing the order things are generated in cust_main::bill insert cust_bill first - with transactions it could be done though + + push @{ $param->{'precommit_hooks'} }, + sub { + #lock the downstream spool file and append the records + use Fcntl qw(:flock); + use IO::File; + my $spool = new IO::File ">>$filename" + or die "can't open $filename: $!\n"; + flock( $spool, LOCK_EX) + or die "can't lock $filename: $!\n"; + seek($spool, 0, 2) + or die "can't seek to end of $filename: $!\n"; + print $spool $downstream_cdr; + flock( $spool, LOCK_UN ); + close $spool; + }; + + } #if ( $spool_cdr && length($downstream_cdr) ) + $self->option('recur_flat') + $charges; } diff --git a/FS/FS/rate_detail.pm b/FS/FS/rate_detail.pm index 1964be2f4..6f023f575 100644 --- a/FS/FS/rate_detail.pm +++ b/FS/FS/rate_detail.pm @@ -114,7 +114,11 @@ sub check { || $self->ut_foreign_keyn('orig_regionnum', 'rate_region', 'regionnum' ) || $self->ut_foreign_key('dest_regionnum', 'rate_region', 'regionnum' ) || $self->ut_number('min_included') - || $self->ut_money('min_charge') + + #|| $self->ut_money('min_charge') + #good enough for now... + || $self->ut_float('min_charge') + || $self->ut_number('sec_granularity') ; return $error if $error; @@ -122,6 +126,30 @@ sub check { $self->SUPER::check; } +=item orig_region + +Returns the origination region (see L) associated with this +call plan rate. + +=cut + +sub orig_region { + my $self = shift; + qsearchs('rate_region', { 'regionnum' => $self->orig_regionnum } ); +} + +=item dest_region + +Returns the destination region (see L) associated with this +call plan rate. + +=cut + +sub dest_region { + my $self = shift; + qsearchs('rate_region', { 'regionnum' => $self->dest_regionnum } ); +} + =back =head1 BUGS diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 759d7372e..a2b7a11c7 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -20,6 +20,7 @@ use Crypt::PasswdMD5 1.2; use FS::UID qw( datasrc ); use FS::Conf; use FS::Record qw( qsearch qsearchs fields dbh dbdef ); +use FS::Msgcat qw(gettext); use FS::svc_Common; use FS::cust_svc; use FS::part_svc; @@ -31,9 +32,9 @@ use FS::queue; use FS::radius_usergroup; use FS::export_svc; use FS::part_export; -use FS::Msgcat qw(gettext); use FS::svc_forward; use FS::svc_www; +use FS::cdr; @ISA = qw( FS::svc_Common ); @@ -1344,6 +1345,67 @@ sub get_session_history { $self->cust_svc->get_session_history(@_); } +=item get_cdrs TIMESTAMP_START TIMESTAMP_END [ 'OPTION' => 'VALUE ... ] + +=cut + +sub get_cdrs { + my($self, $start, $end, %opt ) = @_; + + my $did = $self->username; #yup + + my $prefix = $opt{'default_prefix'}; #convergent.au '+61' + + my $for_update = $opt{'for_update'} ? 'FOR UPDATE' : ''; + + #SELECT $for_update * FROM cdr + # WHERE calldate >= $start #need a conversion + # AND calldate < $end #ditto + # AND ( charged_party = "$did" + # OR charged_party = "$prefix$did" #if length($prefix); + # OR ( ( charged_party IS NULL OR charged_party = '' ) + # AND + # ( src = "$did" OR src = "$prefix$did" ) # if length($prefix) + # ) + # ) + # AND ( freesidestatus IS NULL OR freesidestatus = '' ) + + my $charged_or_src; + if ( length($prefix) ) { + $charged_or_src = + " AND ( charged_party = '$did' + OR charged_party = '$prefix$did' + OR ( ( charged_party IS NULL OR charged_party = '' ) + AND + ( src = '$did' OR src = '$prefix$did' ) + ) + ) + "; + } else { + $charged_or_src = + " AND ( charged_party = '$did' + OR ( ( charged_party IS NULL OR charged_party = '' ) + AND + src = '$did' + ) + ) + "; + + } + + qsearch( + 'select' => "$for_update *", + 'table' => 'cdr', + 'hashref' => { + #( freesidestatus IS NULL OR freesidestatus = '' ) + 'freesidestatus' => '', + }, + 'extra_sql' => $charged_or_src, + + ); + +} + =item radius_groups Returns all RADIUS groups for this account (see L). diff --git a/FS/MANIFEST b/FS/MANIFEST index 6360d5303..a70be40e4 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -322,3 +322,5 @@ FS/inventory_class.pm t/inventory_class.t FS/inventory_item.pm t/inventory_item.t +FS/cdr_upstream_rate.pm +t/cdr_upstream_rate.t diff --git a/FS/t/cdr_upstream_rate.t b/FS/t/cdr_upstream_rate.t new file mode 100644 index 000000000..f9458c527 --- /dev/null +++ b/FS/t/cdr_upstream_rate.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::cdr_upstream_rate; +$loaded=1; +print "ok 1\n"; diff --git a/bin/cdr_upstream_rate.import b/bin/cdr_upstream_rate.import new file mode 100755 index 000000000..fda3883b5 --- /dev/null +++ b/bin/cdr_upstream_rate.import @@ -0,0 +1,142 @@ +#!/usr/bin/perl -w +# +# Usage: bin/cdr_upstream_rate.import username ratenum filename +# +# records will be imported into cdr_upstream_rate, rate_detail and rate_region +# +# Example: bin/cdr_upstream_rate.import ivan 1 ~ivan/convergent/sample_rate_table.csv +# +# username: a freeside login (from /usr/local/etc/freeside/mapsecrets) +# ratenum: rate plan (FS::rate) created with the web UI +# filename: CSV file +# +# the following fields are currently used: +# - Class Code => cdr_upstream_rate.rateid +# - Description => rate_region.regionname +# (rate_detail->dest_region) +# - 1_rate => ( * 60 / 1_rate_seconds ) => rate_detail.min_charge +# - 1_rate_seconds => (used above) +# - 1_second_increment => rate_detail.sec_granularity +# +# the following fields are not (yet) used: +# - Flagfall => what's this for? +# +# - 1_cap_time => freeside doesn't have voip time caps yet... +# - 1_cap_cost => freeside doesn't have voip cost caps yet... +# - 1_repeat => not sure what this is for, sample data is all 0 +# +# - 2_rate => \ +# - 2_rate_seconds => | +# - 2_second_increment => | not sure what the second set of rate data +# - 2_cap_time => | is supposed to be for... +# - 2_cap_cost => | +# - 2_repeat => / +# +# - Carrier => probably not needed? +# - Start Date => not necessary? + +use strict; +use vars qw( $DEBUG ); +use Text::CSV_XS; +use FS::UID qw(dbh adminsuidsetup); +use FS::Record qw(qsearchs); +use FS::rate; +use FS::cdr_upstream_rate; +use FS::rate_detail; +use FS::rate_region; + +$DEBUG = 1; + +my $user = shift or die &usage; +adminsuidsetup $user; + +my $ratenum = shift or die &usage; + +my $rate = qsearchs( 'rate', { 'ratenum' => $ratenum } ); +die "rate plan $ratenum not found in rate table\n" + unless $rate; + +my $csv = new Text::CSV_XS; +my $hline = scalar(<>); +chomp($hline); +$csv->parse($hline) or die "can't parse header: $hline\n"; +my @header = $csv->fields(); + +$FS::UID::AutoCommit = 0; + +while (<>) { + + chomp; + my $line = $_; + +# #$line =~ /^(\d+),"([^"]+)"$/ or do { +# #} +# $line =~ /^(\d+),"([^"]+)"/ or do { +# warn "unparsable line: $line\n"; +# next; +# }; + + $csv->parse($line) or die "can't parse line: $line\n"; + my @line = $csv->fields(); + + my %hash = map { $_ => shift(@line) } @header; + + warn join('', map { "$_ => $hash{$_}\n" } keys %hash ) + if $DEBUG > 1; + + my $rate_region = new FS::rate_region { + 'regionname' => $hash{'Description'} + }; + + my $error = $rate_region->insert; + if ( $error ) { + dbh->rollback; + die "error inserting into rate_region: $error\n"; + } + my $dest_regionnum = $rate_region->regionnum; + warn "rate_region $dest_regionnum inserted\n" + if $DEBUG; + + my $rate_detail = new FS::rate_detail { + 'ratenum' => $ratenum, + 'dest_regionnum' => $dest_regionnum, + 'min_included' => 0, + #'min_charge', => sprintf('%.5f', 60 * $hash{'1_rate'} / $hash{'1_rate_seconds'} ), + 'min_charge', => sprintf('%.5f', $hash{'1_rate'} / + ( $hash{'1_rate_seconds'} / 60 ) + ), + 'sec_granularity' => $hash{'1_second_increment'}, + }; + $error = $rate_detail->insert; + if ( $error ) { + dbh->rollback; + die "error inserting into rate_detail: $error\n"; + } + my $ratedetailnum = $rate_detail->ratedetailnum; + warn "rate_detail $ratedetailnum inserted\n" + if $DEBUG; + + my $cdr_upstream_rate = new FS::cdr_upstream_rate { + 'upstream_rateid' => $hash{'Class Code'}, + 'ratedetailnum' => $rate_detail->ratedetailnum, + }; + $error = $cdr_upstream_rate->insert; + if ( $error ) { + dbh->rollback; + die "error inserting into cdr_upstream_rate: $error\n"; + } + warn "cdr_upstream_rate ". $cdr_upstream_rate->upstreamratenum. " inserted\n" + if $DEBUG; + + dbh->commit or die "can't commit: ". dbh->errstr; + + warn "\n" if $DEBUG; + +} + +dbh->commit or die "can't commit: ". dbh->errstr; + +sub usage { + "Usage:\n\ncdr_upstream_rate.import username ratenum filename\n"; +} + diff --git a/httemplate/edit/cust_main/billing.html b/httemplate/edit/cust_main/billing.html index 96f777baa..790f41f00 100644 --- a/httemplate/edit/cust_main/billing.html +++ b/httemplate/edit/cust_main/billing.html @@ -348,7 +348,7 @@ if ( $payby_default eq 'HIDE' ) { ); - + #this should use FS::payby my %allopt = ( 'CARD' => 'Credit card', 'CHEK' => 'Electronic check', @@ -433,6 +433,14 @@ if ( $payby_default eq 'HIDE' ) {
      spool_cdr eq "Y" ? 'CHECKED' : '' %>> Spool CDRs
      diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index 61e4086be..158c6e2ff 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -297,16 +297,35 @@ my $widget = new HTML::Widgets::SelectLayers( $html .= ' MULTIPLE' if $href->{$field}{'type'} eq 'select_multiple'; $html .= qq! NAME="$field" onChange="fchanged(this)">!; - foreach my $record ( - qsearch( $href->{$field}{'select_table'}, - $href->{$field}{'select_hash'} ) - ) { - my $value = $record->getfield($href->{$field}{'select_key'}); - $html .= qq!
      Spool CDRs<%= $cust_main->spool_cdr ? 'yes' : 'no' %>
      -- cgit v1.2.1 From 6235203383153fb1d3725662db4ee3e779b449f4 Mon Sep 17 00:00:00 2001 From: lsc Date: Thu, 23 Mar 2006 12:00:07 +0000 Subject: for subscription.pm and prorate.pm: -modify the subscription and prorate price plans (FS/FS/part_pkg/subscription.pm and prorate.pm) to have a configurable (add a field to the %info hash) billing day instead of "1st of the month" only. subscription will be easy, prorate will be a little trickier. essentially, I replaced the '1' in the 'day' field of the timelocal that generates $$date with the value I added to the %info hash, 'cutoff_day' -implement a price plan (new file in FS/FS/part_pkg/ - probably @ISA FS::part_pkg::subscription) that charges the first full month if the customer signs up between the 1st and the configurable billing day, and gives them the remainder of the month free if they sign up between the configurable billing day and the end of the month. if this is the first time the customer is billed, and if the date is greater than the cutoff date, advance $ssdate to cutoff_day of next month, else $$date is cutoff_date of this month. Either way, charge them for a month. ---------------------------------------------------------------------- --- FS/FS/part_pkg/billoneday.pm | 48 ++++++++++++++++++++++++++++++++++++++++++ FS/FS/part_pkg/prorate.pm | 14 +++++++----- FS/FS/part_pkg/subscription.pm | 15 +++++++------ 3 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 FS/FS/part_pkg/billoneday.pm diff --git a/FS/FS/part_pkg/billoneday.pm b/FS/FS/part_pkg/billoneday.pm new file mode 100644 index 000000000..8740547a3 --- /dev/null +++ b/FS/FS/part_pkg/billoneday.pm @@ -0,0 +1,48 @@ +package FS::part_pkg::billoneday; + +use strict; +use vars qw(@ISA %info); +use Time::Local qw(timelocal); +#use FS::Record qw(qsearch qsearchs); +use FS::part_pkg::flat; + +@ISA = qw(FS::part_pkg::flat); + +%info = ( + 'name' => 'charge a full month every (selectable) billing day', + 'fields' => { + 'setup_fee' => { 'name' => 'Setup fee for this package', + 'default' => 0, + }, + 'recur_fee' => { 'name' => 'Recurring fee for this package', + 'default' => 0, + }, + 'cutoff_day' => { 'name' => 'billing day', + 'default' => 1, + }, + + }, + 'fieldorder' => [ 'setup_fee', 'recur_fee','cutoff_day'], + #'setup' => 'what.setup_fee.value', + #'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; $sdate = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); \' + what.recur_fee.value', + 'freq' => 'm', + 'weight' => 30, +); + +sub calc_recur { + my($self, $cust_pkg, $sdate ) = @_; + + my $mnow = $$sdate; + my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; + my $mstart = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); + my $mend = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11)); + + if($mday > $self->option('cutoff_date') and $mstart != $mnow ) { + $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11)); + } + else{ + $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon, $year); + } + $self->option('recur_fee'); +} +1; diff --git a/FS/FS/part_pkg/prorate.pm b/FS/FS/part_pkg/prorate.pm index 86c64d53a..d9464eef9 100644 --- a/FS/FS/part_pkg/prorate.pm +++ b/FS/FS/part_pkg/prorate.pm @@ -9,7 +9,7 @@ use FS::part_pkg::flat; @ISA = qw(FS::part_pkg::flat); %info = ( - 'name' => 'First partial month pro-rated, then flat-rate (1st of month billing)', + 'name' => 'First partial month pro-rated, then flat-rate (selectable billing day)', 'fields' => { 'setup_fee' => { 'name' => 'Setup fee for this package', 'default' => 0, @@ -21,8 +21,12 @@ use FS::part_pkg::flat; ' of service at cancellation', 'type' => 'checkbox', }, - }, - 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit' ], + 'cutoff_day' => { 'name' => 'billing day', + 'default' => 1, + }, + + }, + 'fieldorder' => [ 'setup_fee', 'recur_fee', 'unused_credit', 'cutoff_day' ], #'setup' => 'what.setup_fee.value', #'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; my $mstart = timelocal(0,0,0,1,$mon,$year); my $mend = timelocal(0,0,0,1, $mon == 11 ? 0 : $mon+1, $year+($mon==11)); $sdate = $mstart; ( $part_pkg->freq - 1 ) * \' + what.recur_fee.value + \' / $part_pkg->freq + \' + what.recur_fee.value + \' / $part_pkg->freq * ($mend-$mnow) / ($mend-$mstart) ; \'', 'freq' => 'm', @@ -33,8 +37,8 @@ sub calc_recur { my($self, $cust_pkg, $sdate ) = @_; my $mnow = $$sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; - my $mstart = timelocal(0,0,0,1,$mon,$year); - my $mend = timelocal(0,0,0,1, $mon == 11 ? 0 : $mon+1, $year+($mon==11)); + my $mstart = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); + my $mend = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11)); $$sdate = $mstart; my $permonth = $self->option('recur_fee') / $self->freq; diff --git a/FS/FS/part_pkg/subscription.pm b/FS/FS/part_pkg/subscription.pm index 36b5a96fb..8ea791154 100644 --- a/FS/FS/part_pkg/subscription.pm +++ b/FS/FS/part_pkg/subscription.pm @@ -9,18 +9,22 @@ use FS::part_pkg::flat; @ISA = qw(FS::part_pkg::flat); %info = ( - 'name' => 'First partial month full charge, then flat-rate (1st of month billing)', + 'name' => 'First partial month full charge, then flat-rate (selectable month billing)', 'fields' => { 'setup_fee' => { 'name' => 'Setup fee for this package', 'default' => 0, }, 'recur_fee' => { 'name' => 'Recurring fee for this package', 'default' => 0, - }, + }, + 'cutoff_day' => { 'name' => 'billing day', + 'default' => 1, + }, + }, - 'fieldorder' => [ 'setup_fee', 'recur_fee' ], + 'fieldorder' => [ 'setup_fee', 'recur_fee','cutoff_day'], #'setup' => 'what.setup_fee.value', - #'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; $sdate = timelocal(0,0,0,1,$mon,$year); \' + what.recur_fee.value', + #'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; $sdate = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); \' + what.recur_fee.value', 'freq' => 'm', 'weight' => 30, ); @@ -30,9 +34,8 @@ sub calc_recur { my $mnow = $$sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; - $$sdate = timelocal(0,0,0,1,$mon,$year); + $$sdate = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); $self->option('recur_fee'); } - 1; -- cgit v1.2.1 From c39fc534e347971479110595c6a4bb36b40e1198 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 24 Mar 2006 19:49:53 +0000 Subject: fix spelling --- httemplate/misc/process/payment.cgi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httemplate/misc/process/payment.cgi b/httemplate/misc/process/payment.cgi index fa0ede89c..d21c57c40 100644 --- a/httemplate/misc/process/payment.cgi +++ b/httemplate/misc/process/payment.cgi @@ -126,7 +126,7 @@ if ( $cgi->param('save') ) { $new->set( $_ => $cgi->param($_) ) foreach @{$payby2fields{$payby}}; my $error = $new->replace($cust_main); - eidiot "payment processed sucessfully, but error saving info: $error" + eidiot "payment processed successfully, but error saving info: $error" if $error; $cust_main = $new; } @@ -134,7 +134,7 @@ if ( $cgi->param('save') ) { #success! %> -<%= include( '/elements/header.html', ucfirst($type{$payby}). ' processing sucessful', +<%= include( '/elements/header.html', ucfirst($type{$payby}). ' processing successful', include('/elements/menubar.html', 'Main menu' => popurl(3), "View this customer (#$custnum)" => -- cgit v1.2.1 From b9f9a5dc444a66ca138073a0e5229d85569e51b4 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 25 Mar 2006 02:23:26 +0000 Subject: successfully correct the spelling of sucessful --- FS/FS/cust_main.pm | 10 +++++----- FS/FS/queue_depend.pm | 2 +- FS/FS/svc_acct.pm | 4 ++-- FS/bin/freeside-setup | 2 +- fs_selfservice/FS-SelfService/cgi/agent.cgi | 8 ++++---- fs_selfservice/FS-SelfService/cgi/payment_results.html | 2 +- fs_selfservice/FS-SelfService/cgi/process_svc_acct.html | 2 +- fs_selfservice/FS-SelfService/cgi/process_svc_external.html | 2 +- fs_selfservice/FS-SelfService/cgi/recharge_results.html | 2 +- httemplate/misc/process/cdr-import.html | 4 ++-- httemplate/misc/process/cust_main-import.cgi | 2 +- httemplate/misc/process/cust_main-import_charges.cgi | 2 +- httemplate/misc/process/inventory_item-import.html | 4 ++-- httemplate/misc/upload-batch.cgi | 2 +- 14 files changed, 24 insertions(+), 24 deletions(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 99d27dd5e..62e6a5c44 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -336,7 +336,7 @@ Currently available options are: I and I. If I is set, all provisioning jobs will have a dependancy on the supplied jobnum (they will not run until the specific job completes). This can be used to defer provisioning until some action completes (such -as running the customer's credit card sucessfully). +as running the customer's credit card successfully). The I option is deprecated. If I is set true, no provisioning jobs (exports) are scheduled. (You can schedule them later with @@ -480,7 +480,7 @@ Currently available options are: I and I. If I is set, all provisioning jobs will have a dependancy on the supplied jobnum (they will not run until the specific job completes). This can be used to defer provisioning until some action completes (such -as running the customer's credit card sucessfully). +as running the customer's credit card successfully). The I option is deprecated. If I is set true, no provisioning jobs (exports) are scheduled. (You can schedule them later with @@ -2234,7 +2234,7 @@ if set, will override the value from the customer record. I is a free-text field passed to the gateway. It defaults to "Internet services". -If an I is specified, this payment (if sucessful) is applied to the +If an I is specified, this payment (if successful) is applied to the specified invoice. If you don't specify an I you might want to call the B method. @@ -2488,7 +2488,7 @@ sub realtime_bop { $capture->submit(); unless ( $capture->is_success ) { - my $e = "Authorization sucessful but capture failed, custnum #". + my $e = "Authorization successful but capture failed, custnum #". $self->custnum. ': '. $capture->result_code. ": ". $capture->error_message; warn $e; @@ -2668,7 +2668,7 @@ gateway is attempted. #I, I and I are also available. Any of these options, #if set, will override the value from the customer record. -#If an I is specified, this payment (if sucessful) is applied to the +#If an I is specified, this payment (if successful) is applied to the #specified invoice. If you don't specify an I you might want to #call the B method. diff --git a/FS/FS/queue_depend.pm b/FS/FS/queue_depend.pm index bc910d8e9..99a22c5c6 100644 --- a/FS/FS/queue_depend.pm +++ b/FS/FS/queue_depend.pm @@ -43,7 +43,7 @@ inherits from FS::Record. The following fields are currently supported: The job specified by B depends on the job specified B - the B job will not be run until the B job has completed -sucessfully (or manually removed). +successfully (or manually removed). =head1 METHODS diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index a2b7a11c7..2872a5f74 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -1267,7 +1267,7 @@ sub _op_seconds { } } - warn "$me update sucessful; committing\n" + warn "$me update successful; committing\n" if $DEBUG; $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; @@ -1455,7 +1455,7 @@ sub clone_kludge_unsuspend { =item check_password Checks the supplied password against the (possibly encrypted) password in the -database. Returns true for a sucessful authentication, false for no match. +database. Returns true for a successful authentication, false for no match. Currently supported encryptions are: classic DES crypt() and MD5 diff --git a/FS/bin/freeside-setup b/FS/bin/freeside-setup index bff2bcc63..9f87f10b7 100755 --- a/FS/bin/freeside-setup +++ b/FS/bin/freeside-setup @@ -109,7 +109,7 @@ warn "Freeside database initialized - commiting transaction\n" if $opt_v; $dbh->commit or die $dbh->errstr; $dbh->disconnect or die $dbh->errstr; -warn "Database initialization committed sucessfully\n" if $opt_v; +warn "Database initialization committed successfully\n" if $opt_v; sub dbdef_create { # reverse engineer the schema from the DB and save to file my( $dbh, $file ) = @_; diff --git a/fs_selfservice/FS-SelfService/cgi/agent.cgi b/fs_selfservice/FS-SelfService/cgi/agent.cgi index 695d20e4c..b51938d5c 100644 --- a/fs_selfservice/FS-SelfService/cgi/agent.cgi +++ b/fs_selfservice/FS-SelfService/cgi/agent.cgi @@ -193,7 +193,7 @@ sub process_signup { } else { $action = 'agent_main'; my $agent_info = agent_info( 'session_id' => $session_id ); - $agent_info->{'message'} = 'Signup sucessful'; + $agent_info->{'message'} = 'Signup successful'; $agent_info; } @@ -324,7 +324,7 @@ sub process_svc_acct { $action = 'agent_provision'; return { %{agent_provision()}, - 'message' => $result->{'svc'}. ' setup sucessfully.', + 'message' => $result->{'svc'}. ' setup successfully.', }; } @@ -343,7 +343,7 @@ sub process_svc_external { %{agent_provision()}, 'message' => $result->{'error'} ? ''. $result->{'error'}. '' - : $result->{'svc'}. ' setup sucessfully'. + : $result->{'svc'}. ' setup successfully'. ': serial number '. sprintf('%010d', $result->{'id'}). '-'. $result->{'title'} }; @@ -403,7 +403,7 @@ sub process_order_pkg { #$cgi->delete( grep { $_ ne 'custnum' } $cgi->param ); return { %{view_customer()}, - 'message' => 'Package order sucessful.', + 'message' => 'Package order successful.', }; } diff --git a/fs_selfservice/FS-SelfService/cgi/payment_results.html b/fs_selfservice/FS-SelfService/cgi/payment_results.html index de6c54dae..9fe400faf 100644 --- a/fs_selfservice/FS-SelfService/cgi/payment_results.html +++ b/fs_selfservice/FS-SelfService/cgi/payment_results.html @@ -7,7 +7,7 @@ <%= if ( $error ) { $OUT .= qq!Error processing your payment: $error!; } else { - $OUT .= 'Your payment was processed sucessfully. Thank you.'; + $OUT .= 'Your payment was processed successfully. Thank you.'; } %>
      diff --git a/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html b/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html index 200a80dc9..3b812919a 100644 --- a/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html +++ b/fs_selfservice/FS-SelfService/cgi/process_svc_acct.html @@ -4,7 +4,7 @@ <%= include('myaccount_menu') %> -<%= $svc %> setup sucessfully. +<%= $svc %> setup successfully.
      diff --git a/fs_selfservice/FS-SelfService/cgi/process_svc_external.html b/fs_selfservice/FS-SelfService/cgi/process_svc_external.html index 2328fa10f..19fec737f 100644 --- a/fs_selfservice/FS-SelfService/cgi/process_svc_external.html +++ b/fs_selfservice/FS-SelfService/cgi/process_svc_external.html @@ -4,7 +4,7 @@ <%= include('myaccount_menu') %> -<%= $svc %> setup sucessfully. +<%= $svc %> setup successfully.

      Your serial number is <%= sprintf("%010d-$title", $id) %> diff --git a/fs_selfservice/FS-SelfService/cgi/recharge_results.html b/fs_selfservice/FS-SelfService/cgi/recharge_results.html index ec3ea2c7a..b1eb7cb7a 100644 --- a/fs_selfservice/FS-SelfService/cgi/recharge_results.html +++ b/fs_selfservice/FS-SelfService/cgi/recharge_results.html @@ -7,7 +7,7 @@ <%= if ( $error ) { $OUT .= qq!Error processing your prepaid card: $error!; } else { - $OUT .= 'Prepaid card recharge sucessful!

      '; + $OUT .= 'Prepaid card recharge successful!

      '; $OUT .= '$'. sprintf('%.2f', $amount). ' added to your account.

      ' if $amount; diff --git a/httemplate/misc/process/cdr-import.html b/httemplate/misc/process/cdr-import.html index 381b07820..653dd479e 100644 --- a/httemplate/misc/process/cdr-import.html +++ b/httemplate/misc/process/cdr-import.html @@ -19,8 +19,8 @@ } else { %> - <%= include("/elements/header.html",'Import sucessful') %> + <%= include("/elements/header.html",'Import successful') %> - <%= include("/elements/footer.html",'Import sucessful') %> <% + <%= include("/elements/footer.html",'Import successful') %> <% } %> diff --git a/httemplate/misc/process/cust_main-import.cgi b/httemplate/misc/process/cust_main-import.cgi index 2348ef680..371929a5e 100644 --- a/httemplate/misc/process/cust_main-import.cgi +++ b/httemplate/misc/process/cust_main-import.cgi @@ -25,6 +25,6 @@ } else { %> - <%= include("/elements/header.html",'Import sucessful') %> <% + <%= include("/elements/header.html",'Import successful') %> <% } %> diff --git a/httemplate/misc/process/cust_main-import_charges.cgi b/httemplate/misc/process/cust_main-import_charges.cgi index c14228cf4..404dfde2e 100644 --- a/httemplate/misc/process/cust_main-import_charges.cgi +++ b/httemplate/misc/process/cust_main-import_charges.cgi @@ -21,6 +21,6 @@ } else { %> - <%= include("/elements/header.html",'Import sucessful') %> <% + <%= include("/elements/header.html",'Import successful') %> <% } %> diff --git a/httemplate/misc/process/inventory_item-import.html b/httemplate/misc/process/inventory_item-import.html index 8a58203c2..e98a6ed2a 100644 --- a/httemplate/misc/process/inventory_item-import.html +++ b/httemplate/misc/process/inventory_item-import.html @@ -19,9 +19,9 @@ } else { %> - <%= include("/elements/header.html",'Import sucessful') %> + <%= include("/elements/header.html",'Import successful') %> - <%= include("/elements/footer.html",'Import sucessful') %> <% + <%= include("/elements/footer.html",'Import successful') %> <% } %> diff --git a/httemplate/misc/upload-batch.cgi b/httemplate/misc/upload-batch.cgi index 24d7cf15c..746b81b17 100644 --- a/httemplate/misc/upload-batch.cgi +++ b/httemplate/misc/upload-batch.cgi @@ -24,7 +24,7 @@ } else { %> - <%= include("/elements/header.html",'Batch results upload sucessful') %> <% + <%= include("/elements/header.html",'Batch results upload successful') %> <% } %> -- cgit v1.2.1 From ee8ec98f0161b6fe11a231c7ac44e50811723b99 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 30 Mar 2006 14:22:38 +0000 Subject: move all the schema-updating magic into DBIx::DBSchema --- FS/bin/freeside-upgrade | 92 +++---------------------------------------------- README.1.7.0 | 6 +++- 2 files changed, 9 insertions(+), 89 deletions(-) diff --git a/FS/bin/freeside-upgrade b/FS/bin/freeside-upgrade index 419384c2a..25d883fcf 100755 --- a/FS/bin/freeside-upgrade +++ b/FS/bin/freeside-upgrade @@ -3,14 +3,11 @@ use strict; use vars qw($DEBUG $DRY_RUN); use Term::ReadKey; -use DBIx::DBSchema 0.27; +use DBIx::DBSchema 0.31; use FS::UID qw(adminsuidsetup checkeuid datasrc ); #getsecrets); use FS::Schema qw( dbdef dbdef_dist reload_dbdef ); - $DEBUG = 1; -$DRY_RUN = 0; - die "Not running uid freeside!" unless checkeuid(); @@ -25,63 +22,9 @@ dbdef_create($dbh, $dbdef_file); delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload reload_dbdef($dbdef_file); - -foreach my $table ( dbdef_dist->tables ) { - - if ( dbdef->table($table) ) { - - warn "$table exists\n" if $DEBUG > 1; - - foreach my $column ( dbdef_dist->table($table)->columns ) { - if ( dbdef->table($table)->column($column) ) { - warn " $table.$column exists\n" if $DEBUG > 2; - } else { - - if ( $DEBUG ) { - print STDERR "column $table.$column does not exist. create?"; - next unless yesno(); - } - - foreach my $statement ( - dbdef_dist->table($table)->column($column)->sql_add_column( $dbh ) - ) { - warn "$statement\n" if $DEBUG || $DRY_RUN; - unless ( $DRY_RUN ) { - $dbh->do( $statement) - or die "CREATE error: ". $dbh->errstr. "\nexecuting: $statement"; - } - } - - } - - } - - #should eventually check & create missing indices - - #should eventually drop columns not in dbdef_dist... - - } else { - - if ( $DEBUG ) { - print STDERR "table $table does not exist. create?"; - next unless yesno(); - } - - foreach my $statement ( - dbdef_dist->table($table)->sql_create_table( $dbh ) - ) { - warn "$statement\n" if $DEBUG || $DRY_RUN; - unless ( $DRY_RUN ) { - $dbh->do( $statement) - or die "CREATE error: ". $dbh->errstr. "\nexecuting: $statement"; - } - } - - } - -} - -# should eventually drop tables not in dbdef_dist too i guess... +$DBIx::DBSchema::DEBUG = $DEBUG; +$DBIx::DBSchema::Table::DEBUG = $DEBUG; +dbdef->update_schema( dbdef_dist, $dbh ); $dbh->commit or die $dbh->errstr; @@ -91,32 +34,6 @@ $dbh->disconnect or die $dbh->errstr; ### -my $all = 0; -sub yesno { - print STDERR ' [yes/no/all] '; - if ( $all ) { - warn "yes\n"; - return 1; - } else { - while ( 1 ) { - ReadMode 4; - my $x = lc(ReadKey); - ReadMode 0; - if ( $x eq 'n' ) { - warn "no\n"; - return 0; - } elsif ( $x eq 'y' ) { - warn "yes\n"; - return 1; - } elsif ( $x eq 'a' ) { - warn "yes\n"; - $all = 1; - return 1; - } - } - } -} - sub dbdef_create { # reverse engineer the schema from the DB and save to file my( $dbh, $file ) = @_; my $dbdef = new_native DBIx::DBSchema $dbh; @@ -128,4 +45,3 @@ sub usage { } 1; - diff --git a/README.1.7.0 b/README.1.7.0 index 834972ae2..62d6232c1 100644 --- a/README.1.7.0 +++ b/README.1.7.0 @@ -1,5 +1,5 @@ -install DBIx::DBSchema 0.29 (or later) +install DBIx::DBSchema 0.31 (or later) make install-perl-modules run "freeside-upgrade username" to uprade your database schema @@ -12,6 +12,10 @@ to the new cust_tax_exempt_pkg table. An example script to get you started is in bin/fs-migrate-cust_tax_exempt - it may need to be customized for your specific data. +Optional for better zip code report performance: +CREATE INDEX cust_main16 on cust_main ( zip ); +CREATE INDEX cust_main17 on cust_main ( ship_zip ); + ------ make install-docs -- cgit v1.2.1 From fe413bf637b9cc4375defe83d8d7e1e9ed06b9dc Mon Sep 17 00:00:00 2001 From: lsc Date: Fri, 31 Mar 2006 09:20:54 +0000 Subject: fixed the errors pointed out by Ivan in the following email: ---- before and after now? I gave subscription and prorate a try. Subscription came out as: subscription 27th (03/25/06 - 04/27/06) $10.00 subscription 23rd (03/25/06 - 04/23/06) $10.00 the "23rd" one is right, but the "27th" one should have only advanced the date two days to 3/27/06. Prorate came out as: prorate 23rd (03/25/06 - 04/23/06) $9.20 prorate 27th (03/25/06 - 04/27/06) $10.49 The "23rd" one is right, but the "27th" one should have only advanced the date two days to 4/27/06. lsc@prgmr.com --- FS/FS/part_pkg/prorate.pm | 19 +++++++++++++++---- FS/FS/part_pkg/subscription.pm | 13 ++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/FS/FS/part_pkg/prorate.pm b/FS/FS/part_pkg/prorate.pm index d9464eef9..ac2f656b9 100644 --- a/FS/FS/part_pkg/prorate.pm +++ b/FS/FS/part_pkg/prorate.pm @@ -35,12 +35,23 @@ use FS::part_pkg::flat; sub calc_recur { my($self, $cust_pkg, $sdate ) = @_; + my $cutoff_day=$self->option('cutoff_day') or 1; my $mnow = $$sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; - my $mstart = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); - my $mend = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11)); - $$sdate = $mstart; - + my $mend; + my $mstart; + if($mday > $cutoff_day){ + $mend = timelocal(0,0,0,$cutoff_day, $mon == 11 ? 0 : $mon+1, $year+($mon==11)); + $mstart= timelocal(0,0,0,$cutoff_day,$mon,$year); + + } + else{ + $mend = timelocal(0,0,0,$cutoff_day, $mon, $year); + if ($mon==0) {$mon=11;$year--;} else {$mon--;} + $mstart= timelocal(0,0,0,$cutoff_day,$mon,$year); + } + + $$sdate = $mstart; my $permonth = $self->option('recur_fee') / $self->freq; $permonth * ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) ); diff --git a/FS/FS/part_pkg/subscription.pm b/FS/FS/part_pkg/subscription.pm index 8ea791154..ccfcc00a8 100644 --- a/FS/FS/part_pkg/subscription.pm +++ b/FS/FS/part_pkg/subscription.pm @@ -9,7 +9,7 @@ use FS::part_pkg::flat; @ISA = qw(FS::part_pkg::flat); %info = ( - 'name' => 'First partial month full charge, then flat-rate (selectable month billing)', + 'name' => 'First partial month full charge, then flat-rate (selectable billing day)', 'fields' => { 'setup_fee' => { 'name' => 'Setup fee for this package', 'default' => 0, @@ -23,18 +23,21 @@ use FS::part_pkg::flat; }, 'fieldorder' => [ 'setup_fee', 'recur_fee','cutoff_day'], - #'setup' => 'what.setup_fee.value', - #'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; $sdate = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); \' + what.recur_fee.value', 'freq' => 'm', 'weight' => 30, ); sub calc_recur { my($self, $cust_pkg, $sdate ) = @_; - + my $cutoff_day=$self->option('cutoff_day') or 1; my $mnow = $$sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; - $$sdate = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); + + if($mday <$cutoff_day){ + if ($mon==0) {$mon=11;$year--;} + else {$mon--;} + } +$$sdate = timelocal(0,0,0,$cutoff_day,$mon,$year); $self->option('recur_fee'); } -- cgit v1.2.1 From 9719693a031bee8d35dffa7ea543f27a25443309 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 31 Mar 2006 23:22:08 +0000 Subject: quick sort fix for billing events --- httemplate/browse/part_bill_event.cgi | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/httemplate/browse/part_bill_event.cgi b/httemplate/browse/part_bill_event.cgi index 380e4d78b..91e31d832 100755 --- a/httemplate/browse/part_bill_event.cgi +++ b/httemplate/browse/part_bill_event.cgi @@ -31,13 +31,14 @@ my $total = scalar(@part_bill_event); foreach my $payby ( keys %payby ) { my $oldfreq = ''; - my @payby_part_bill_event = grep { $payby eq $_->payby } - sort { $a->freq cmp $b->freq # for now - || $a->seconds <=> $b->seconds - || $a->weight <=> $b->weight - || $a->eventpart <=> $b->eventpart - } - @part_bill_event; + my @payby_part_bill_event = + grep { $payby eq $_->payby } + sort { ( $a->freq || '1d') cmp ( $b->freq || '1d' ) # for now + || $a->seconds <=> $b->seconds + || $a->weight <=> $b->weight + || $a->eventpart <=> $b->eventpart + } + @part_bill_event; %> -- cgit v1.2.1 From 36693c42cbaca86f6957d5ed6794624810018bb3 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 2 Apr 2006 22:13:34 +0000 Subject: typo --- FS/FS/cust_main.pm | 2 +- FS/FS/cust_pay_batch.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 62e6a5c44..a2eb72473 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1311,7 +1311,7 @@ sub check { } if ( $self->paydate eq '' || $self->paydate eq '-' ) { - return "Expriation date required" + return "Expiration date required" unless $self->payby =~ /^(BILL|PREPAY|CHEK|DCHK|LECB|CASH|WEST|MCRD)$/; $self->paydate(''); } else { diff --git a/FS/FS/cust_pay_batch.pm b/FS/FS/cust_pay_batch.pm index 8059f1ca2..25b796ba9 100644 --- a/FS/FS/cust_pay_batch.pm +++ b/FS/FS/cust_pay_batch.pm @@ -147,7 +147,7 @@ sub check { return "Unknown card type" if cardtype($cardnum) eq "Unknown"; if ( $self->exp eq '' ) { - return "Expriation date required"; #unless + return "Expiration date required"; #unless $self->exp(''); } else { if ( $self->exp =~ /^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})$/ ) { -- cgit v1.2.1 From 929f432c766bbe3bdeed5b80818a12ddf6ec6339 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 3 Apr 2006 09:46:57 +0000 Subject: have the UI use full country names, and state names outside the US... --- FS/FS/Misc.pm | 181 +++++++++++++++++--------- htetc/handler.pl | 4 +- httemplate/edit/cust_main/contact.html | 2 +- httemplate/edit/cust_main/select-country.html | 11 +- httemplate/edit/cust_main/select-state.html | 13 +- httemplate/misc/states.cgi | 14 +- httemplate/view/cust_main/contacts.html | 4 +- 7 files changed, 139 insertions(+), 90 deletions(-) diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm index 2e383d549..101a2d4e0 100644 --- a/FS/FS/Misc.pm +++ b/FS/FS/Misc.pm @@ -7,7 +7,7 @@ use Carp; use Data::Dumper; @ISA = qw( Exporter ); -@EXPORT_OK = qw( send_email send_fax ); +@EXPORT_OK = qw( send_email send_fax states_hash state_label ); $DEBUG = 0; @@ -185,6 +185,80 @@ sub send_email { } +#this kludges a "mysmtpsend" method into Mail::Internet for send_email above +package Mail::Internet; + +use Mail::Address; +use Net::SMTP; + +sub Mail::Internet::mysmtpsend { + my $src = shift; + my %opt = @_; + my $host = $opt{Host}; + my $envelope = $opt{MailFrom}; + my $noquit = 0; + my $smtp; + my @hello = defined $opt{Hello} ? (Hello => $opt{Hello}) : (); + + push(@hello, 'Port', $opt{'Port'}) + if exists $opt{'Port'}; + + push(@hello, 'Debug', $opt{'Debug'}) + if exists $opt{'Debug'}; + + if(ref($host) && UNIVERSAL::isa($host,'Net::SMTP')) { + $smtp = $host; + $noquit = 1; + } + else { + #local $SIG{__DIE__}; + #$smtp = eval { Net::SMTP->new($host, @hello) }; + $smtp = new Net::SMTP $host, @hello; + } + + unless ( defined($smtp) ) { + my $err = $!; + $err =~ s/Invalid argument/Unknown host/; + return "can't connect to $host: $err" + } + + my $hdr = $src->head->dup; + + _prephdr($hdr); + + # Who is it to + + my @rcpt = map { ref($_) ? @$_ : $_ } grep { defined } @opt{'To','Cc','Bcc'}; + @rcpt = map { $hdr->get($_) } qw(To Cc Bcc) + unless @rcpt; + my @addr = map($_->address, Mail::Address->parse(@rcpt)); + + return 'No valid destination addresses found!' + unless(@addr); + + $hdr->delete('Bcc'); # Remove blind Cc's + + # Send it + + #warn "Headers: \n" . join('',@{$hdr->header}); + #warn "Body: \n" . join('',@{$src->body}); + + my $ok = $smtp->mail( $envelope ) && + $smtp->to(@addr) && + $smtp->data(join("", @{$hdr->header},"\n",@{$src->body})); + + if ( $ok ) { + $smtp->quit + unless $noquit; + return ''; + } else { + return $smtp->code. ' '. $smtp->message; + } + +} +package FS::Misc; +#eokludge + =item send_fax OPTION => VALUE ... Options: @@ -268,77 +342,66 @@ sub send_fax { } -package Mail::Internet; - -use Mail::Address; -use Net::SMTP; - -sub Mail::Internet::mysmtpsend { - my $src = shift; - my %opt = @_; - my $host = $opt{Host}; - my $envelope = $opt{MailFrom}; - my $noquit = 0; - my $smtp; - my @hello = defined $opt{Hello} ? (Hello => $opt{Hello}) : (); - - push(@hello, 'Port', $opt{'Port'}) - if exists $opt{'Port'}; +=item states_hash COUNTRY - push(@hello, 'Debug', $opt{'Debug'}) - if exists $opt{'Debug'}; +Returns a list of key/value pairs containing state (or other sub-country +division) abbriviations and names. - if(ref($host) && UNIVERSAL::isa($host,'Net::SMTP')) { - $smtp = $host; - $noquit = 1; - } - else { - #local $SIG{__DIE__}; - #$smtp = eval { Net::SMTP->new($host, @hello) }; - $smtp = new Net::SMTP $host, @hello; - } - - unless ( defined($smtp) ) { - my $err = $!; - $err =~ s/Invalid argument/Unknown host/; - return "can't connect to $host: $err" - } +=cut - my $hdr = $src->head->dup; +use FS::Record qw(qsearch); +use Locale::SubCountry; + +sub states_hash { + my($country) = @_; + + my @states = +# sort + map { s/[\n\r]//g; $_; } + map { $_->state; } + qsearch( 'cust_main_county', + { 'country' => $country }, + 'DISTINCT ON ( state ) *', + ) + ; + + #it could throw a fatal "Invalid country code" error (for example "AX") + my $subcountry = eval { new Locale::SubCountry($country) } + or return ( '', '(n/a)' ); + + #"i see your schwartz is as big as mine!" + map { ( $_->[0] => $_->[1] ) } + sort { $a->[1] cmp $b->[1] } + map { [ $_ => state_label($_, $subcountry) ] } + @states; +} - _prephdr($hdr); +=item state_label STATE COUNTRY_OR_LOCALE_SUBCOUNRY_OBJECT - # Who is it to +=cut - my @rcpt = map { ref($_) ? @$_ : $_ } grep { defined } @opt{'To','Cc','Bcc'}; - @rcpt = map { $hdr->get($_) } qw(To Cc Bcc) - unless @rcpt; - my @addr = map($_->address, Mail::Address->parse(@rcpt)); +sub state_label { + my( $state, $country ) = @_; - return 'No valid destination addresses found!' - unless(@addr); + unless ( ref($country) ) { + $country = eval { new Locale::SubCountry($country) } + or return'(n/a)'; - $hdr->delete('Bcc'); # Remove blind Cc's - - # Send it + } - #warn "Headers: \n" . join('',@{$hdr->header}); - #warn "Body: \n" . join('',@{$src->body}); + # US kludge to avoid changing existing behaviour + # also we actually *use* the abbriviations... + my $full_name = $country->country_code eq 'US' + ? '' + : $country->full_name($state); - my $ok = $smtp->mail( $envelope ) && - $smtp->to(@addr) && - $smtp->data(join("", @{$hdr->header},"\n",@{$src->body})); + $full_name = '' if $full_name eq 'unknown'; + $full_name =~ s/\(see also.*\)\s*$//; + $full_name .= " ($state)" if $full_name; - if ( $ok ) { - $smtp->quit - unless $noquit; - return ''; - } else { - return $smtp->code. ' '. $smtp->message; - } + $full_name || $state || '(n/a)'; } -package FS::Misc; =back diff --git a/htetc/handler.pl b/htetc/handler.pl index 9b808e68c..008b0b5f9 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -114,6 +114,7 @@ sub handler use String::Approx qw(amatch); use Chart::LinesPoints; use HTML::Widgets::SelectLayers 0.05; + use Locale::Country; use FS; use FS::UID qw(cgisuidsetup dbh getotaker datasrc driver_name); use FS::Record qw(qsearch qsearchs fields dbdef); @@ -122,7 +123,7 @@ sub handler eidiot small_custview myexit http_header); use FS::UI::Web; use FS::Msgcat qw(gettext geterror); - use FS::Misc qw( send_email send_fax ); + use FS::Misc qw( send_email send_fax states_hash state_label ); use FS::Report::Table::Monthly; use FS::TicketSystem; @@ -208,6 +209,7 @@ sub handler my( $self, $location ) = @_; use vars qw($m); + # false laziness w/below if ( defined(@DBIx::Profile::ISA) ) { #profiling redirect my $page = diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html index e0cd06f56..6e4f08957 100644 --- a/httemplate/edit/cust_main/contact.html +++ b/httemplate/edit/cust_main/contact.html @@ -96,7 +96,7 @@ my $r = qq!* !; <%=$r%>Country - <%= include('select-country.html', %select_hash ) %> + <%= include('select-country.html', %select_hash ) %> diff --git a/httemplate/edit/cust_main/select-country.html b/httemplate/edit/cust_main/select-country.html index 014effd66..3941f2f93 100644 --- a/httemplate/edit/cust_main/select-country.html +++ b/httemplate/edit/cust_main/select-country.html @@ -26,7 +26,7 @@ function <%= $opt{'prefix'} %>country_changed(what, callback) { - country = what.options[what.selectedIndex].text; + country = what.options[what.selectedIndex].value; function <%= $opt{'prefix'} %>update_states(states) { @@ -36,8 +36,8 @@ // add the new states var statesArray = eval('(' + states + ')' ); - for ( var s = 0; s < statesArray.length; s++ ) { - var stateLabel = statesArray[s]; + for ( var s = 0; s < statesArray.length; s=s+2 ) { + var stateLabel = statesArray[s+1]; if ( stateLabel == "" ) stateLabel = '(n/a)'; opt(what.form.<%= $opt{'prefix'} %>state, statesArray[s], stateLabel); @@ -58,13 +58,14 @@ > -<% foreach my $state ( - sort - map { $_->state } - qsearch( 'cust_main_county', - { 'country' => $opt{'country'} }, - 'DISTINCT ON ( state ) *', - ) - ) { -%> +<% tie my %states, 'Tie::IxHash', states_hash( $opt{'country'} ); %> +<% foreach my $state ( keys %states ) { %> -
    @@ -234,6 +234,8 @@ - Locally defined fields
  • View/Edit message catalog - Change error messages and other customizable labels. +
  • View/Edit inventory classes and inventory + - Setup inventory classes and stock inventory.

diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index 5da4d82fb..a2fb89c12 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -19,8 +19,10 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi); push @where, - "bill >= $beginning ", - "bill <= $ending", + #"bill >= $beginning ", + #"bill <= $ending", + "CASE WHEN bill IS NULL THEN 0 ELSE bill END >= $beginning ", + "CASE WHEN bill IS NULL THEN 0 ELSE bill END <= $ending", '( cancel IS NULL OR cancel = 0 )'; } else { @@ -141,7 +143,7 @@ sub time_or_blank { 'name' => 'packages', 'query' => $sql_query, 'count_query' => $count_query, - 'redirect' => $link, + #'redirect' => $link, 'header' => [ '#', 'Package', 'Status', diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html index 7f7243588..6cf574164 100644 --- a/httemplate/search/elements/search.html +++ b/httemplate/search/elements/search.html @@ -8,8 +8,12 @@ # 'name' => 'items', #name for the records returned # # # some HTML callbacks... - # 'menubar' => '', #menubar arrayref - # 'html_init' => '', #after the header/menubar and before the pager + # 'menubar' => '', #menubar arrayref + # 'html_init' => '', #after the header/menubar and before the pager + # 'html_foot' => '', #at the bottom + # 'html_posttotal' => '', #at the bottom + # # (these three can be strings or coderefs) + # # # #literal SQL query string or qsearch hashref, required # 'query' => { @@ -39,6 +43,10 @@ # # (if not specified the database column names will be used) # 'header' => [ '#', 'Item' ], # + # 'disable_download' => '', # set true to hide the CSV/Excel download links + # 'disable_nonefound' => '', # set true to disable the "No matching Xs found" + # # message + # # #listref - each item is a literal column name (or method) or coderef # #if not specified all columns will be shown # 'fields' => [ @@ -286,7 +294,13 @@ include( '/elements/menubar.html', @menubar ) ) %> - <%= defined($opt{'html_init'}) ? $opt{'html_init'} : '' %> + <%= defined($opt{'html_init'}) + ? ( ref($opt{'html_init'}) + ? &{$opt{'html_init'}}() + : $opt{'html_init'} + ) + : '' + %> <% my $pager = include ( '/elements/pager.html', 'offset' => $offset, 'num_rows' => scalar(@$rows), @@ -295,26 +309,38 @@ ); %> <% unless ( $total ) { %> - No matching <%= $opt{'name'} %> found.
+ <% unless ( $opt{'disable_nonefound'} ) { %> + No matching <%= $opt{'name'} %> found.
+ <% } %> <% } else { %> - + <% unless ( $opt{'disable_download'} ) { %> + + <% } %>
- <%= $total %> total <%= $opt{'name'} %>
+ <%= $total %> total <%= $opt{'name'} %> + <%= defined($opt{'html_posttotal'}) + ? ( ref($opt{'html_posttotal'}) + ? &{$opt{'html_posttotal'}}() + : $opt{'html_posttotal'} + ) + : '' + %> +
<% if ( $opt{'count_addl'} ) { %> <% my $n=0; foreach my $count ( @{$opt{'count_addl'}} ) { %> <%= sprintf( $count, $count_arrayref->[++$n] ) %>
<% } %> <% } %>
- <% $cgi->param('_type', "$xlsname.xls" ); %> - Download full results
- as Excel spreadsheet
- <% $cgi->param('_type', 'csv'); %> - as CSV file -
+ <% $cgi->param('_type', "$xlsname.xls" ); %> + Download full results
+ as Excel spreadsheet
+ <% $cgi->param('_type', 'csv'); %> + as CSV file +
@@ -471,6 +497,13 @@
<% } %> + <%= defined($opt{'html_foot'}) + ? ( ref($opt{'html_foot'}) + ? &{$opt{'html_foot'}}() + : $opt{'html_foot'} + ) + : '' + %> <%= include( '/elements/footer.html' ) %> <% } %> <% } %> diff --git a/httemplate/search/inventory_class.html b/httemplate/search/inventory_class.html deleted file mode 100644 index 37735f3c9..000000000 --- a/httemplate/search/inventory_class.html +++ /dev/null @@ -1,89 +0,0 @@ -<% - -tie my %labels, 'Tie::IxHash', - 'num_avail' => 'Available', # (upload batch)', - 'num_used' => 'In use', #'Used', #'Allocated', - 'num_total' => 'Total', -; - -my %link = ( - 'num_avail' => ';avail=1', - 'num_used' => ';avail=1', - 'num_total' => '', -); - -my %inv_action_link = ( - 'num_avail' => [ 'upload batch', - $p.'misc/inventory_item-import.html?classnum=', - 'classnum' - ], -); - -my $link = [ "${p}edit/inventory_class.html?", 'classnum' ]; - -%><%= include( 'elements/search.html', - 'title' => 'Inventory Classes', - 'name' => 'inventory classes', - 'menubar' => [ 'Add a new inventory class' => - $p.'edit/inventory_class.html', - ], - 'query' => { 'table' => 'inventory_class', }, - 'count_query' => 'SELECT COUNT(*) FROM inventory_class', - 'header' => [ '#', 'Inventory class', 'Inventory' ], - 'fields' => [ 'classnum', - 'classname', - sub { - #my $inventory_class = shift; - my $i_c = shift; - - my $link = - $p. 'search/inventory_item.html?'. - 'classnum='. $i_c->classnum; - - my %actioncol = (); - foreach ( keys %inv_action_link ) { - my($label, $baseurl, $method) = - @{ $inv_action_link{$_} }; - my $url = $baseurl. $i_c->$method(); - $actioncol{$_} = - ''. - '('. - ''. - $label. - ''. - ')'. - ''; - } - - my %num = map { - $_ => $i_c->$_(); - } keys %labels; - - [ map { - [ - { - 'data' => ''. $num{$_}. '', - 'align' => 'right', - }, - { - 'data' => $labels{$_}, - 'align' => 'left', - 'link' => ( $num{$_} - ? $link.$link{$_} - : '' - ), - }, - { 'data' => $actioncol{$_}, - 'align' => 'left', - }, - ] - } keys %labels - ]; - }, - ], - 'links' => [ $link, - $link, - '', - ], - ) -%> -- cgit v1.2.1 From cf0749726a8e719909cf5e2dbe7fb0f1581ae8c3 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 26 Apr 2006 13:16:11 +0000 Subject: apache reload doesn't work when server isn't running already --- Makefile | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index dae5f23b4..1163237b8 100644 --- a/Makefile +++ b/Makefile @@ -41,10 +41,8 @@ INIT_INSTALL = /usr/sbin/update-rc.d freeside defaults 21 20 #not necessary (freebsd) #INIT_INSTALL = /usr/bin/true -#deb -HTTPD_RESTART = /etc/init.d/apache reload -#suse -#HTTPD_RESTART = /etc/init.d/apache restart +#deb, suse +HTTPD_RESTART = /etc/init.d/apache restart #redhat, fedora, mandrake #HTTPD_RESTART = /etc/init.d/httpd restart #freebsd @@ -198,7 +196,10 @@ perl-modules: make; \ perl -p -i -e "\ s/%%%VERSION%%%/${VERSION}/g;\ - " blib/lib/FS.pm + " blib/lib/FS.pm; \ + perl -p -i -e "\ + s'%%%FREESIDE_URL%%%'${FREESIDE_URL}'g;\ + " blib/lib/FS/CGI.pm install-perl-modules: perl-modules [ -L ${PERL_INC_DEV_KLUDGE}/FS ] \ @@ -208,13 +209,13 @@ install-perl-modules: perl-modules cd FS; \ make install UNINST=1 -dev-perl-modules: +dev-perl-modules: perl-modules [ -d ${PERL_INC_DEV_KLUDGE}/FS -a ! -L ${PERL_INC_DEV_KLUDGE}/FS ] \ && mv ${PERL_INC_DEV_KLUDGE}/FS ${PERL_INC_DEV_KLUDGE}/FS.old \ || true rm -rf ${PERL_INC_DEV_KLUDGE}/FS - ln -sf ${FREESIDE_PATH}/FS/FS ${PERL_INC_DEV_KLUDGE}/FS + ln -sf ${FREESIDE_PATH}/FS/blib/lib/FS ${PERL_INC_DEV_KLUDGE}/FS install-init: #[ -e ${INIT_FILE} ] || install -o root -g ${INSTALLGROUP} -m 711 init.d/freeside-init ${INIT_FILE} -- cgit v1.2.1 From 69506a2741c08f1f849c9650bf3650a0f6a319f0 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 1 May 2006 11:43:09 +0000 Subject: fix bug with duplicate tickets showing up on customer view listing when the custom priority field was edited --- FS/FS/TicketSystem/RT_External.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/FS/FS/TicketSystem/RT_External.pm b/FS/FS/TicketSystem/RT_External.pm index d951cc0e7..667f0dc2d 100644 --- a/FS/FS/TicketSystem/RT_External.pm +++ b/FS/FS/TicketSystem/RT_External.pm @@ -109,6 +109,7 @@ sub _from_customer { ON ( tickets.id = ObjectCustomFieldValues.ObjectId )"; $where = " AND content = ? + AND disabled != 1 AND ObjectType = 'RT::Ticket' AND $customfield_sql"; -- cgit v1.2.1 From d3fb20fc820eef7b0d06ea67ed8d72c3c387aa44 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 1 May 2006 11:45:42 +0000 Subject: column reference "disabled" is ambiguous --- FS/FS/TicketSystem/RT_External.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/TicketSystem/RT_External.pm b/FS/FS/TicketSystem/RT_External.pm index 667f0dc2d..ea3448039 100644 --- a/FS/FS/TicketSystem/RT_External.pm +++ b/FS/FS/TicketSystem/RT_External.pm @@ -109,7 +109,7 @@ sub _from_customer { ON ( tickets.id = ObjectCustomFieldValues.ObjectId )"; $where = " AND content = ? - AND disabled != 1 + AND ObjectCustomFieldValues.disabled != 1 AND ObjectType = 'RT::Ticket' AND $customfield_sql"; -- cgit v1.2.1 From 168d603dd94225a837c72d6ae4a5938f1b4d13e4 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 1 May 2006 12:38:06 +0000 Subject: fix some very annoying clucks (warnings with backtraces) when cutoff day isn't found in old packages --- FS/FS/part_pkg/prorate.pm | 2 +- FS/FS/part_pkg/subscription.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FS/FS/part_pkg/prorate.pm b/FS/FS/part_pkg/prorate.pm index d3df01034..f5d447adf 100644 --- a/FS/FS/part_pkg/prorate.pm +++ b/FS/FS/part_pkg/prorate.pm @@ -37,7 +37,7 @@ use FS::part_pkg::flat; sub calc_recur { my($self, $cust_pkg, $sdate ) = @_; - my $cutoff_day = $self->option('cutoff_day') || 1; + my $cutoff_day = $self->option('cutoff_day', 1) || 1; my $mnow = $$sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; my $mend; diff --git a/FS/FS/part_pkg/subscription.pm b/FS/FS/part_pkg/subscription.pm index bfb0582f3..6b5da5cdc 100644 --- a/FS/FS/part_pkg/subscription.pm +++ b/FS/FS/part_pkg/subscription.pm @@ -33,7 +33,7 @@ use FS::part_pkg::flat; sub calc_recur { my($self, $cust_pkg, $sdate ) = @_; - my $cutoff_day = $self->option('cutoff_day') || 1; + my $cutoff_day = $self->option('cutoff_day', 1) || 1; my $mnow = $$sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; -- cgit v1.2.1 From aa9fd844aeeb385cfc1e934eb7b7a08b1353a4ea Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 1 May 2006 13:09:11 +0000 Subject: small fix to make prorate behave on the 1st as it did before --- FS/FS/part_pkg/prorate.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/part_pkg/prorate.pm b/FS/FS/part_pkg/prorate.pm index f5d447adf..e43667993 100644 --- a/FS/FS/part_pkg/prorate.pm +++ b/FS/FS/part_pkg/prorate.pm @@ -43,7 +43,7 @@ sub calc_recur { my $mend; my $mstart; - if ( $mday > $cutoff_day ) { + if ( $mday >= $cutoff_day ) { $mend = timelocal(0,0,0,$cutoff_day, $mon == 11 ? 0 : $mon+1, $year+($mon==11)); $mstart = -- cgit v1.2.1 From f29c752d6f9e813c10295b334eefb924216e34cf Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 2 May 2006 11:59:31 +0000 Subject: add a "pre-report" page to this report/graph as requested by lewis/wtxs, also add 12mo total option --- FS/FS/Report/Table/Monthly.pm | 41 +++++++++++++++++++ httemplate/graph/money_time-graph.cgi | 9 +++++ httemplate/graph/money_time.cgi | 67 +++++++++---------------------- httemplate/graph/report_money_time.html | 71 +++++++++++++++++++++++++++++++++ httemplate/index.html | 2 +- 5 files changed, 140 insertions(+), 50 deletions(-) create mode 100644 httemplate/graph/report_money_time.html diff --git a/FS/FS/Report/Table/Monthly.pm b/FS/FS/Report/Table/Monthly.pm index 9ef108c44..e7f05e5f2 100644 --- a/FS/FS/Report/Table/Monthly.pm +++ b/FS/FS/Report/Table/Monthly.pm @@ -156,6 +156,47 @@ sub credits { ); } +#these should be auto-generated +sub invoiced_12mo { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $speriod = $self->_subtract_11mo($speriod); + $self->invoiced($speriod, $eperiod, $agentnum); +} + +sub netsales_12mo { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $speriod = $self->_subtract_11mo($speriod); + $self->netsales($speriod, $eperiod, $agentnum); +} + +sub receipts_12mo { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $speriod = $self->_subtract_11mo($speriod); + $self->receipts($speriod, $eperiod, $agentnum); +} + +sub payments_12mo { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $speriod = $self->_subtract_11mo($speriod); + $self->payments($speriod, $eperiod, $agentnum); +} + +sub credits_12mo { + my( $self, $speriod, $eperiod, $agentnum ) = @_; + $speriod = $self->_subtract_11mo($speriod); + $self->credits($speriod, $eperiod, $agentnum); +} + +#not being too bad with the false laziness +use Time::Local qw(timelocal); +sub _subtract_11mo { + my($self, $time) = @_; + my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($time) )[0,1,2,3,4,5]; + $mon -= 11; + if ( $mon < 0 ) { $mon+=12; $year--; } + timelocal($sec,$min,$hour,$mday,$mon,$year); +} + # NEEDS TO BE AGENTNUM-capable sub canceled { #active my( $self, $speriod, $eperiod, $agentnum ) = @_; diff --git a/httemplate/graph/money_time-graph.cgi b/httemplate/graph/money_time-graph.cgi index fc8207a81..637a3bf94 100755 --- a/httemplate/graph/money_time-graph.cgi +++ b/httemplate/graph/money_time-graph.cgi @@ -22,6 +22,10 @@ if ( $cgi->param('agentnum') =~ /^(\d*)$/ ) { #my %data; my @items = qw( invoiced netsales credits payments receipts ); +if ( $cgi->param('12mo') == 1 ) { + @items = map $_.'_12mo', @items; +} + my %label = ( 'invoiced' => 'Gross Sales (invoiced)', 'netsales' => 'Net Sales (invoiced - applied credits)', @@ -29,6 +33,9 @@ my %label = ( 'payments' => 'Gross Receipts (payments)', 'receipts' => 'Net Receipts/Cashflow (payments - refunds)', ); +$label{$_.'_12mo'} = $label{$_}. " (previous 12 months)" + foreach keys %label; + my %color = ( 'invoiced' => [ 153, 153, 255 ], #light blue 'netsales' => [ 0, 0, 204 ], #blue @@ -36,6 +43,8 @@ my %color = ( 'payments' => [ 153, 204, 153 ], #light green 'receipts' => [ 0, 204, 0 ], #green ); +$color{$_.'_12mo'} = $color{$_} + foreach keys %color; my $report = new FS::Report::Table::Monthly ( 'items' => \@items, diff --git a/httemplate/graph/money_time.cgi b/httemplate/graph/money_time.cgi index bc789cb7e..73f9d23a2 100644 --- a/httemplate/graph/money_time.cgi +++ b/httemplate/graph/money_time.cgi @@ -1,16 +1,15 @@ - <% -#my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); -my ($curmon,$curyear) = (localtime(time))[4,5]; +# #my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); +# my ($curmon,$curyear) = (localtime(time))[4,5]; #find first month -my $syear = $cgi->param('syear') || 1899+$curyear; -my $smonth = $cgi->param('smonth') || $curmon+1; +my $syear = $cgi->param('syear'); # || 1899+$curyear; +my $smonth = $cgi->param('smonth'); # || $curmon+1; #find last month -my $eyear = $cgi->param('eyear') || 1900+$curyear; -my $emonth = $cgi->param('emonth') || $curmon+1; +my $eyear = $cgi->param('eyear'); # || 1900+$curyear; +my $emonth = $cgi->param('emonth'); # || $curmon+1; #XXX or virtual my( $agentnum, $agent ) = ('', ''); @@ -20,10 +19,8 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { die "agentnum $agentnum not found!" unless $agent; } my $agentname = $agent ? $agent->agent.' ' : ''; -warn $agentname; %> - <%= include('/elements/header.html', $agentname. 'Sales, Credits and Receipts Summary' ) @@ -36,6 +33,10 @@ warn $agentname; <% my @items = qw( invoiced netsales credits payments receipts ); +if ( $cgi->param('12mo') == 1 ) { + @items = map $_.'_12mo', @items; +} + my %label = ( 'invoiced' => 'Gross Sales', 'netsales' => 'Net Sales', @@ -43,6 +44,9 @@ my %label = ( 'payments' => 'Gross Receipts', 'receipts' => 'Net Receipts', ); +$label{$_.'_12mo'} = $label{$_}. " (previous 12 months)" + foreach keys %label; + my %color = ( 'invoiced' => '9999ff', #light blue 'netsales' => '0000cc', #blue @@ -50,11 +54,15 @@ my %color = ( 'payments' => '99cc99', #light green 'receipts' => '00cc00', #green ); +$color{$_.'_12mo'} = $color{$_} + foreach keys %color; + my %link = ( 'invoiced' => "${p}search/cust_bill.html?agentnum=$agentnum;", 'credits' => "${p}search/cust_credit.html?agentnum=$agentnum;", 'payments' => "${p}search/cust_pay.cgi?magic=_date;agentnum=$agentnum;", ); +# XXX link 12mo? my $report = new FS::Report::Table::Monthly ( 'items' => \@items, @@ -102,43 +110,4 @@ my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); <% } %> -
-
- -From - - to - - -for agent: <%= include('/elements/select-agent.html', $agentnum) %> - - -
- - +<%= include('/elements/footer.html') %> diff --git a/httemplate/graph/report_money_time.html b/httemplate/graph/report_money_time.html new file mode 100644 index 000000000..6c7e42716 --- /dev/null +++ b/httemplate/graph/report_money_time.html @@ -0,0 +1,71 @@ +<% + +#my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); +my ($curmon,$curyear) = (localtime(time))[4,5]; + +#find first month +my $syear = 1899+$curyear; +my $smonth = $curmon+1; + +#want 12 month by default, not 13 +$smonth++; +if ( $smonth > 12 ) { $smonth-=12; $syear++ } + +#find last month +my $eyear = 1900+$curyear; +my $emonth = $curmon+1; + +my @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); + +%> +<%= include('/elements/header.html', 'Sales, Credits and Receipts Summary' ) %> + +
+ + + +From: + +
+ +To: + +
+ +For agent: <%= include('/elements/select-agent.html' ) %> +
+ + Show 12 month totals instead of monthly values. +
+ + +
+ +<%= include('/elements/footer.html') %> + diff --git a/httemplate/index.html b/httemplate/index.html index 6cd667d8a..4704ebe33 100644 --- a/httemplate/index.html +++ b/httemplate/index.html @@ -97,7 +97,7 @@ Payment report (by type and/or date range)

Credit report (by employee and/or date range) -

Sales, Credits and Receipts Summary +

Sales, Credits and Receipts Summary

Accounts Receivable Aging Summary

Prepaid Income (Unearned Revenue) Report

Sales Tax Liability Report -- cgit v1.2.1 From ecc090b960f14a68a51bc966113ab4e0feeb38cf Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 2 May 2006 13:29:57 +0000 Subject: need to install the new Schema.pm before you can autogenerate off it --- SCHEMA_CHANGE | 1 + 1 file changed, 1 insertion(+) diff --git a/SCHEMA_CHANGE b/SCHEMA_CHANGE index 26ebeea76..db39c28d9 100644 --- a/SCHEMA_CHANGE +++ b/SCHEMA_CHANGE @@ -6,6 +6,7 @@ if the changes are something other than table and/or column additions: - README.1.5.X for new tables: +- make sure the new tables are added to FS/FS/Schema.pm and run make install-perl-modules - run bin/generate-table-module tablename - edit the resulting FS/FS/table.pm -- cgit v1.2.1 From 0e2bb254c0e5018b6b2a7766962319cd4137683f Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 2 May 2006 15:03:00 +0000 Subject: add an agent pre-selection page to receivables report --- httemplate/index.html | 2 +- httemplate/search/report_receivables.html | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100755 httemplate/search/report_receivables.html diff --git a/httemplate/index.html b/httemplate/index.html index 4704ebe33..1a9293ecd 100644 --- a/httemplate/index.html +++ b/httemplate/index.html @@ -98,7 +98,7 @@ Payment report (by type and/or date range)

Credit report (by employee and/or date range)

Sales, Credits and Receipts Summary -

Accounts Receivable Aging Summary +

Accounts Receivable Aging Summary

Prepaid Income (Unearned Revenue) Report

Sales Tax Liability Report

diff --git a/httemplate/search/report_receivables.html b/httemplate/search/report_receivables.html new file mode 100755 index 000000000..fc5174116 --- /dev/null +++ b/httemplate/search/report_receivables.html @@ -0,0 +1,16 @@ +<%= include('/elements/header.html', 'Accounts Receivable Aging Summary' ) %> + +
+ + + + <%= include( '/elements/tr-select-agent.html' ) %> + +
+ +
+
+ + + + -- cgit v1.2.1 From 8e889ba1218905519bcd912ff6b84d0041860317 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 2 May 2006 15:23:03 +0000 Subject: yours! --- FS/FS/Pony.pm | 23 +++++++++++++++++++++++ FS/MANIFEST | 15 +++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 FS/FS/Pony.pm diff --git a/FS/FS/Pony.pm b/FS/FS/Pony.pm new file mode 100644 index 000000000..c37dd7855 --- /dev/null +++ b/FS/FS/Pony.pm @@ -0,0 +1,23 @@ +package FS::Pony; + +=head1 NAME + +FS::Pony - A pony + +=head1 SYNOPSYS + +use FS::Pony; # <-- yours! + +=head1 DESCRIPTION + +We told you it came with a pony. + +=head1 BUGS + +=head1 SEE ALSO + +http://420.am/~ivan/nopony.jpg + +=cut + +1; diff --git a/FS/MANIFEST b/FS/MANIFEST index a70be40e4..c309251ff 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -25,6 +25,7 @@ bin/freeside-sqlradius-radacctd bin/freeside-sqlradius-reset bin/freeside-sqlradius-seconds FS.pm +FS/AccessRight.pm FS/CGI.pm FS/InitHandler.pm FS/ClientAPI.pm @@ -46,6 +47,7 @@ FS/SearchCache.pm FS/UI/Web.pm FS/UID.pm FS/Msgcat.pm +FS/Pony.pm FS/acct_snarf.pm FS/agent.pm FS/agent_type.pm @@ -165,6 +167,7 @@ FS/clientapi_session.pm FS/clientapi_session_field.pm t/agent.t t/agent_type.t +t/AccessRight.t t/CGI.t t/InitHandler.t t/ClientAPI.t @@ -324,3 +327,15 @@ FS/inventory_item.pm t/inventory_item.t FS/cdr_upstream_rate.pm t/cdr_upstream_rate.t +FS/access_user.pm +t/access_user.t +FS/access_user_pref.pm +t/access_user_pref.t +FS/access_group.pm +t/access_group.t +FS/access_usergroup.pm +t/access_usergroup.t +FS/access_groupagent.pm +t/access_groupagent.t +FS/access_right.pm +t/access_right.t -- cgit v1.2.1 From 29644d5921c99520965b884b25800ed084891e94 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 3 May 2006 09:47:31 +0000 Subject: pg 8.1 fix from Chris Cappuccio --- 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 d675346f0..1dd0fee86 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -110,7 +110,7 @@ END $owed_cols =~ s/cust_bill\.custnum/cust_bill.custnum AND cust_main.agentnum = '$agentnum'/g; } - my $total_sql = "select $owed_cols"; + my $total_sql = "select $owed_cols from cust_main"; my $total_sth = dbh->prepare($total_sql) or die dbh->errstr; $total_sth->execute or die $total_sth->errstr; my $row = $total_sth->fetchrow_hashref(); -- cgit v1.2.1 From e65c6a26ca778166aec2b2d1dd3012ab84fa611a Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 7 May 2006 20:27:21 +0000 Subject: first pass at sales reports per agent and package class --- README.1.7.0 | 1 + htetc/handler.pl | 2 + httemplate/docs/upgrade10.html | 2 +- httemplate/elements/select-month_year.html | 19 +++- httemplate/elements/select-pkg_class.html | 7 +- httemplate/elements/select-table.html | 11 +- httemplate/elements/tr-select-from_to.html | 51 +++++++++ httemplate/elements/tr-select-pkg_class.html | 15 +-- httemplate/graph/cust_bill_pkg-graph.cgi | 84 ++++++++++++++ httemplate/graph/cust_bill_pkg.cgi | 104 ++++++++++++++++++ httemplate/graph/elements/monthly.html | 157 +++++++++++++++++++++++++++ httemplate/graph/money_time-graph.cgi | 84 -------------- httemplate/graph/money_time.cgi | 97 ++++++----------- httemplate/graph/report_cust_bill_pkg.html | 29 +++++ httemplate/graph/report_money_time.html | 58 ++-------- 15 files changed, 503 insertions(+), 218 deletions(-) create mode 100644 httemplate/elements/tr-select-from_to.html create mode 100755 httemplate/graph/cust_bill_pkg-graph.cgi create mode 100644 httemplate/graph/cust_bill_pkg.cgi create mode 100644 httemplate/graph/elements/monthly.html delete mode 100755 httemplate/graph/money_time-graph.cgi create mode 100644 httemplate/graph/report_cust_bill_pkg.html diff --git a/README.1.7.0 b/README.1.7.0 index 62d6232c1..30f7ec7de 100644 --- a/README.1.7.0 +++ b/README.1.7.0 @@ -1,5 +1,6 @@ install DBIx::DBSchema 0.31 (or later) +install Color::Scheme make install-perl-modules run "freeside-upgrade username" to uprade your database schema diff --git a/htetc/handler.pl b/htetc/handler.pl index dcbe72733..e8cd3333b 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -113,6 +113,8 @@ sub handler use Business::CreditCard; use String::Approx qw(amatch); use Chart::LinesPoints; + use Chart::Mountain; + use Color::Scheme; use HTML::Widgets::SelectLayers 0.05; use Locale::Country; use FS; diff --git a/httemplate/docs/upgrade10.html b/httemplate/docs/upgrade10.html index 8d90ab7a2..7cd1d8e50 100644 --- a/httemplate/docs/upgrade10.html +++ b/httemplate/docs/upgrade10.html @@ -31,7 +31,7 @@ Perl or Linux.
  • install NetAddr::IP, Chart::Base, Locale::SubCountry, Text::CSV_XS, Spreadsheet::WriteExcel, IO-stringy (IO::Scalar), Frontier::RPC (Frontier::RPC2), MIME::Entity (MIME-tools), IPC::Run3, Net::Whois::Raw, -JSON and Term::ReadKey +JSON, Term::ReadKey and Color::Scheme
  • Apply the following changes to your database: diff --git a/httemplate/elements/select-month_year.html b/httemplate/elements/select-month_year.html index a0ea74ddd..2866960bd 100644 --- a/httemplate/elements/select-month_year.html +++ b/httemplate/elements/select-month_year.html @@ -5,13 +5,22 @@ my $prefix = $opt{'prefix'} || ''; my $disabled = $opt{'disabled'} || ''; my $empty = $opt{'empty_option'} || ''; + my $start_year = $opt{'start_year'}; + my $end_year = $opt{'end_year'} || '2037'; + + my @mon; + if ( $opt{'show_month_abbr'} ) { + @mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); + } else { + @mon = ( 1 .. 12 ); + } + my $date = $opt{'selected_date'} || ''; $date = '' if $date eq '-'; #$date ||= '01-2000' unless $empty; - my $start_year = $opt{'start_year'}; - my $end_year = $opt{'end_year'} || '2037'; - my( $mon, $year ) = (0, 0); + my $mon = $opt{'selected_mon'} || 0; + my $year = $opt{'selected_year'} || 0; if ( $date ) { if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format ( $mon, $year ) = ( $2, $1 ); @@ -34,8 +43,8 @@ <%= $empty ? '
  • All invoice events for a date range
  • Invoice event errors for a date range (failed credit cards, processor or printer problems, etc.) - Payment report (by type and/or date range) + Sales, Credits and Receipts Summary +

    Sales report (by agent, package class and/or date range)

    Credit report (by employee and/or date range) -

    Sales, Credits and Receipts Summary +

    Payment report (by type and/or date range)

    Accounts Receivable Aging Summary

    Prepaid Income (Unearned Revenue) Report

    Sales Tax Liability Report diff --git a/httemplate/search/cust_bill_pkg.cgi b/httemplate/search/cust_bill_pkg.cgi index cc0f97536..4779071a4 100644 --- a/httemplate/search/cust_bill_pkg.cgi +++ b/httemplate/search/cust_bill_pkg.cgi @@ -12,15 +12,23 @@ my $join_pkg = " LEFT JOIN part_pkg USING ( pkgpart ) "; -my $where = " - WHERE _date >= $beginning AND _date <= $ending - AND payby != 'COMP' -"; +my $where = " WHERE _date >= $beginning AND _date <= $ending "; + +$where .= " AND payby != 'COMP' " + unless $cgi->param('include_comp_cust'); if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { $where .= " AND agentnum = $1 "; } +if ( $cgi->param('classnum') =~ /^(\d+)$/ ) { + if ( $1 == 0 ) { + $where .= " AND classnum IS NULL "; + } else { + $where .= " AND classnum = $1 "; + } +} + if ( $cgi->param('out') ) { $where .= " -- cgit v1.2.1 From c363307c1af959dce2ab4821ff5dfa697e3f0e19 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 8 May 2006 11:28:52 +0000 Subject: add config switch to base tax off shipping address if present --- FS/FS/Conf.pm | 7 +++ FS/FS/cust_main.pm | 39 +++++++------- httemplate/search/report_tax.cgi | 107 ++++++++++++++++++++++++++++++++------- 3 files changed, 115 insertions(+), 38 deletions(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 21e752867..e7b9fa556 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1702,6 +1702,13 @@ httemplate/docs/config.html 'type' => 'checkbox', }, + { + 'key' => 'tax-ship_address', + 'section' => 'billing', + 'description' => 'By default, tax calculations are done based on the billing address. Enable this switch to calculate tax based on the shipping address instead. Note: Tax reports can take a long time when enabled.', + 'type' => 'checkbox', + }, + ); 1; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 298c01dee..65ccb343b 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1805,29 +1805,26 @@ sub bill { unless ( $self->tax =~ /Y/i || $self->payby eq 'COMP' ) { - my @taxes = qsearch( 'cust_main_county', { - 'state' => $self->state, - 'county' => $self->county, - 'country' => $self->country, - 'taxclass' => $part_pkg->taxclass, - } ); + my $prefix = + ( $conf->exists('tax-ship_address') && length($self->ship_last) ) + ? 'ship_' + : ''; + my %taxhash = map { $_ => $self->get("$prefix$_") } + qw( state county country ); + + $taxhash{'taxclass'} = $part_pkg->taxclass; + + my @taxes = qsearch( 'cust_main_county', \%taxhash ); + unless ( @taxes ) { - @taxes = qsearch( 'cust_main_county', { - 'state' => $self->state, - 'county' => $self->county, - 'country' => $self->country, - 'taxclass' => '', - } ); + $taxhash{'taxclass'} = ''; + @taxes = qsearch( 'cust_main_county', \%taxhash ); } #one more try at a whole-country tax rate unless ( @taxes ) { - @taxes = qsearch( 'cust_main_county', { - 'state' => '', - 'county' => '', - 'country' => $self->country, - 'taxclass' => '', - } ); + $taxhash{$_} = '' foreach qw( state county ); + @taxes = qsearch( 'cust_main_county', \%taxhash ); } # maybe eliminate this entirely, along with all the 0% records @@ -1835,8 +1832,10 @@ sub bill { $dbh->rollback if $oldAutoCommit; return "fatal: can't find tax rate for state/county/country/taxclass ". - join('/', ( map $self->$_(), qw(state county country) ), - $part_pkg->taxclass ). "\n"; + join('/', ( map $self->get("$prefix$_"), + qw(state county country) + ), + $part_pkg->taxclass ). "\n"; } foreach my $tax ( @taxes ) { diff --git a/httemplate/search/report_tax.cgi b/httemplate/search/report_tax.cgi index 0f33c4676..1b6f40b8a 100755 --- a/httemplate/search/report_tax.cgi +++ b/httemplate/search/report_tax.cgi @@ -19,14 +19,38 @@ my $join_pkg = " LEFT JOIN cust_pkg USING ( pkgnum ) LEFT JOIN part_pkg USING ( pkgpart ) "; -my $where = " - WHERE _date >= $beginning AND _date <= $ending - AND ( county = ? OR ? = '' ) - AND ( state = ? OR ? = '' ) - AND country = ? -"; -# AND payby != 'COMP' + +my $where = "WHERE _date >= $beginning AND _date <= $ending "; my @base_param = qw( county county state state country ); +if ( $conf->exists('tax-ship_address') ) { + + $where .= " + AND ( ( ( ship_last IS NULL OR ship_last = '' ) + AND ( county = ? OR ? = '' ) + AND ( state = ? OR ? = '' ) + AND country = ? + ) + OR ( ship_last IS NOT NULL AND ship_last != '' + AND ( ship_county = ? OR ? = '' ) + AND ( ship_state = ? OR ? = '' ) + AND ship_country = ? + ) + ) + "; + # AND payby != 'COMP' + + push @base_param, @base_param; + +} else { + + $where .= " + AND ( county = ? OR ? = '' ) + AND ( state = ? OR ? = '' ) + AND country = ? + "; + # AND payby != 'COMP' + +} my $agentname = ''; if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { @@ -38,16 +62,63 @@ if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { my $gotcust = " WHERE 0 < ( SELECT COUNT(*) FROM cust_main - WHERE ( cust_main.county = cust_main_county.county - OR cust_main_county.county = '' - OR cust_main_county.county IS NULL ) - AND ( cust_main.state = cust_main_county.state - OR cust_main_county.state = '' - OR cust_main_county.state IS NULL ) - AND ( cust_main.country = cust_main_county.country ) - LIMIT 1 - ) "; +if ( $conf->exists('tax-ship_address') ) { + + $gotcust .= " + WHERE + + ( cust_main_county.country = cust_main.country + OR cust_main_county.country = cust_main.ship_country + ) + + AND + + ( + + ( ( ship_last IS NULL OR ship_last = '' ) + AND ( cust_main_county.country = cust_main.country ) + AND ( cust_main_county.state = cust_main.state + OR cust_main_county.state = '' + OR cust_main_county.state IS NULL ) + AND ( cust_main_county.county = cust_main.county + OR cust_main_county.county = '' + OR cust_main_county.county IS NULL ) + ) + + OR + + ( ship_last IS NOT NULL AND ship_last != '' + AND ( cust_main_county.country = cust_main.ship_country ) + AND ( cust_main_county.state = cust_main.ship_state + OR cust_main_county.state = '' + OR cust_main_county.state IS NULL ) + AND ( cust_main_county.county = cust_main.ship_county + OR cust_main_county.county = '' + OR cust_main_county.county IS NULL ) + ) + + ) + + LIMIT 1 + ) + "; + +} else { + + $gotcust .= " + WHERE ( cust_main.county = cust_main_county.county + OR cust_main_county.county = '' + OR cust_main_county.county IS NULL ) + AND ( cust_main.state = cust_main_county.state + OR cust_main_county.state = '' + OR cust_main_county.state IS NULL ) + AND ( cust_main.country = cust_main_county.country ) + LIMIT 1 + ) + "; + +} my($total, $tot_taxable, $owed, $tax) = ( 0, 0, 0, 0, 0 ); my( $exempt_cust, $exempt_pkg, $exempt_monthly ) = ( 0, 0 ); @@ -190,8 +261,8 @@ foreach my $r ( my $label = getlabel($r); - my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' "; - my @param = @base_param; + #my $fromwhere = $join_pkg. $where. " AND payby != 'COMP' "; + #my @param = @base_param; #match itemdesc if necessary! my $named_tax = -- cgit v1.2.1 From 82653831d2e5a02f0f074b3c3f11c63cc67d3944 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 8 May 2006 11:48:32 +0000 Subject: suggest "make clean" on upgrade - something is not quite right with perl Makefile hoohaw --- README.1.5.8 | 3 +++ README.1.7.0 | 3 +++ 2 files changed, 6 insertions(+) diff --git a/README.1.5.8 b/README.1.5.8 index cf414545e..6ad19b063 100644 --- a/README.1.5.8 +++ b/README.1.5.8 @@ -54,3 +54,6 @@ Installs w/integrated RT: make install-docs (or "make deploy" if you've got everything setup in the Makefile) +(errors? try "make clean" then "make install-perl-modules", then + "make install-docs" or "make deploy" again) + diff --git a/README.1.7.0 b/README.1.7.0 index 30f7ec7de..992a988c8 100644 --- a/README.1.7.0 +++ b/README.1.7.0 @@ -22,3 +22,6 @@ CREATE INDEX cust_main17 on cust_main ( ship_zip ); make install-docs (or "make deploy" if you've got everything setup in the Makefile) +(errors? try "make clean" then "make install-perl-modules", then + "make install-docs" or "make deploy" again) + -- cgit v1.2.1 From 3059dc3cfe6fa7280c40f64e598fc05f37ffa96f Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 12 May 2006 13:57:23 +0000 Subject: Pg 8.1 fix was incorrect and broke things, this should actually work --- httemplate/search/report_receivables.cgi | 113 +++++++++++++++---------------- 1 file changed, 53 insertions(+), 60 deletions(-) diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index 1dd0fee86..ac94e1a5c 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -1,6 +1,38 @@ <% - my $charged = < extract(epoch from now()) - ". + ($end * 86400) + if $end; + + #handle 'cust' option + + push @where, "cust_main.custnum = cust_bill.custnum" + if $opt{'cust'}; + + #handle 'agentnum' option + my $join = ''; + if ( $opt{'agentnum'} ) { + $join = 'LEFT JOIN cust_main USING ( custnum )'; + push @where, "agentnum = '$opt{'agentnum'}'"; + } + + my $where = scalar(@where) ? 'WHERE '.join(' AND ', @where) : ''; + + my $as = $opt{'noas'} ? '' : "as owed_${start}_$end"; + + my $charged = < extract(epoch from now())-2592000 - and cust_main.custnum = cust_bill.custnum - ) - ,0 - ) as owed_0_30, - - coalesce( - ( 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 - ) - ,0 - ) as owed_30_60, - - coalesce( - ( 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 - ) - ,0 - ) as owed_60_90, + "coalesce( ( select $charged from cust_bill $join $where ) ,0 ) $as"; + + } - coalesce( - ( select $charged from cust_bill - where cust_bill._date <= extract(epoch from now())-7776000 - and cust_main.custnum = cust_bill.custnum - ) - ,0 - ) as owed_90_pl, + my @ranges = ( + [ 0, 30 ], + [ 30, 60 ], + [ 60, 90 ], + [ 90, 0 ], + [ 0, 0 ], + ); - coalesce( - ( select $charged from cust_bill - where cust_main.custnum = cust_bill.custnum - ) - ,0 - ) as owed_total -END + my $owed_cols = join(',', map owed( @$_, 'cust'=>1 ), @ranges ); my $recurring = <1, 'noas'=>1). " > 0"; my $agentnum = ''; if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { @@ -106,13 +101,11 @@ END 'extra_sql' => "$where order by coalesce(lower(company), ''), lower(last)", }; - if ( $agentnum ) { - $owed_cols =~ - s/cust_bill\.custnum/cust_bill.custnum AND cust_main.agentnum = '$agentnum'/g; - } - my $total_sql = "select $owed_cols from cust_main"; + my $total_sql = "select ". + join(',', map owed( @$_, 'agentnum'=>$agentnum ), @ranges ); + my $total_sth = dbh->prepare($total_sql) or die dbh->errstr; - $total_sth->execute or die $total_sth->errstr; + $total_sth->execute or die "error executing $total_sql: ". $total_sth->errstr; my $row = $total_sth->fetchrow_hashref(); my $conf = new FS::Conf; @@ -154,9 +147,9 @@ END sprintf( $money_char.'%.2f', $row->{'owed_60_90'} ), sprintf( $money_char.'%.2f', - $row->{'owed_90_pl'} ), + $row->{'owed_90_0'} ), sprintf( ''. $money_char.'%.2f'. '', - $row->{'owed_total'} ), + $row->{'owed_0_0'} ), ], 'fields' => [ \&FS::UI::Web::cust_fields, @@ -182,9 +175,9 @@ END sub { sprintf( $money_char.'%.2f', shift->get('owed_60_90') ) }, sub { sprintf( $money_char.'%.2f', - shift->get('owed_90_pl') ) }, + shift->get('owed_90_0') ) }, sub { sprintf( $money_char.'%.2f', - shift->get('owed_total') ) }, + shift->get('owed_0_0') ) }, ], 'links' => [ ( map $clink, FS::UI::Web::cust_header() ), -- cgit v1.2.1 From 20d1889a5fcecf66b720500326726ab266c7b712 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 13 May 2006 15:31:58 +0000 Subject: adding new images --- httemplate/images/32clear.gif | Bin 0 -> 815 bytes httemplate/images/arrow.down.png | Bin 0 -> 170 bytes httemplate/images/arrow.right.black.png | Bin 0 -> 160 bytes httemplate/images/arrow.right.png | Bin 0 -> 160 bytes httemplate/images/black-gradient.png | Bin 0 -> 397 bytes httemplate/images/black-gray-corner.png | Bin 0 -> 460 bytes httemplate/images/black-gray-gradient.png | Bin 0 -> 384 bytes httemplate/images/black-gray-side.png | Bin 0 -> 198 bytes httemplate/images/black-gray-top.png | Bin 0 -> 203 bytes 9 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 httemplate/images/32clear.gif create mode 100644 httemplate/images/arrow.down.png create mode 100644 httemplate/images/arrow.right.black.png create mode 100644 httemplate/images/arrow.right.png create mode 100644 httemplate/images/black-gradient.png create mode 100644 httemplate/images/black-gray-corner.png create mode 100644 httemplate/images/black-gray-gradient.png create mode 100644 httemplate/images/black-gray-side.png create mode 100644 httemplate/images/black-gray-top.png diff --git a/httemplate/images/32clear.gif b/httemplate/images/32clear.gif new file mode 100644 index 000000000..5fdcea204 Binary files /dev/null and b/httemplate/images/32clear.gif differ diff --git a/httemplate/images/arrow.down.png b/httemplate/images/arrow.down.png new file mode 100644 index 000000000..675d84bde Binary files /dev/null and b/httemplate/images/arrow.down.png differ diff --git a/httemplate/images/arrow.right.black.png b/httemplate/images/arrow.right.black.png new file mode 100644 index 000000000..933c25894 Binary files /dev/null and b/httemplate/images/arrow.right.black.png differ diff --git a/httemplate/images/arrow.right.png b/httemplate/images/arrow.right.png new file mode 100644 index 000000000..60bcb76ab Binary files /dev/null and b/httemplate/images/arrow.right.png differ diff --git a/httemplate/images/black-gradient.png b/httemplate/images/black-gradient.png new file mode 100644 index 000000000..225732d16 Binary files /dev/null and b/httemplate/images/black-gradient.png differ diff --git a/httemplate/images/black-gray-corner.png b/httemplate/images/black-gray-corner.png new file mode 100644 index 000000000..17954cdd7 Binary files /dev/null and b/httemplate/images/black-gray-corner.png differ diff --git a/httemplate/images/black-gray-gradient.png b/httemplate/images/black-gray-gradient.png new file mode 100644 index 000000000..f5c318fe7 Binary files /dev/null and b/httemplate/images/black-gray-gradient.png differ diff --git a/httemplate/images/black-gray-side.png b/httemplate/images/black-gray-side.png new file mode 100644 index 000000000..f7a98a43d Binary files /dev/null and b/httemplate/images/black-gray-side.png differ diff --git a/httemplate/images/black-gray-top.png b/httemplate/images/black-gray-top.png new file mode 100644 index 000000000..ed0707573 Binary files /dev/null and b/httemplate/images/black-gray-top.png differ -- cgit v1.2.1 From c46235292c6bf929615ac28fc48c1d5609ce4590 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 13 May 2006 18:34:39 +0000 Subject: yay for cheating --- httemplate/images/background-cheat.png | Bin 0 -> 338 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 httemplate/images/background-cheat.png diff --git a/httemplate/images/background-cheat.png b/httemplate/images/background-cheat.png new file mode 100644 index 000000000..ad332f675 Binary files /dev/null and b/httemplate/images/background-cheat.png differ -- cgit v1.2.1 From 2c757d7db4cb6a7b9655de13206fcc84fb7ce61f Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 14 May 2006 16:47:31 +0000 Subject: first part of ACL and re-skinning work and some other small stuff --- CREDITS | 6 + Changes.1.7.0 | 39 ++ FS/FS/AccessRight.pm | 77 +++ FS/FS/CGI.pm | 5 +- FS/FS/Schema.pm | 87 ++- FS/FS/UI/Web.pm | 14 +- FS/FS/access_group.pm | 121 +++++ FS/FS/access_groupagent.pm | 124 +++++ FS/FS/access_right.pm | 127 +++++ FS/FS/access_user.pm | 167 ++++++ FS/FS/access_user_pref.pm | 127 +++++ FS/FS/access_usergroup.pm | 144 +++++ FS/FS/agent_type.pm | 3 +- FS/FS/cust_bill.pm | 5 + FS/FS/m2m_Common.pm | 110 ++++ FS/FS/part_pkg/billoneday.pm | 48 -- FS/FS/payby.pm | 3 +- FS/FS/svc_domain.pm | 6 +- FS/MANIFEST | 1 + FS/bin/freeside-addoutsourceuser | 4 +- FS/t/AccessRight.t | 5 + FS/t/access_group.t | 5 + FS/t/access_groupagent.t | 5 + FS/t/access_right.t | 5 + FS/t/access_user.t | 5 + FS/t/access_user_pref.t | 5 + FS/t/access_usergroup.t | 5 + htetc/handler.pl | 2 + httemplate/autohandler | 10 +- httemplate/browse/access_group.html | 33 ++ httemplate/browse/access_user.html | 63 +++ httemplate/browse/agent_type.cgi | 108 ++-- httemplate/browse/cust_main_county.cgi | 185 +++---- httemplate/browse/msgcat.cgi | 20 +- httemplate/browse/part_pkg.cgi | 14 +- httemplate/edit/access_group.html | 10 + httemplate/edit/access_user.html | 37 ++ httemplate/edit/agent_type.cgi | 52 +- httemplate/edit/cust_bill_pay.cgi | 87 ++- httemplate/edit/cust_credit.cgi | 71 ++- httemplate/edit/cust_credit_bill.cgi | 90 ++-- httemplate/edit/cust_main.cgi | 105 ++-- httemplate/edit/cust_pkg.cgi | 136 +++-- httemplate/edit/elements/edit.html | 36 +- httemplate/edit/part_referral.cgi | 36 +- httemplate/edit/part_virtual_field.cgi | 19 +- httemplate/edit/process/access_group.html | 5 + httemplate/edit/process/access_user.html | 8 + httemplate/edit/process/agent_type.cgi | 39 +- httemplate/edit/process/cust_bill_pay.cgi | 16 +- httemplate/edit/process/cust_credit.cgi | 17 +- httemplate/edit/process/cust_credit_bill.cgi | 16 +- httemplate/edit/process/elements/process.html | 19 +- httemplate/edit/svc_domain.cgi | 55 +- httemplate/elements/checkboxes-table.html | 110 ++++ httemplate/elements/cssexpr.js | 66 +++ httemplate/elements/footer.html | 3 + httemplate/elements/header.html | 289 +++++++++- httemplate/elements/menubar.html | 1 + httemplate/elements/select-access_group.html | 15 + httemplate/elements/tr-select-access_group.html | 22 + httemplate/elements/xmenu.css | 185 +++++++ httemplate/elements/xmenu.js | 668 ++++++++++++++++++++++++ httemplate/index.html | 12 +- httemplate/misc/batch-cust_pay.html | 2 - httemplate/misc/payment.cgi | 38 +- httemplate/search/cust_bill.cgi | 165 ------ httemplate/search/cust_main-otaker.cgi | 47 +- httemplate/search/cust_main-payinfo.html | 20 - httemplate/search/cust_main-quickpay.html | 44 -- httemplate/search/cust_main.cgi | 50 +- httemplate/search/cust_pay.html | 18 - httemplate/search/cust_pkg_report.cgi | 39 +- httemplate/search/report_cust_bill.html | 54 +- httemplate/search/report_cust_credit.html | 62 +-- httemplate/search/report_cust_pay.html | 81 +-- httemplate/search/report_prepaid_income.html | 26 +- httemplate/search/report_tax.html | 73 ++- httemplate/search/sqlradius.html | 52 +- httemplate/search/svc_acct.html | 19 - httemplate/search/svc_domain.cgi | 2 +- httemplate/search/svc_domain.html | 19 - httemplate/search/svc_external.cgi | 21 +- httemplate/view/cust_main/packages.html | 4 +- httemplate/view/cust_main/payment_history.html | 26 +- 85 files changed, 3577 insertions(+), 1198 deletions(-) create mode 100644 Changes.1.7.0 create mode 100644 FS/FS/AccessRight.pm create mode 100644 FS/FS/access_group.pm create mode 100644 FS/FS/access_groupagent.pm create mode 100644 FS/FS/access_right.pm create mode 100644 FS/FS/access_user.pm create mode 100644 FS/FS/access_user_pref.pm create mode 100644 FS/FS/access_usergroup.pm create mode 100644 FS/FS/m2m_Common.pm delete mode 100644 FS/FS/part_pkg/billoneday.pm create mode 100644 FS/t/AccessRight.t create mode 100644 FS/t/access_group.t create mode 100644 FS/t/access_groupagent.t create mode 100644 FS/t/access_right.t create mode 100644 FS/t/access_user.t create mode 100644 FS/t/access_user_pref.t create mode 100644 FS/t/access_usergroup.t create mode 100644 httemplate/browse/access_group.html create mode 100644 httemplate/browse/access_user.html create mode 100644 httemplate/edit/access_group.html create mode 100644 httemplate/edit/access_user.html create mode 100644 httemplate/edit/process/access_group.html create mode 100644 httemplate/edit/process/access_user.html create mode 100644 httemplate/elements/checkboxes-table.html create mode 100644 httemplate/elements/cssexpr.js create mode 100644 httemplate/elements/select-access_group.html create mode 100644 httemplate/elements/tr-select-access_group.html create mode 100644 httemplate/elements/xmenu.css create mode 100644 httemplate/elements/xmenu.js delete mode 100755 httemplate/search/cust_bill.cgi delete mode 100755 httemplate/search/cust_main-payinfo.html delete mode 100755 httemplate/search/cust_main-quickpay.html delete mode 100755 httemplate/search/cust_pay.html delete mode 100755 httemplate/search/svc_acct.html delete mode 100755 httemplate/search/svc_domain.html diff --git a/CREDITS b/CREDITS index 930b4f351..72b049129 100644 --- a/CREDITS +++ b/CREDITS @@ -157,5 +157,11 @@ Perl backend version (c) copyright 2005 Nathan Schmidt Scott Edwards contributed magic for XMLHTTP error handling, and other patches. +Contains XMenu +by Erik Arvidsson, licensed under the terms of the GNU GPL. + +Contains public domain artwork from openclipart.org by mimooh and other +authors. + Everything else is my (Ivan Kohler ) fault. diff --git a/Changes.1.7.0 b/Changes.1.7.0 new file mode 100644 index 000000000..8dcc36e08 --- /dev/null +++ b/Changes.1.7.0 @@ -0,0 +1,39 @@ +- tax report overhaul +- package classes +- UI overhaul. No more Apache::ASP support, HTML::Mason only +- lots of CDR/telephony work +- inventory classes and inventory (better description from directleap/specs.txt) +- vonage click2call bs :) +- zip code report +- sales/credit/receipt summary report now has options for agent, 12mo cumulative totals +- gross sales report/graph broken down by agent and package class +- config switch to base tax off shipping address if present (warning: tax reports can take a long time with this switch on) +- plesk provisioning + +-------- some of the above, nicely: + +- Charge taxes based on shipping address if present. Note that tax + reports can take a bit longer than they used to. + +- Per-agent A/R Aging + - Bookeeping/Collections | Accounts Receivable Aging Summary + +- Per-agent Sales/Credit/Receipt Summary and "pre-selection" of agent + and time period as you requested. + - Bookeeping/Collections | Sales, Credits and Receipts Summary + +- Package classes + - go to Sysadmin | View/Edit package classes and create some classes + - go to Sysadmin | View/Edit package definitions, edit the existing + package defs and put them into classes + +- The sales report you requested, broken down by agent and + package class. This works fine right now, but it will show more + information once you enter some package classes. + - Bookeeping/Collections | Sales report (by agent, package class ... + +-------- + +and... +- ACLs +- Agent virtualization diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm new file mode 100644 index 000000000..01d63e35d --- /dev/null +++ b/FS/FS/AccessRight.pm @@ -0,0 +1,77 @@ +package FS::AccessRight; + +use strict; +user vars qw(@rights %rights); +use Tie::IxHash; + +=head1 NAME + +FS::AccessRight - Access control rights. + +=head1 SYNOPSIS + + use FS::AccessRight; + +=head1 DESCRIPTION + +Access control rights - Permission to perform specific actions that can be +assigned to users and/or groups. + +=cut + +@rights = ( + 'Reports' => [ + '_desc' => 'Access to high-level reporting', + ], + 'Configuration' => [ + '_desc' => 'Access to configuration', + + 'Settings' => {}, + + 'agent' => [ + '_desc' => 'Master access to reseller configuration', + 'agent_type' => {}, + 'agent' => {}, + ], + + 'export_svc_pkg' => [ + '_desc' => 'Access to export, service and package configuration', + 'part_export' => {}, + 'part_svc' => {}, + 'part_pkg' => {}, + 'pkg_class' => {}, + ], + + 'billing' => [ + '_desc' => 'Access to billing configuration', + 'payment_gateway' => {}, + 'part_bill_event' => {}, + 'prepay_credit' => {}, + 'rate' => {}, + 'cust_main_county' => {}, + ], + + 'dialup' => [ + '_desc' => 'Access to dialup configuraiton', + 'svc_acct_pop' => {}, + ], + + 'broadband' => [ + '_desc' => 'Access to broadband configuration', + 'router' => {}, + 'addr_block' => {}, + ], + + 'misc' => [ + 'part_referral' => {}, + 'part_virtual_field' => {}, + 'msgcat' => {}, + 'inventory_class' => {}, + ], + + }, + +); + +#turn it into a more hash-like structure, but ordered via IxHash + diff --git a/FS/FS/CGI.pm b/FS/FS/CGI.pm index f1f2a3dca..d598f5218 100644 --- a/FS/FS/CGI.pm +++ b/FS/FS/CGI.pm @@ -62,9 +62,9 @@ sub header { - $title +
    $title
    -

    +
    END $x .= $menubar. "

    " if $menubar; $x; @@ -115,6 +115,7 @@ sub menubar { #$menubar=menubar('Main Menu', '../', 'Item', 'url', ... ); my($item,$url,@html); while (@_) { ($item,$url)=splice(@_,0,2); + next if $item =~ /^\s*Main\s+Menu\s*$/i; push @html, qq!$item!; } join(' | ',@html); diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 9125758d0..e81185666 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -244,6 +244,8 @@ sub tables_hashref { my $username_len = 32; #usernamemax config file + # name type nullability length default local + return { 'agent' => { @@ -445,6 +447,9 @@ sub tables_hashref { 'index' => [ ['last'], [ 'company' ], [ 'referral_custnum' ], [ 'daytime' ], [ 'night' ], [ 'fax' ], [ 'refnum' ], [ 'county' ], [ 'state' ], [ 'country' ], [ 'zip' ], + [ 'ship_last' ], [ 'ship_company' ], + [ 'payby' ], [ 'paydate' ], + ], }, @@ -1444,16 +1449,94 @@ sub tables_hashref { 'inventory_class' => { 'columns' => [ - 'classnum', 'serial', '', '', '', '', - 'classname', 'varchar', $char_d, '', '', '', + 'classnum', 'serial', '', '', '', '', + 'classname', 'varchar', '', $char_d, '', '', ], 'primary_key' => 'classnum', 'unique' => [], 'index' => [], }, + 'access_user' => { + 'columns' => [ + 'usernum', 'serial', '', '', '', '', + 'username', 'varchar', '', $char_d, '', '', + '_password', 'varchar', '', $char_d, '', '', + 'last', 'varchar', '', $char_d, '', '', + 'first', 'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'usernum', + 'unique' => [ [ 'username' ] ], + 'index' => [], + }, + + 'access_user_pref' => { + 'columns' => [ + 'prefnum', 'serial', '', '', '', '', + 'usernum', 'int', '', '', '', '', + 'prefname', 'varchar', '', $char_d, '', '', + 'prefvalue', 'text', 'NULL', '', '', '', + ], + 'primary_key' => 'prefnum', + 'unique' => [], + 'index' => [ [ 'usernum' ] ], + }, + + 'access_group' => { + 'columns' => [ + 'groupnum', 'serial', '', '', '', '', + 'groupname', 'varchar', '', $char_d, '', '', + ], + 'primary_key' => 'groupnum', + 'unique' => [ [ 'groupname' ] ], + 'index' => [], + }, + + 'access_usergroup' => { + 'columns' => [ + 'usergroupnum', 'serial', '', '', '', '', + 'usernum', 'int', '', '', '', '', + 'groupnum', 'int', '', '', '', '', + ], + 'primary_key' => 'usergroupnum', + 'unique' => [ [ 'usernum', 'groupnum' ] ], + 'index' => [ [ 'usernum' ] ], + }, + + 'access_groupagent' => { + 'columns' => [ + 'groupagentnum', 'serial', '', '', '', '', + 'groupnum', 'int', '', '', '', '', + 'agentnum', 'int', '', '', '', '', + ], + 'primary_key' => 'groupagentnum', + 'unique' => [ [ 'groupnum', 'agentnum' ] ], + 'index' => [ [ 'groupnum' ] ], + }, + + 'access_right' => { + 'columns' => [ + 'rightnum', 'serial', '', '', '', '', + 'righttype', 'varchar', '', $char_d, '', '', + 'rightobjnum', 'int', '', '', '', '', + 'rightname', 'varchar', '', '', '', '', + ], + 'primary_key' => 'rightnum', + 'unique' => [ [ 'righttype', 'rightobjnum', 'rightname' ] ], + 'index' => [], + }, + }; + #'new_table' => { + # 'columns' => [ + # 'num', 'serial', '', '', '', '', + # ], + # 'primary_key' => 'num', + # 'unique' => [], + # 'index' => [], + #}, + } =back diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm index dc45e0188..10ddbf33f 100644 --- a/FS/FS/UI/Web.pm +++ b/FS/FS/UI/Web.pm @@ -184,6 +184,10 @@ sub process { $self->job_status(@args); + } else { + + die "unknown sub $sub"; + } } @@ -228,11 +232,19 @@ sub start_job { my $error = $job->insert( '_JOB', encode_base64(nfreeze(\%param)) ); if ( $error ) { + + warn "job not inserted: $error\n" + if $DEBUG; + $error; #this doesn't seem to be handled well, # will trigger "illegal jobnum" below? # (should never be an error inserting the job, though, only thing # would be Pg f%*kage) } else { + + warn "job inserted successfully with jobnum ". $job->jobnum. "\n" + if $DEBUG; + $job->jobnum; } @@ -253,7 +265,7 @@ sub job_status { my @return; if ( $job && $job->status ne 'failed' ) { @return = ( 'progress', $job->statustext ); - } elsif ( !$job ) { #handle job gone case : job sucessful + } elsif ( !$job ) { #handle job gone case : job successful # so close popup, redirect parent window... @return = ( 'complete' ); } else { diff --git a/FS/FS/access_group.pm b/FS/FS/access_group.pm new file mode 100644 index 000000000..9d870e57f --- /dev/null +++ b/FS/FS/access_group.pm @@ -0,0 +1,121 @@ +package FS::access_group; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::access_group - Object methods for access_group records + +=head1 SYNOPSIS + + use FS::access_group; + + $record = new FS::access_group \%hash; + $record = new FS::access_group { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_group object represents an example. FS::access_group inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item groupnum - primary key + +=item groupname - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_group'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=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. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('groupnum') + || $self->ut_text('groupname') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/access_groupagent.pm b/FS/FS/access_groupagent.pm new file mode 100644 index 000000000..6b5def1a3 --- /dev/null +++ b/FS/FS/access_groupagent.pm @@ -0,0 +1,124 @@ +package FS::access_groupagent; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::access_groupagent - Object methods for access_groupagent records + +=head1 SYNOPSIS + + use FS::access_groupagent; + + $record = new FS::access_groupagent \%hash; + $record = new FS::access_groupagent { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_groupagent object represents an example. FS::access_groupagent inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item groupagentnum - primary key + +=item groupnum - + +=item agentnum - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_groupagent'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=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. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('groupagentnum') + || $self->ut_number('groupnum') + || $self->ut_number('agentnum') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/access_right.pm b/FS/FS/access_right.pm new file mode 100644 index 000000000..67200f245 --- /dev/null +++ b/FS/FS/access_right.pm @@ -0,0 +1,127 @@ +package FS::access_right; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::access_right - Object methods for access_right records + +=head1 SYNOPSIS + + use FS::access_right; + + $record = new FS::access_right \%hash; + $record = new FS::access_right { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_right object represents an example. FS::access_right inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item rightnum - primary key + +=item righttype - + +=item rightobjnum - + +=item rightname - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_right'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=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. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('rightnum') + || $self->ut_text('righttype') + || $self->ut_text('rightobjnum') + || $self->ut_text('rightname') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm new file mode 100644 index 000000000..ca311d3b8 --- /dev/null +++ b/FS/FS/access_user.pm @@ -0,0 +1,167 @@ +package FS::access_user; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); +use FS::m2m_Common; +use FS::access_usergroup; + +@ISA = qw( FS::m2m_Common FS::Record ); + +=head1 NAME + +FS::access_user - Object methods for access_user records + +=head1 SYNOPSIS + + use FS::access_user; + + $record = new FS::access_user \%hash; + $record = new FS::access_user { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_user object represents an example. FS::access_user inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item usernum - primary key + +=item username - + +=item _password - + +=item last - + +=item first - + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_user'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=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. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('usernum') + || $self->ut_text('username') + || $self->ut_text('_password') + || $self->ut_text('last') + || $self->ut_text('first') + ; + return $error if $error; + + $self->SUPER::check; +} + +=item name + +Returns a name string for this user: "Last, First". + +=cut + +sub name { + my $self = shift; + $self->get('last'). ', '. $self->first; +} + +=item access_usergroup + +=cut + +sub access_usergroup { + my $self = shift; + qsearch( 'access_usergroup', { 'usernum' => $self->usernum } ); +} + +#=item access_groups +# +#=cut +# +#sub access_groups { +# +#} +# +#=item access_groupnames +# +#=cut +# +#sub access_groupnames { +# +#} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/access_user_pref.pm b/FS/FS/access_user_pref.pm new file mode 100644 index 000000000..ff957f2a1 --- /dev/null +++ b/FS/FS/access_user_pref.pm @@ -0,0 +1,127 @@ +package FS::access_user_pref; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::access_user_pref - Object methods for access_user_pref records + +=head1 SYNOPSIS + + use FS::access_user_pref; + + $record = new FS::access_user_pref \%hash; + $record = new FS::access_user_pref { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_user_pref object represents an example. FS::access_user_pref inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item prefnum - primary key + +=item usernum - + +=item prefname - + +=item prefvalue - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_user_pref'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=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. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('prefnum') + || $self->ut_number('usernum') + || $self->ut_text('prefname') + || $self->ut_textn('prefvalue') + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/access_usergroup.pm b/FS/FS/access_usergroup.pm new file mode 100644 index 000000000..4d8836c15 --- /dev/null +++ b/FS/FS/access_usergroup.pm @@ -0,0 +1,144 @@ +package FS::access_usergroup; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); +use FS::access_user; +use FS::access_group; + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::access_usergroup - Object methods for access_usergroup records + +=head1 SYNOPSIS + + use FS::access_usergroup; + + $record = new FS::access_usergroup \%hash; + $record = new FS::access_usergroup { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::access_usergroup object represents an example. FS::access_usergroup inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item usergroupnum - primary key + +=item usernum - + +=item groupnum - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'access_usergroup'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=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. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('usergroupnum') + || $self->ut_number('usernum') + || $self->ut_number('groupnum') + ; + return $error if $error; + + $self->SUPER::check; +} + +=item access_user + +=cut + +sub access_user { + my $self = shift; + qsearchs( 'access_user', { 'usernum' => $self->usernum } ); +} + +=item access_group + +=cut + +sub access_group { + my $self = shift; + qsearchs( 'access_group', { 'groupnum' => $self->groupnum } ); +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/agent_type.pm b/FS/FS/agent_type.pm index 968b3b72e..b28c57285 100644 --- a/FS/FS/agent_type.pm +++ b/FS/FS/agent_type.pm @@ -3,10 +3,11 @@ package FS::agent_type; use strict; use vars qw( @ISA ); use FS::Record qw( qsearch ); +use FS::m2m_Common; use FS::agent; use FS::type_pkgs; -@ISA = qw( FS::Record ); +@ISA = qw( FS::m2m_Common FS::Record ); =head1 NAME diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index cce028b2c..bcae4d646 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -2463,6 +2463,7 @@ use Data::Dumper; use MIME::Base64; sub process_re_X { my( $method, $job ) = ( shift, shift ); + warn "process_re_X $method for job $job\n" if $DEBUG; my $param = thaw(decode_base64(shift)); warn Dumper($param) if $DEBUG; @@ -2478,6 +2479,10 @@ sub process_re_X { sub re_X { my($method, $job, %param ) = @_; # [ 'begin', 'end', 'agentnum', 'open', 'days', 'newest_percust' ], + if ( $DEBUG ) { + warn "re_X $method for job $job with param:\n". + join( '', map { " $_ => ". $param{$_}. "\n" } keys %param ); + } #some false laziness w/search/cust_bill.html my $distinct = ''; diff --git a/FS/FS/m2m_Common.pm b/FS/FS/m2m_Common.pm new file mode 100644 index 000000000..fd8700a2d --- /dev/null +++ b/FS/FS/m2m_Common.pm @@ -0,0 +1,110 @@ +package FS::m2m_Common; + +use strict; +use vars qw( @ISA $DEBUG ); +use FS::Schema qw( dbdef ); +use FS::Record qw( qsearch qsearchs ); #dbh ); + +@ISA = qw( FS::Record ); + +$DEBUG = 0; + +=head1 NAME + +FS::m2m_Common - Base class for classes in a many-to-many relationship + +=head1 SYNOPSIS + +use FS::m2m_Common; + +@ISA = qw( FS::m2m_Common ); + +=head1 DESCRIPTION + +FS::m2m_Common is intended as a base class for classes which have a +many-to-many relationship with another table (via a linking table). + +Note: It is currently assumed that the link table contains two fields +named the same as the primary keys of ths base and target tables. + +=head1 METHODS + +=over 4 + +=item process_m2m + +=cut + +sub process_m2m { + my( $self, %opt ) = @_; + + my $self_pkey = $self->dbdef_table->primary_key; + + my $link_table = $self->_load_table($opt{'link_table'}); + + my $target_table = $self->_load_table($opt{'target_table'}); + my $target_pkey = dbdef->table($target_table)->primary_key; + + foreach my $target_obj ( qsearch($target_table, {} ) ) { + + my $targetnum = $target_obj->$target_pkey(); + + my $link_obj = qsearchs( $link_table, { + $self_pkey => $self->$self_pkey(), + $target_pkey => $targetnum, + }); + + if ( $link_obj && ! $opt{'params'}->{"$target_pkey$targetnum"} ) { + + my $d_link_obj = $link_obj; #need to save $link_obj for below. + my $error = $d_link_obj->delete; + die $error if $error; + + } elsif ( $opt{'params'}->{"$target_pkey$targetnum"} && ! $link_obj ) { + + #ok to clobber it now (but bad form nonetheless?) + #$link_obj = new "FS::$link_table" ( { + $link_obj = "FS::$link_table"->new( { + $self_pkey => $self->$self_pkey(), + $target_pkey => $targetnum, + }); + my $error = $link_obj->insert; + die $error if $error; + } + + } + + ''; +} + +sub _load_table { + my( $self, $table ) = @_; + eval "use FS::$table"; + die $@ if $@; + $table; +} + +#=item target_table +# +#=cut +# +#sub target_table { +# my $self = shift; +# my $target_table = $self->_target_table; +# eval "use FS::$target_table"; +# die $@ if $@; +# $target_table; +#} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L + +=cut + +1; + diff --git a/FS/FS/part_pkg/billoneday.pm b/FS/FS/part_pkg/billoneday.pm deleted file mode 100644 index 8740547a3..000000000 --- a/FS/FS/part_pkg/billoneday.pm +++ /dev/null @@ -1,48 +0,0 @@ -package FS::part_pkg::billoneday; - -use strict; -use vars qw(@ISA %info); -use Time::Local qw(timelocal); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'charge a full month every (selectable) billing day', - 'fields' => { - 'setup_fee' => { 'name' => 'Setup fee for this package', - 'default' => 0, - }, - 'recur_fee' => { 'name' => 'Recurring fee for this package', - 'default' => 0, - }, - 'cutoff_day' => { 'name' => 'billing day', - 'default' => 1, - }, - - }, - 'fieldorder' => [ 'setup_fee', 'recur_fee','cutoff_day'], - #'setup' => 'what.setup_fee.value', - #'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; $sdate = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); \' + what.recur_fee.value', - 'freq' => 'm', - 'weight' => 30, -); - -sub calc_recur { - my($self, $cust_pkg, $sdate ) = @_; - - my $mnow = $$sdate; - my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; - my $mstart = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); - my $mend = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11)); - - if($mday > $self->option('cutoff_date') and $mstart != $mnow ) { - $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11)); - } - else{ - $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon, $year); - } - $self->option('recur_fee'); -} -1; diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm index 4425df040..9f8b68918 100644 --- a/FS/FS/payby.pm +++ b/FS/FS/payby.pm @@ -115,7 +115,8 @@ sub cust_payby2longname { =head1 BUGS -This should eventually be an actual database table. +This should eventually be an actual database table, and all tables that +currently have a char payby field should have a foreign key into here instead. =head1 SEE ALSO diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm index 191d85604..bdaf79b2f 100644 --- a/FS/FS/svc_domain.pm +++ b/FS/FS/svc_domain.pm @@ -230,7 +230,11 @@ sub delete { my $error = $domain_record->delete; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return $error; + return "can't delete DNS entry: ". + join(' ', map $domain_record->$_(), + qw( reczone recaf rectype recdata ) + ). + ":$error"; } } diff --git a/FS/MANIFEST b/FS/MANIFEST index c309251ff..bd810a8db 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -339,3 +339,4 @@ FS/access_groupagent.pm t/access_groupagent.t FS/access_right.pm t/access_right.t +FS/m2m_Common.pm diff --git a/FS/bin/freeside-addoutsourceuser b/FS/bin/freeside-addoutsourceuser index cad07f1fd..02a435141 100644 --- a/FS/bin/freeside-addoutsourceuser +++ b/FS/bin/freeside-addoutsourceuser @@ -3,6 +3,7 @@ username=$1 domain=$2 password=$3 +realdomain=$4 freeside-adduser -h /usr/local/etc/freeside/htpasswd \ -s conf.DBI:Pg:dbname=$domain/secrets \ @@ -10,6 +11,5 @@ freeside-adduser -h /usr/local/etc/freeside/htpasswd \ $username $password 2>/dev/null [ -e /usr/local/etc/freeside/dbdef.DBI:Pg:dbname=$domain ] \ - || ( freeside-setup -s $username 2>/dev/null; \ - /home/ivan/freeside/bin/populate-msgcat $username 2>/dev/null ) + || ( freeside-setup -d $realdomain $username 2>/dev/null ) diff --git a/FS/t/AccessRight.t b/FS/t/AccessRight.t new file mode 100644 index 000000000..a96684224 --- /dev/null +++ b/FS/t/AccessRight.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::AccessRight; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_group.t b/FS/t/access_group.t new file mode 100644 index 000000000..be141099b --- /dev/null +++ b/FS/t/access_group.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_group; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_groupagent.t b/FS/t/access_groupagent.t new file mode 100644 index 000000000..aff1f2524 --- /dev/null +++ b/FS/t/access_groupagent.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_groupagent; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_right.t b/FS/t/access_right.t new file mode 100644 index 000000000..66cd362e8 --- /dev/null +++ b/FS/t/access_right.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_right; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_user.t b/FS/t/access_user.t new file mode 100644 index 000000000..cab679d8d --- /dev/null +++ b/FS/t/access_user.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_user; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_user_pref.t b/FS/t/access_user_pref.t new file mode 100644 index 000000000..282209830 --- /dev/null +++ b/FS/t/access_user_pref.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_user_pref; +$loaded=1; +print "ok 1\n"; diff --git a/FS/t/access_usergroup.t b/FS/t/access_usergroup.t new file mode 100644 index 000000000..383a7cf9c --- /dev/null +++ b/FS/t/access_usergroup.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::access_usergroup; +$loaded=1; +print "ok 1\n"; diff --git a/htetc/handler.pl b/htetc/handler.pl index e8cd3333b..cbe2dd35d 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -179,6 +179,8 @@ sub handler use FS::inventory_class; use FS::inventory_item; use FS::pkg_class; + use FS::access_user; + use FS::access_group; if ( %%%RT_ENABLED%%% ) { eval ' diff --git a/httemplate/autohandler b/httemplate/autohandler index a3f7eb008..ad0ab8ba6 100644 --- a/httemplate/autohandler +++ b/httemplate/autohandler @@ -9,7 +9,15 @@ if ( UNIVERSAL::can(dbh, 'sprintProfile') ) { if ( lc($r->content_type) eq 'text/html' ) { - $profile = '
    '. encode_entities(dbh->sprintProfile()).
    +    # barely worth it, just in case someone tries to use profiling on a
    +    # non-RT install
    +    eval "use Text::Wrapper;";
    +    die $@ if $@;
    +
    +    my $wrapper = new Text::Wrapper( columns => 80 );
    +
    +    $profile = '
    '.
    +               encode_entities( $wrapper->wrap( dbh->sprintProfile() ) ).
                    #"\n\n". &sprintAutoProfile(). '
    '; "\n\n". '
    '; } diff --git a/httemplate/browse/access_group.html b/httemplate/browse/access_group.html new file mode 100644 index 000000000..6ba89ea81 --- /dev/null +++ b/httemplate/browse/access_group.html @@ -0,0 +1,33 @@ +<% + +my $html_init = + "Internal access groups control access to the back-office interface.

    ". + qq!Add an internal access group

    !; + +my $count_query = 'SELECT COUNT(*) FROM access_group'; + +my $link = [ $p.'edit/access_group.html?', 'groupnum' ]; + +%><%= include( 'elements/browse.html', + 'title' => 'Internal Access Groups', + 'menubar' => [ # 'Main menu' => $p, + 'Internal users' => $p.'browse/access_user.html', + ], + 'html_init' => $html_init, + 'name' => 'internal access groups', + 'query' => { 'table' => 'access_group', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY groupname', #?? + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Group name', + ], + 'fields' => [ 'groupnum', + 'groupname', + ], + 'links' => [ $link, + $link, + ], + ) +%> diff --git a/httemplate/browse/access_user.html b/httemplate/browse/access_user.html new file mode 100644 index 000000000..38d5430b1 --- /dev/null +++ b/httemplate/browse/access_user.html @@ -0,0 +1,63 @@ +<% + +my $html_init = + "Internal users have access to the back-office interface. Typically, this is your employees and contractors, but in a VISP setup, you can also add accounts for your reseller's employees. It is highly recommended to add a separate account for each person rather than using role accounts.

    ". + qq!Add an internal user

    !; + +#false laziness w/agent_type.cgi +my $groups_sub = sub { + my $access_user = shift; + + [ map { + my $access_usergroup = $_; + my $access_group = $access_usergroup->access_group; + [ + { + 'data' => $access_group->groupname, + 'align' => 'left', + 'link' => + $p. 'edit/access_group.html?'. $access_usergroup->groupnum, + }, + ]; + } + grep { $_->access_group # and ! $_->access_group->disabled + } + $access_user->access_usergroup, + + ]; + +}; + +my $count_query = 'SELECT COUNT(*) FROM access_user'; + +my $link = [ $p.'edit/access_user.html?', 'usernum' ]; + +%><%= include( 'elements/browse.html', + 'title' => 'Internal Users', + 'menubar' => [ #'Main menu' => $p, + 'Internal access groups' => $p.'browse/access_group.html', + ], + 'html_init' => $html_init, + 'name' => 'internal users', + 'query' => { 'table' => 'access_user', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY last, first', + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Username', + 'Full name', + 'Groups' + ], + 'fields' => [ 'usernum', + 'username', + 'name', # sub { shift->name }, + $groups_sub, + ], + 'links' => [ $link, + $link, + $link, + '' + ], + ) +%> diff --git a/httemplate/browse/agent_type.cgi b/httemplate/browse/agent_type.cgi index 2e1bdad42..a5ffb1048 100755 --- a/httemplate/browse/agent_type.cgi +++ b/httemplate/browse/agent_type.cgi @@ -1,60 +1,62 @@ - -<%= include("/elements/header.html","Agent Type Listing", menubar( - 'Main Menu' => $p, - 'Agents' => $p. 'browse/agent.cgi', -)) %> -Agent types define groups of packages that you can then assign to particular -agents.

    -Add a new agent type

    +<% -<%= table() %> - - Agent Type - Packages - +my $html_init = + 'Agent types define groups of packages that you can then assign to'. + ' particular agents.

    '. + qq!Add a new agent type

    !; -<% -foreach my $agent_type ( sort { - $a->getfield('typenum') <=> $b->getfield('typenum') -} qsearch('agent_type',{}) ) { - my $hashref = $agent_type->hashref; - #more efficient to do this with SQL... - my @type_pkgs = grep { $_->part_pkg and ! $_->part_pkg->disabled } - qsearch('type_pkgs',{'typenum'=> $hashref->{typenum} }); - my $rowspan = scalar(@type_pkgs); - $rowspan = int($rowspan/2+0.5) ; - print < - - $hashref->{typenum} - - $hashref->{atype} -END +my $count_query = 'SELECT COUNT(*) FROM agent_type'; - my($type_pkgs); - my($tdcount) = -1 ; - foreach $type_pkgs ( @type_pkgs ) { - my($pkgpart)=$type_pkgs->getfield('pkgpart'); - my($part_pkg) = qsearchs('part_pkg',{'pkgpart'=> $pkgpart }); - print qq!! if ($tdcount == 0) ; - $tdcount = 0 if ($tdcount == -1) ; - print qq!!, - $part_pkg->getfield('pkg'),""; - $tdcount ++ ; - if ($tdcount == 2) - { - print qq!\n! ; - $tdcount = 0 ; - } - } +#false laziness w/access_user.html +my $packages_sub = sub { + my $agent_type = shift; - print ""; -} + [ map { + my $type_pkgs = $_; + my $part_pkg = $type_pkgs->part_pkg; + [ + { + 'data' => $part_pkg->pkg. ' - '. $part_pkg->comment, + 'align' => 'left', + 'link' => $p. 'edit/part_pkg.cgi?'. $type_pkgs->pkgpart, + }, + ]; + } + #sort { + # } + grep { + $_->part_pkg and ! $_->part_pkg->disabled + } + $agent_type->type_pkgs #XXX the method should order itself by something + ]; -print < - - -END +}; +my $link = [ $p.'edit/agent_type.cgi?', 'typenum' ]; + +%><%= include( 'elements/browse.html', + 'title' => 'Agent Types', + 'menubar' => [ #'Main menu' => $p, + 'Agents' =>"${p}browse/agent.cgi", + ], + 'html_init' => $html_init, + 'name' => 'agent types', + 'query' => { 'table' => 'agent_type', + 'hashref' => {}, + 'extra_sql' => 'ORDER BY typenum', # 'ORDER BY atype', + }, + 'count_query' => $count_query, + 'header' => [ '#', + 'Agent Type', + 'Packages', + ], + 'fields' => [ 'typenum', + 'atype', + $packages_sub, + ], + 'links' => [ $link, + $link, + '', + ], + ) %> diff --git a/httemplate/browse/cust_main_county.cgi b/httemplate/browse/cust_main_county.cgi index 1e0e0880c..9e3feb8f3 100755 --- a/httemplate/browse/cust_main_county.cgi +++ b/httemplate/browse/cust_main_county.cgi @@ -1,33 +1,34 @@ - -<% +<%= include('/elements/header.html', "Tax Rate Listing", menubar( + 'Edit tax rates' => $p. "edit/cust_main_county.cgi", +)) %> + + Click on expand country to specify a country's tax rates by state. +
    Click on expand state to specify a state's tax rates by county. +<% my $conf = new FS::Conf; my $enable_taxclasses = $conf->exists('enable_taxclasses'); -print header("Tax Rate Listing", menubar( - 'Main Menu' => $p, - 'Edit tax rates' => $p. "edit/cust_main_county.cgi", -)),<expand country to specify a country's tax rates by state. -
    Click on expand state to specify a state's tax rates by county. -END +if ( $enable_taxclasses ) { %> -if ( $enable_taxclasses ) { - print '
    Click on expand taxclasses to specify tax classes'; -} +
    Click on expand taxclasses to specify tax classes -print '

    '. &table(). < - Country - State - County - Taxclass
    (per-package classification) - Tax name
    (printed on invoices) - Tax - Exemption - -END +<% } %> + +

    +<%= table() %> + + + Country + State + County + Taxclass
    (per-package classification) + Tax name
    (printed on invoices) + Tax + Exemption + +<% my @regions = sort { $a->country cmp $b->country or $a->state cmp $b->state or $a->county cmp $b->county @@ -39,10 +40,12 @@ my $sup=0; for ( my $i=0; $i<@regions; $i++ ) { my $cust_main_county = $regions[$i]; my $hashref = $cust_main_county->hashref; - print < - $hashref->{country} -END + <%= $hashref->{country} %> + + <% my $j; if ( $sup ) { @@ -74,69 +77,73 @@ END $j = 1; } - print "{state} + %> + + <%= + $hashref->{state} ? ' BGCOLOR="#ffffff">'. $hashref->{state} : qq! BGCOLOR="#cccccc">(ALL) !. qq!expand country!; - - print qq! collapse state! if $j>1; - - print ""; - } - -# $sup=$newsup; - - print "{county} ) { - print ' BGCOLOR="#ffffff">'. $hashref->{county}; - } else { - print ' BGCOLOR="#cccccc">(ALL)'; - if ( $hashref->{state} ) { - print qq!!. - qq!expand state!; - } - } - print ""; - - print "{taxclass} ) { - print ' BGCOLOR="#ffffff">'. $hashref->{taxclass}; - } else { - print ' BGCOLOR="#cccccc">(ALL)'; - if ( $enable_taxclasses ) { - print qq!!. - qq!expand taxclasses!; - } - - } - print ""; - - print "{taxname} ) { - print ' BGCOLOR="#ffffff">'. $hashref->{taxname}; - } else { - print ' BGCOLOR="#cccccc">Tax'; - } - print ""; - - print "$hashref->{tax}%". - ''; - print '$'. sprintf("%.2f", $hashref->{exempt_amount} ). - ' per month
    ' - if $hashref->{exempt_amount} > 0; - print 'Setup fee
    ' if $hashref->{setuptax} =~ /^Y$/i; - print 'Recurring fee
    ' if $hashref->{recurtax} =~ /^Y$/i; - print ''; - -} - -print < - - -END - -%> + qq!">expand country
    ! + %> + <% if ( $j>1 ) { %> + collapse state + <% } %> + + + <% } %> + +<% # $sup=$newsup; %> + + {county} ) { + %> BGCOLOR="#ffffff"><%= $hashref->{county} %> + <% } else { + %> BGCOLOR="#cccccc">(ALL) + <% if ( $hashref->{state} ) { %> + expand state + <% } %> + <% } %> + + + {taxclass} ) { + %> BGCOLOR="#ffffff"><%= $hashref->{taxclass} %> + <% } else { + %> BGCOLOR="#cccccc">(ALL) + <% if ( $enable_taxclasses ) { %> + expand taxclasses + <% } %> + <% } %> + + + {taxname} ) { + %> BGCOLOR="#ffffff"><%= $hashref->{taxname} %> + <% } else { + %> BGCOLOR="#cccccc">Tax + <% } %> + + + <%= $hashref->{tax} %>% + + + + <% if ( $hashref->{exempt_amount} > 0 ) { %> + $<%= sprintf("%.2f", $hashref->{exempt_amount} ) %> per month
    + <% } %> + + <% if ( $hashref->{setuptax} =~ /^Y$/i ) { %> + Setup fee
    + <% } %> + + <% if ( $hashref->{recurtax} =~ /^Y$/i ) { %> + Recurring fee
    + <% } %> + + + + + +<% } %> + + + +<%= include('/elements/footer.html') %> diff --git a/httemplate/browse/msgcat.cgi b/httemplate/browse/msgcat.cgi index d4adf9f1a..318ebfdff 100755 --- a/httemplate/browse/msgcat.cgi +++ b/httemplate/browse/msgcat.cgi @@ -1,10 +1,6 @@ - -<% - -print header("View Message catalog", menubar( - 'Main Menu' => $p, +<%= include('/elements/header.html', "View Message catalog", menubar( 'Edit message catalog' => $p. "edit/msgcat.cgi", -)), '
    '; +)) %><% my $widget = new HTML::Widgets::SelectLayers( 'selected_layer' => 'en_US', @@ -38,13 +34,7 @@ my $widget = new HTML::Widgets::SelectLayers( }, ); - -print $widget->html; - -print < - - -END - %> + +<%= $widget->html %> +<%= include('/elements/footer.html') %> diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index 0afa54750..41d86358c 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -11,8 +11,8 @@ my $select = '*'; my $orderby = 'pkgpart'; if ( $cgi->param('active') ) { - $orderby = 'num_active'; - + $orderby = 'num_active DESC'; +} $select = " *, @@ -33,13 +33,13 @@ if ( $cgi->param('active') ) { "; -} +#} my $conf = new FS::Conf; my $taxclasses = $conf->exists('enable_taxclasses'); my $html_init; -unless ( $cgi->param('active') ) { +#unless ( $cgi->param('active') ) { $html_init = qq! One or more service definitions are grouped together into a package definition and given pricing information. Customers purchase packages @@ -47,7 +47,7 @@ unless ( $cgi->param('active') ) { Add a new package definition

    !; -} +#} my $posttotal; if ( $cgi->param('showdisabled') ) { @@ -85,7 +85,7 @@ unless ( 0 ) { #already showing only one class or something? $align .= 'l'; } -if ( $cgi->param('active') ) { +#if ( $cgi->param('active') ) { push @header, 'Customer
    packages'; my %col = ( 'active' => '00CC00', @@ -117,7 +117,7 @@ if ( $cgi->param('active') ) { } (qw( active suspended cancelled )) ]; }; $align .= 'r'; -} +#} push @header, 'Frequency'; push @fields, sub { shift->freq_pretty; }; diff --git a/httemplate/edit/access_group.html b/httemplate/edit/access_group.html new file mode 100644 index 000000000..11b8df7bc --- /dev/null +++ b/httemplate/edit/access_group.html @@ -0,0 +1,10 @@ +<%= include( 'elements/edit.html', + 'name' => 'Internal Access Group', + 'table' => 'access_group', + 'labels' => { + 'groupnum' => 'Group number', + 'groupname' => 'Group name', + }, + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/access_user.html b/httemplate/edit/access_user.html new file mode 100644 index 000000000..2b19dbf7b --- /dev/null +++ b/httemplate/edit/access_user.html @@ -0,0 +1,37 @@ +<%= include( 'elements/edit.html', + 'name' => 'Internal User', + 'table' => 'access_user', + 'fields' => [ + 'username', + { field=>'_password', type=>'password' }, + 'last', + 'first', + ], + 'labels' => { + 'usernum' => 'User number', + 'username' => 'Username', + '_password' => 'Password', + 'last' => 'Last name', + 'first' => 'First name', + }, + 'viewall_dir' => 'browse', + 'html_bottom' => + sub { + my $access_user = shift; + + '
    Internal Access Groups
    '. + ntable("#cccccc",2). + ''. + include( '/elements/checkboxes-table.html', + 'source_obj' => $access_user, + 'link_table' => 'access_usergroup', + 'target_table' => 'access_group', + 'name_col' => 'groupname', + 'target_link' => $p.'edit/access_group.html?', + #'disable-able' => 1, + ). + '' + ; + }, + ) +%> diff --git a/httemplate/edit/agent_type.cgi b/httemplate/edit/agent_type.cgi index 944ddd0d0..f5afd3a96 100755 --- a/httemplate/edit/agent_type.cgi +++ b/httemplate/edit/agent_type.cgi @@ -14,9 +14,7 @@ if ( $cgi->param('error') ) { } my $action = $agent_type->typenum ? 'Edit' : 'Add'; -%> - -<%= include("/elements/header.html","$action Agent Type", menubar( +%><%= include("/elements/header.html","$action Agent Type", menubar( 'Main Menu' => "$p", 'View all agent types' => "${p}browse/agent_type.cgi", )) @@ -29,47 +27,29 @@ my $action = $agent_type->typenum ? 'Edit' : 'Add';
    Agent Type #<%= $agent_type->typenum || "(NEW)" %> -

    +
    Agent Type

    Select which packages agents of this type may sell to customers
    - -<% foreach my $part_pkg ( - qsearch({ 'table' => 'part_pkg', - 'hashref' => { 'disabled' => '' }, - 'select' => 'part_pkg.*', - 'addl_from' => 'LEFT JOIN type_pkgs USING ( pkgpart )', - 'extra_sql' => ( $agent_type->typenum - ? 'OR typenum = '. $agent_type->typenum - : '' - ), - }) - ) { +<%= ntable("#cccccc", 2) %> +<%= include('/elements/checkboxes-table.html', + 'source_obj' => $agent_type, + 'link_table' => 'type_pkgs', + 'target_table' => 'part_pkg', + 'name_callback' => sub { $_[0]->pkg. ' - '. $_[0]->comment; }, + 'target_link' => $p.'edit/part_pkg.cgi?', + 'disable-able' => 1, + + ) %> - -
    - $agent_type->typenum, - 'pkgpart' => $part_pkg->pkgpart, - }) - ? 'CHECKED ' - : '' - %> VALUE="ON"> - - <%= $part_pkg->pkgpart %>: - <%= $part_pkg->pkg %> - <%= $part_pkg->comment %> - <%= $part_pkg->disabled =~ /^Y/i ? ' (DISABLED)' : '' %> - -<% } %> - -

    + +
    ">
    - - + +<%= include('/elements/footer.html') %> diff --git a/httemplate/edit/cust_bill_pay.cgi b/httemplate/edit/cust_bill_pay.cgi index 24bce308a..9d3bdd8cb 100755 --- a/httemplate/edit/cust_bill_pay.cgi +++ b/httemplate/edit/cust_bill_pay.cgi @@ -1,4 +1,3 @@ - <% my($paynum, $amount, $invnum); @@ -18,78 +17,76 @@ my $otaker = getotaker; my $p1 = popurl(1); -print header("Apply Payment", ''); -print qq!Error: !, $cgi->param('error'), - "

    " - if $cgi->param('error'); -print < -END +%><%= header("Apply Payment", '') %> +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +

    +<% } %> + +
    + +<% my $cust_pay = qsearchs('cust_pay', { 'paynum' => $paynum } ); die "payment $paynum not found!" unless $cust_pay; my $unapplied = $cust_pay->unapplied; +%> + +Payment #<%= $paynum %> + -print "Payment # $paynum". - qq!!. - '
    Date: '. time2str("%D", $cust_pay->_date). ''. - '
    Amount: $'. $cust_pay->paid. ''. - "
    Unapplied amount: \$$unapplied" - ; +
    Date: <%= time2str("%D", $cust_pay->_date) %> +
    Amount: $<%= $cust_pay->paid %> + +
    Unapplied amount: $<%= $unapplied %> + +<% my @cust_bill = grep $_->owed != 0, qsearch('cust_bill', { 'custnum' => $cust_pay->custnum } ); -print < + -END - -print qq!
    Invoice #"; -print qq!
    Amount \$!; +
    Invoice # + +
    Amount $ -print < - -END +
    -print < - - + -END - -%> diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi index aae0df2fc..946b1087b 100755 --- a/httemplate/edit/cust_credit.cgi +++ b/httemplate/edit/cust_credit.cgi @@ -1,4 +1,3 @@ - <% my $conf = new FS::Conf; @@ -25,39 +24,57 @@ my $otaker = getotaker; my $p1 = popurl(1); -print header("Post Credit", ''); -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); -print <config('countrydefault')); -
    - - - - - - -END - -print '

    Credit'. ntable("#cccccc", 2). - 'Date'. - time2str("%D",$_date). ''; - -print qq!Amount\$!; +%> + +<%= header("Post Credit", '') %> + +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +

    +<% } %> + + + + + + + + + + + +Credit + +<%= ntable("#cccccc", 2) %> + + Date + <%= time2str("%D",$_date) %> + + + + Amount + $ + + +<% #print qq! Also post refund!; +%> -print qq!Reason!; + + Reason + + -print qq!Auto-apply
    to invoices!; + + Auto-apply
    to invoices + + -print < +
    - +
    -END - -%> diff --git a/httemplate/edit/cust_credit_bill.cgi b/httemplate/edit/cust_credit_bill.cgi index 1a97e1312..409ea3c25 100755 --- a/httemplate/edit/cust_credit_bill.cgi +++ b/httemplate/edit/cust_credit_bill.cgi @@ -1,4 +1,3 @@ - <% my($crednum, $amount, $invnum); @@ -23,79 +22,78 @@ my $otaker = getotaker; my $p1 = popurl(1); -print header("Apply Credit", ''); -print qq!Error: !, $cgi->param('error'), - "

    " - if $cgi->param('error'); -print < -END +%><%= header("Apply Credit", '') %> +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +

    +<% } %> + +
    + +<% my $cust_credit = qsearchs('cust_credit', { 'crednum' => $crednum } ); die "credit $crednum not found!" unless $cust_credit; my $credited = $cust_credit->credited; +%> + +Credit #<%= $crednum %> + + +
    Date: <%= time2str("%D", $cust_credit->_date) %> -print "Credit # $crednum". - qq!!. - '
    Date: '. time2str("%D", $cust_credit->_date). ''. - '
    Amount: $'. $cust_credit->amount. ''. - "
    Unapplied amount: \$$credited". - '
    Reason: '. $cust_credit->reason. '' - ; +
    Amount: $<%= $cust_credit->amount %> +
    Unapplied amount: $<%= $credited %> + +
    Reason: <%= $cust_credit->reason %> + +<% my @cust_bill = grep $_->owed != 0, qsearch('cust_bill', { 'custnum' => $cust_credit->custnum } ); -print < + -END - -print qq!
    Invoice #"; -print qq!
    Amount \$!; +
    Invoice # + +
    Amount $ -print < - -END +
    -print < - - + -END - -%> diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index 80fec9359..bb2a8618e 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -397,49 +397,66 @@ unless ( $custnum ) { if ( @part_pkg ) { -# print "

    First package", &itable("#cccccc", "0 ALIGN=LEFT"), -#apiabuse & undesirable wrapping - print "
    First package", &ntable("#cccccc"), - qq!"; - - #false laziness: (mostly) copied from edit/svc_acct.cgi - #$ulen = $svc_acct->dbdef_table->column('username')->length; - my $ulen = dbdef->table('svc_acct')->column('username')->length; - my $ulen2 = $ulen+2; - my $passwordmax = $conf->config('passwordmax') || 8; - my $pmax2 = $passwordmax + 2; - print <Username - -Password - -(blank to generate) -END - - print 'Access number' - . - &FS::svc_acct_pop::popselector($popnum). - '' - ; - } -} + # print "

    First package", &itable("#cccccc", "0 ALIGN=LEFT"), + #apiabuse & undesirable wrapping + + %> +
    First package + <%= ntable("#cccccc") %> + + + + + + + + <% + #false laziness: (mostly) copied from edit/svc_acct.cgi + #$ulen = $svc_acct->dbdef_table->column('username')->length; + my $ulen = dbdef->table('svc_acct')->column('username')->length; + my $ulen2 = $ulen+2; + my $passwordmax = $conf->config('passwordmax') || 8; + my $pmax2 = $passwordmax + 2; + %> + + + Username + + MAXLENGTH=<%= $ulen %>> + + + + + Password + + MAXLENGTH=<%= $passwordmax %>> + (blank to generate) + + + + + Access number + <%= FS::svc_acct_pop::popselector($popnum) %> + + + + <% } %> + +<% } %> -my $otaker = $cust_main->otaker; -print qq!!, - qq!

    !, - "", -; + +
    +"> +
    + + +<%= include('/elements/footer.html') %> -%> diff --git a/httemplate/edit/cust_pkg.cgi b/httemplate/edit/cust_pkg.cgi index ce1c86612..174d4dde1 100755 --- a/httemplate/edit/cust_pkg.cgi +++ b/httemplate/edit/cust_pkg.cgi @@ -1,4 +1,3 @@ - <% my %pkg = (); @@ -29,48 +28,62 @@ if ( $cgi->param('error') ) { } my $p1 = popurl(1); -print header("Add/Edit Packages", ''); -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); +%><%= include('/elements/header.html', "Add/Edit Packages", '') %> -print qq!
    !; +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +<% } %> -print qq!!; + + + +<% #current packages -my @cust_pkg = qsearch('cust_pkg',{ 'custnum' => $custnum, 'cancel' => '' } ); +my @cust_pkg = qsearch('cust_pkg', { 'custnum' => $custnum, 'cancel' => '' } ); if (@cust_pkg) { - print < - - Pkg # - Package description - -

    -END +%> + + Current packages - select to remove (services are moved to a new package below) + + + + + +

    - foreach (sort { $all_pkg{$a->getfield('pkgpart')} cmp $all_pkg{$b->getfield('pkgpart')} } @cust_pkg) { + <% + + foreach ( sort { $all_pkg{ $a->getfield('pkgpart') } + cmp $all_pkg{ $b->getfield('pkgpart') } + } + @cust_pkg + ) + { my($pkgnum,$pkgpart)=( $_->getfield('pkgnum'), $_->getfield('pkgpart') ); my $checked = $remove_pkg{$pkgnum} ? ' CHECKED' : ''; - print < - - \n - - -END - } - print qq!
    Pkg #Package description
    $pkgnum:$all_pkg{$pkgpart} - $all_comment{$pkgpart}


    !; -} -print <
    -END + %> + + + > + <%= $pkgnum %>: + <%= $all_pkg{$pkgpart} %> - <%= $all_comment{$pkgpart} %> + + + <% } %> + + +

    +<% } %> + +Order new packages +

    + +<% my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum}); my $agent = qsearchs('agent',{'agentnum'=> $cust_main->agentnum }); @@ -79,13 +92,15 @@ my %agent_pkgs = map { ( $_->pkgpart , $all_pkg{$_->pkgpart} ) } my $count = 0; my $pkgparts = 0; -print < + -END + +<% #foreach my $type_pkgs ( qsearch('type_pkgs',{'typenum'=> $agent->typenum }) ) { foreach my $pkgpart ( sort { $agent_pkgs{$a} cmp $agent_pkgs{$b} } keys(%agent_pkgs) ) { @@ -93,38 +108,43 @@ foreach my $pkgpart ( sort { $agent_pkgs{$a} cmp $agent_pkgs{$b} } next unless exists $pkg{$pkgpart}; #skip disabled ones #print qq!! if ( $count == 0 ); my $value = $cgi->param("pkg$pkgpart") || 0; - print < + - - - + + + -END + +<% $count ++ ; #if ( $count == 2 ) { # print qq!\n! ; # $count = 0; #} } -print qq!
    Qty. Package Description
    $pkgpart:$pkg{$pkgpart} - $comment{$pkgpart} + " VALUE="<%= $value %>" SIZE="2" MAXLENGTH="2"> + <%= $pkgpart %>:<%= $pkg{$pkgpart} %> - <%= $comment{$pkgpart}%>
    !; - -unless ( $pkgparts ) { - my $p2 = popurl(2); - my $typenum = $agent->typenum; - my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } ); - my $atype = $agent_type->atype; - print <package definitions, or agent type -$atype not allowed to purchase -any packages.) -END -} +%> -#submit -print < - - - -END + + +<% unless ( $pkgparts ) { + my $p2 = popurl(2); + my $typenum = $agent->typenum; + my $agent_type = qsearchs( 'agent_type', { 'typenum' => $typenum } ); + my $atype = $agent_type->atype; %> + + (No package definitions, + or agent type + <%= $atype %> + is not allowed to purchase any packages.) + +<% } %> + +

    + + + +<%= include('/elements/footer.html') %> diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 5486b4b00..120c03a3c 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -17,6 +17,13 @@ # 'menubar' => '', #menubar arrayref # # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' + # + # 'html_bottom' => '', #string + # 'html_bottom' => sub { + # my $object = shift; + # # ... + # "html_string"; + # }, my(%opt) = @_; @@ -27,6 +34,7 @@ my $fields = $opt{'fields'} #|| [ grep { $_ ne $pkey } dbdef->table($table)->columns ]; || [ grep { $_ ne $pkey } fields($table) ]; + #my @actualfields = map { ref($_) ? $_->{'field'} : $_ } @$fields; my $object; if ( $cgi->param('error') ) { @@ -63,10 +71,7 @@ ); } -%> - - -<%= include("/elements/header.html", $title, +%><%= include("/elements/header.html", $title, include( '/elements/menubar.html', @menubar ) ) %> @@ -86,7 +91,18 @@ <%= ntable("#cccccc",2) %> -<% foreach my $field ( @$fields ) { %> +<% foreach my $f ( @$fields ) { + + my( $field, $type); + if ( ref($f) ) { + $field = $f->{'field'}, + $type = $f->{'type'} || 'text', + } else { + $field = $f; + $type = 'text'; + } + +%> @@ -98,12 +114,11 @@ <% - #just text in one size for now... eventually more options for - # uneditable, hidden, , etc. fields %> - + @@ -112,6 +127,11 @@ +<%= ref( $opt{'html_bottom'} ) + ? &{ $opt{'html_bottom'} }( $object ) + : $opt{'html_bottom'} +%> +
    "> diff --git a/httemplate/edit/part_referral.cgi b/httemplate/edit/part_referral.cgi index f784dfa3e..dce1e6394 100755 --- a/httemplate/edit/part_referral.cgi +++ b/httemplate/edit/part_referral.cgi @@ -1,4 +1,3 @@ - <% my $part_referral; @@ -17,32 +16,29 @@ my $action = $part_referral->refnum ? 'Edit' : 'Add'; my $hashref = $part_referral->hashref; my $p1 = popurl(1); -print header("$action Advertising source", menubar( + +%><%= include('/elements/header.html', "$action Advertising source", menubar( 'Main Menu' => popurl(2), 'View all advertising sources' => popurl(2). "browse/part_referral.cgi", -)); +)) %> + +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +<% } %> -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); +

    -print qq!!; + -print qq!!; +<% #print "Referral #", $hashref->{refnum} ? $hashref->{refnum} : "(NEW)"; +%> -print < -END +Advertising source -print qq!
    !; +
    +"> -print < - - -END + -%> +<%= include('/elements/footer.html') %> diff --git a/httemplate/edit/part_virtual_field.cgi b/httemplate/edit/part_virtual_field.cgi index fb10321e8..7b2c768a7 100644 --- a/httemplate/edit/part_virtual_field.cgi +++ b/httemplate/edit/part_virtual_field.cgi @@ -1,4 +1,3 @@ - <% my ($vfieldpart, $part_virtual_field); @@ -21,12 +20,14 @@ if ( $cgi->param('error') ) { my $action = $part_virtual_field->vfieldpart ? 'Edit' : 'Add'; my $p1 = popurl(1); -print header("$action Virtual Field Definition", ''); -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); -%> +%><%= include('/elements/header.html', "$action Virtual Field Definition") %> + +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +

    +<% } %> +
    @@ -83,10 +84,8 @@ Field #<%=$vfieldpart or "(NEW)"%>

    -

    +
    If you don't understand what check_block and list_source mean, LEAVE THEM BLANK. We mean it. - - - +<%= include('/elements/footer.html') %> diff --git a/httemplate/edit/process/access_group.html b/httemplate/edit/process/access_group.html new file mode 100644 index 000000000..e8c6d07b1 --- /dev/null +++ b/httemplate/edit/process/access_group.html @@ -0,0 +1,5 @@ +<%= include( 'elements/process.html', + 'table' => 'access_group', + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/edit/process/access_user.html b/httemplate/edit/process/access_user.html new file mode 100644 index 000000000..a6c2a36b1 --- /dev/null +++ b/httemplate/edit/process/access_user.html @@ -0,0 +1,8 @@ +<%= include( 'elements/process.html', + 'table' => 'access_user', + 'viewall_dir' => 'browse', + 'process_m2m' => { 'link_table' => 'access_usergroup', + 'target_table' => 'access_group', + }, + ) +%> diff --git a/httemplate/edit/process/agent_type.cgi b/httemplate/edit/process/agent_type.cgi index 516594573..fd8ca8833 100755 --- a/httemplate/edit/process/agent_type.cgi +++ b/httemplate/edit/process/agent_type.cgi @@ -11,43 +11,24 @@ my $new = new FS::agent_type ( { my $error; if ( $typenum ) { - $error=$new->replace($old); + $error = $new->replace($old); } else { - $error=$new->insert; - $typenum=$new->getfield('typenum'); + $error = $new->insert; + $typenum = $new->getfield('typenum'); } +#$error ||= $new->process_m2m( ); if ( $error ) { $cgi->param('error', $error); print $cgi->redirect(popurl(2). "agent_type.cgi?". $cgi->query_string ); } else { - #false laziness w/ edit/process/part_svc.cgi - foreach my $part_pkg (qsearch('part_pkg',{})) { - my($pkgpart)=$part_pkg->getfield('pkgpart'); - - my($type_pkgs)=qsearchs('type_pkgs',{ - 'typenum' => $typenum, - 'pkgpart' => $pkgpart, - }); - if ( $type_pkgs && ! $cgi->param("pkgpart$pkgpart") ) { - my($d_type_pkgs)=$type_pkgs; #need to save $type_pkgs for below. - $error=$d_type_pkgs->delete; - die $error if $error; - - } elsif ( $cgi->param("pkgpart$pkgpart") - && ! $type_pkgs - ) { - #ok to clobber it now (but bad form nonetheless?) - $type_pkgs=new FS::type_pkgs ({ - 'typenum' => $typenum, - 'pkgpart' => $pkgpart, - }); - $error= $type_pkgs->insert; - die $error if $error; - } - - } + my $error = $new->process_m2m( + 'link_table' => 'type_pkgs', + 'target_table' => 'part_pkg', + 'params' => scalar($cgi->Vars) + ); + die $error if $error; print $cgi->redirect(popurl(3). "browse/agent_type.cgi"); } diff --git a/httemplate/edit/process/cust_bill_pay.cgi b/httemplate/edit/process/cust_bill_pay.cgi index 0025b16b5..fc668bb07 100755 --- a/httemplate/edit/process/cust_bill_pay.cgi +++ b/httemplate/edit/process/cust_bill_pay.cgi @@ -33,11 +33,19 @@ if ($cgi->param('invnum') =~ /^Refund$/) { my $error = $new->insert; if ( $error ) { + $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "cust_bill_pay.cgi?". $cgi->query_string ); + %><%= $cgi->redirect(popurl(2). "cust_bill_pay.cgi?". $cgi->query_string ) %><% + } else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); -} + #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); + + %><%= header('Payment application sucessful') %> + + + -%> +<% } %> diff --git a/httemplate/edit/process/cust_credit.cgi b/httemplate/edit/process/cust_credit.cgi index 85bfd4489..6a4ef194a 100755 --- a/httemplate/edit/process/cust_credit.cgi +++ b/httemplate/edit/process/cust_credit.cgi @@ -13,14 +13,23 @@ my $error = $new->insert; if ( $error ) { $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "cust_credit.cgi?". $cgi->query_string ); + + %><%= $cgi->redirect(popurl(2). "cust_credit.cgi?". $cgi->query_string ) %><% + } else { + if ( $cgi->param('apply') eq 'yes' ) { my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum }) or die "unknown custnum $custnum"; $cust_main->apply_credits; } - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); -} + #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); + + %><%= header('Credit sucessful') %> + + + -%> +<% } %> diff --git a/httemplate/edit/process/cust_credit_bill.cgi b/httemplate/edit/process/cust_credit_bill.cgi index 28f892f62..3b759536f 100755 --- a/httemplate/edit/process/cust_credit_bill.cgi +++ b/httemplate/edit/process/cust_credit_bill.cgi @@ -34,11 +34,19 @@ if ($cgi->param('invnum') =~ /^Refund$/) { my $error = $new->insert; if ( $error ) { + $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "cust_credit_bill.cgi?". $cgi->query_string ); + %><%= $cgi->redirect(popurl(2). "cust_credit_bill.cgi?". $cgi->query_string ) %><% + } else { - print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); -} + #print $cgi->redirect(popurl(3). "view/cust_main.cgi?$custnum"); + + %><%= header('Credit application sucessful') %> + + + -%> +<% } %> diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index 83ff6f728..59ad35ee4 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -2,10 +2,21 @@ # options example... # + ### + ##req + ## # 'table' => + # # #? 'primary_key' => #required when the dbdef doesn't know...??? # #? 'fields' => [] + # + ### + ##opt + ### # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' + # 'process_m2m' => { 'link_table' => 'link_table_name', + # 'target_table' => 'target_table_name', + # }. my(%opt) = @_; @@ -31,12 +42,16 @@ if ( $pkeyvalue ) { $error = $new->replace($old); } else { - warn $new; $error = $new->insert; - warn $error; $pkeyvalue = $new->getfield($pkey); } + if ( !$error && $opt{'process_m2m'} ) { + $error = $new->process_m2m( %{ $opt{'process_m2m'} }, + 'params' => scalar($cgi->Vars), + ); + } + if ( $error ) { $cgi->param('error', $error); print $cgi->redirect(popurl(2). "$table.html?". $cgi->query_string ); diff --git a/httemplate/edit/svc_domain.cgi b/httemplate/edit/svc_domain.cgi index ca0e3398f..f47ba0a8f 100755 --- a/httemplate/edit/svc_domain.cgi +++ b/httemplate/edit/svc_domain.cgi @@ -1,4 +1,3 @@ - <% my($svcnum, $pkgnum, $svcpart, $kludge_action, $purpose, $part_svc, @@ -66,33 +65,31 @@ my $otaker = getotaker; my $domain = $svc_domain->domain; my $p1 = popurl(1); -print header("$action $svc", ''); - -print qq!Error: !, $cgi->param('error'), - "" - if $cgi->param('error'); - -print < - - - -END - -print qq!New!; -print qq!
    Transfer!; - -print <Domain -
    Purpose/Description: -

    - - - -END %> + +<%= include('/elements/header.html', "$action $svc", '') %> + +<% if ( $cgi->param('error') ) { %> + Error: <%= $cgi->param('error') %> +<% } %> + +

    + + + + +>New +
    + +>Transfer + +

    Domain + +
    Purpose/Description: + +

    + +

    + +<%= include('/elements/footer.html') %> diff --git a/httemplate/elements/checkboxes-table.html b/httemplate/elements/checkboxes-table.html new file mode 100644 index 000000000..d26ebef35 --- /dev/null +++ b/httemplate/elements/checkboxes-table.html @@ -0,0 +1,110 @@ +<% + + ## + # required + ## + # 'target_table' => 'table_name', + # 'link_table' => 'table_name', + # + # 'name_col' => 'name_column', + # #or + # 'name_callback' => sub { }, + # + ## + # recommended (required?) + ## + # 'source_obj' => $obj, + # #or? + # #'source_table' => 'table_name', + # #'sourcenum' => '4', #current value of primary key in source_table + # # # (none is okay, just pass it if you have it) + ## + # optional + ## + # 'disable-able' => 1, + + my( %opt ) = @_; + + my $target_pkey = dbdef->table($opt{'target_table'})->primary_key; + + my( $source_pkey, $sourcenum, $source_obj ); + if ( $opt{'source_obj'} ) { + + $source_obj = $opt{'source_obj'}; + #$source_table = $source_obj->dbdef_table->table; + $source_pkey = $source_obj->dbdef_table->primary_key; + $sourcenum = $source_obj->$source_pkey(); + + } else { + + #$source_obj? + $source_pkey = $opt{'source_table'} + ? dbdef->table($opt{'source_table'})->primary_key + : ''; + $sourcenum = $opt{'sourcenum'}; + } + + my $hashref = $opt{'hashref'} || {}; + + my $extra_sql = ''; + + if ( $opt{'disable-able'} ) { + $hashref->{'disabled'} = ''; + + $extra_sql .= ( $sourcenum && $source_pkey ) + ? "OR $source_pkey = $sourcenum" + : ''; + } + +%> + +<% foreach my $target_obj ( + qsearch({ 'table' => $opt{'target_table'}, + 'hashref' => $hashref, + 'select' => $opt{'target_table'}. '.*', + 'addl_from' => "LEFT JOIN $opt{'link_table'} USING ( $target_pkey )", + 'extra_sql' => $extra_sql, + }) + ) { + + my $targetnum = $target_obj->$target_pkey(); +%> + + $sourcenum, + $target_pkey => $targetnum, + }) + ? 'CHECKED ' + : '' + %> VALUE="ON"> + + <% if ( $opt{'target_link'} ) { %> + + <% + + } + %><%= $targetnum %>: + + <% if ( $opt{'name_callback'} ) { %> + + <%= &{ $opt{'name_callback'} }( $target_obj ) %><%= $opt{'target_link'} ? '' : '' %> + + <% } else { + my $name_col = $opt{'name_col'}; + %> + + <%= $target_obj->$name_col() %><%= $opt{'target_link'} ? '' : '' %> + + <% } %> + + <% if ( $opt{'disable-able'} ) { %> + + <%= $target_obj->disabled =~ /^Y/i ? ' (DISABLED)' : '' %> + + <% } %> + +
    + +<% } %> + diff --git a/httemplate/elements/cssexpr.js b/httemplate/elements/cssexpr.js new file mode 100644 index 000000000..c434d8da0 --- /dev/null +++ b/httemplate/elements/cssexpr.js @@ -0,0 +1,66 @@ +function constExpression(x) { + return x; +} + +function simplifyCSSExpression() { + try { + var ss,sl, rs, rl; + ss = document.styleSheets; + sl = ss.length + + for (var i = 0; i < sl; i++) { + simplifyCSSBlock(ss[i]); + } + } + catch (exc) { + //alert("Got an error while processing css. The page should still work but might be a bit slower"); + throw exc; + } +} + +function simplifyCSSBlock(ss) { + var rs, rl; + + for (var i = 0; i < ss.imports.length; i++) + simplifyCSSBlock(ss.imports[i]); + + if (ss.cssText.indexOf("expression(constExpression(") == -1) + return; + + rs = ss.rules; + rl = rs.length; + for (var j = 0; j < rl; j++) + simplifyCSSRule(rs[j]); + +} + +function simplifyCSSRule(r) { + var str = r.style.cssText; + var str2 = str; + var lastStr; + do { + lastStr = str2; + str2 = simplifyCSSRuleHelper(lastStr); + } while (str2 != lastStr) + + if (str2 != str) + r.style.cssText = str2; +} + +function simplifyCSSRuleHelper(str) { + var i, i2; + i = str.indexOf("expression(constExpression("); + if (i == -1) return str; + i2 = str.indexOf("))", i); + var hd = str.substring(0, i); + var tl = str.substring(i2 + 2); + var exp = str.substring(i + 27, i2); + var val = eval(exp) + return hd + val + tl; +} + +if (/msie/i.test(navigator.userAgent) && window.attachEvent != null) { + window.attachEvent("onload", function () { + simplifyCSSExpression(); + }); +} diff --git a/httemplate/elements/footer.html b/httemplate/elements/footer.html index 6029d7637..32d121996 100644 --- a/httemplate/elements/footer.html +++ b/httemplate/elements/footer.html @@ -1,2 +1,5 @@ + + + diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index 10e4e40f1..49814577e 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -2,20 +2,285 @@ my($title, $menubar) = ( shift, shift ); my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc. my $head = @_ ? shift : ''; #$head is for things that go in the section + my $conf = new FS::Conf; %> - - - - <%= $title %> - - - - - <%= $head %> - - > + + + + + <%= $title %> + + + + + + + + <% + + tie my %report_menu, 'Tie::IxHash', + 'Report one' => [ 'there', 'theretip' ], + 'Report too' => [ 'here', 'heretip' ], + ; + + tie my %config_employees, 'Tie::IxHash', + 'View/Edit employees' => [ $fsurl.'browse/access_user.html', 'Setup internal users' ], + 'View/Edit employee groups' => [ $fsurl.'browse/access_group.html', 'Employee groups allow you to control access to the backend' ], + ; + + tie my %config_export_svc_pkg, 'Tie::IxHash', + 'View/Edit exports' => [ $fsurl.'browse/part_export.cgi', 'Provisioning services to external machines, databases and APIs' ], + 'View/Edit service definitions' => [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ], + 'View/Edit package definitions' => [ $fsurl.'browse/part_pkg.cgi', 'One or more services are grouped together into a package and given pricing information. Customers purchase packages, not services' ], + 'View/Edit package classes' => [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for reporting and convenience purposes.' ], + ; + + tie my %config_agent, 'Tie::IxHash', + 'View/Edit agent types' => [ $fsurl.'browse/agent_type.cgi', 'Agent types define groups of package definitions that you can then assign to particular agents' ], + 'View/Edit agents' => [ $fsurl.'browse/agent.cgi', 'Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type)' ], + ; + + tie my %config_billing, 'Tie::IxHash', + 'View/Edit payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors' ], + 'View/Edit invoice events' => [ $fsurl.'browse/part_bill_event.cgi', 'Actions for overdue invoices' ], + 'View/Edit prepaid cards' => [ $fsurl.'browse/prepay_credit.html', 'View outstanding cards, generate new cards' ], + 'View/Edit call rates and regions' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans, regions and prefixes for VoIP and call billing' ], + 'View/Edit locales and tax rates' => [ $fsurl.'browse/cust_main_county.cgi', 'Change tax rates, or break down a country into states, or a state into counties and assign different tax rates to each' ], + ; + + tie my %config_dialup, 'Tie::IxHash', + 'View/Edit access numbers' => [ $fsurl.'browse/svc_acct_pop.cgi', 'Points of Presence' ], + ; + + tie my %config_broadband, 'Tie::IxHash', + 'View/Edit routers' => [ $fsurl.'browse/router.cgi', 'Broadband access routers' ], + 'View/Edit address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ], + ; + + tie my %config_misc, 'Tie::IxHash', + 'View/Edit advertising sources' => [ $fsurl.'browse/part_referral.cgi', 'Where a customer heard about your service. Tracked for informational purposes' ], + 'View/Edit virtual fields' => [ $fsurl.'browse/part_virtual_field.cgi', 'Locally defined fields', ], + 'View/Edit message catalog' => [ $fsurl.'browse/msgcat.cgi', 'Change error messages and other customizable labels' ], + 'View/Edit inventory classes and inventory' => [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ], + ; + + tie my %config_menu, 'Tie::IxHash', + 'Settings' => [ $fsurl.'config/config-view.cgi', 'XXXconfigittip' ], + 'separator' => '', #its a separator! + 'Employees' => [ \%config_employees, 'XXXtooltip' ], + 'Provisioning, services and packages' + => [ \%config_export_svc_pkg, 'XXXtootip' ], + 'Resellers' => [ \%config_agent, 'XXXtootip' ], + 'Billing' => [ \%config_billing, 'XXXtootip' ], + 'Dialup' => [ \%config_dialup, 'XXXtootip' ], + 'Fixed (username-less) broadband' + => [ \%config_broadband, 'XXXtootip' ], + 'Miscellaneous' => [ \%config_misc, 'XXXtootip' ], + ; + + tie my %menu, 'Tie::IxHash', + 'Home' => [ $fsurl, 'hometip', ], + 'Top item one' => [ 'nowhere_yet', 'nowheretip', ], + 'Top item too' => [ 'nowhere_yet_either', 'eithertip', ], + 'Reports' => [ \%report_menu, 'reportmenutip' ], + 'Configuration' => [ \%config_menu, 'configmenutip' ], + ; + + use vars qw($gmenunum); + $gmenunum = 0; + + sub submenu { + my($submenu, $title) = @_; + my $menunum = $gmenunum++; + + #return two args: html, menuname + + "var myMenu$menunum = new WebFXMenu;\n". + #"myMenu$menunum.useAutoPosition = true;\n". + "myMenu$menunum.emptyText = '$title';\n". + + ( + join("\n", map { + + if ( !ref( $submenu->{$_} ) ) { + + "myMenu$menunum.add(new WebFXMenuSeparator());"; + + } else { + + my($url_or_submenu, $tooltip ) = @{ $submenu->{$_} }; + if ( ref($url_or_submenu) ) { + + my($subhtml, $submenuname ) = submenu($url_or_submenu, $_); #mmm, recursion + + "$subhtml\n". + "myMenu$menunum.add(new WebFXMenuItem(\"$_\", null, \"$tooltip\", $submenuname ));"; + + } else { + + "myMenu$menunum.add(new WebFXMenuItem(\"$_\", \"$url_or_submenu\", \"$tooltip\" ));"; + + } + + } + + } keys %$submenu ) + ). "\n". + "myMenu$menunum.width = 224\n", + + "myMenu$menunum"; + + } + + %> + + + + + <%= $head %> + + + STYLE="margin-top:0; margin-bottom:0; margin-left:0; margin-right:0"> + + + + + + + + + +
    + freeside + + <%= $conf->config('company_name') %> Billing + Logged in as <%= getotaker %> 
    Preferences 

    +
    + + + + + <% if ( $conf->config('ticket_system') eq 'RT_Internal' ) { %> + <% eval "use RT;"; %> + + + <% } %> + + +
    + + Freeside v<%= $FS::VERSION %>
    + Documentation
    +
    +
    + + RT v<%= $RT::VERSION %>
    +
    Documentation
    +
    +
    + +
    + + + + + + + + + + + +
    + +
    + +
    +
    +
    + + +
    +
    +
    + + +
    +
    + + + + + + + + + + + + + + +<% } %> diff --git a/httemplate/elements/xmenu.css b/httemplate/elements/xmenu.css new file mode 100644 index 000000000..5bb8a0deb --- /dev/null +++ b/httemplate/elements/xmenu.css @@ -0,0 +1,185 @@ + +.webfx-menu, .webfx-menu * { + /* + Set the box sizing to content box + in the future when IE6 supports box-sizing + there will be an issue to fix the sizes + + There is probably an issue with IE5 mac now + because IE5 uses content-box but the script + assumes all versions of IE uses border-box. + + At the time of this writing mozilla did not support + box-sizing for absolute positioned element. + + Opera only supports content-box + */ + box-sizing: content-box; + -moz-box-sizing: content-box; +} + +.webfx-menu { + position: absolute; + z-index: 100; + visibility: hidden; + width: 154px; + border: 1px solid black; + padding: 1px; + background: white; + filter: progid:DXImageTransform.Microsoft.Shadow(color="#777777", Direction=135, Strength=4) + alpha(Opacity=95); + -moz-opacity: 0.95; + /* a drop shadow would be nice in moz/others too... */ +} + +.webfx-menu-empty { + display: block; + border: 1px solid white; + padding: 2px 5px 2px 5px; + font-size: 11px; + /* font-family: Tahoma, Verdan, Helvetica, Sans-Serif; */ + color: black; +} + +.webfx-menu a { + display: block; + width: expression(constExpression(ieBox ? "100%": "auto")); /* should be ignored by mz and op */ + height: expression(constExpression("1px")); + overflow: visible; + padding: 2px 0px 2px 5px; + font-size: 11px; + font-family: Tahoma, Verdan, Helvetica, Sans-Serif; + text-decoration: none; + vertical-align: center; + color: black; + border: 1px solid white; +} + +.webfx-menu a:visited, +.webfx-menu a:visited:hover { + color: black; +} + +.webfx-menu a:hover { + color: black; + /* background: #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */ + /* background: #ffe6fe; */ + /* background: #ffc2fe; */ + background: #fff2fe; + border: 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/ +} + +.webfx-menu a .arrow { + float: right; + border: 0; + width: 3px; + margin-right: 3px; + margin-top: 4px; +} + +/* separtor */ +.webfx-menu div { + height: 0; + height: expression(constExpression(ieBox ? "2px" : "0")); + border-top: 1px solid #7e0079; /* rgb(120,172,255); */ + border-bottom: 1px solid rgb(234,242,255); + overflow: hidden; + margin: 2px 0px 2px 0px; + font-size: 0mm; +} + +.webfx-menu-bar { + /* i want a vertical bar */ + display: block; + + /* background: rgb(120,172,255);/*rgb(255,128,0);*/ + /* background: #a097ed; */ + background: #000000; + /* border: 1px solid #7E0079; */ + /* border: 1px solid #000000; */ + /* border: none */ + + padding: 2px; + + font-family: Verdana, Helvetica, Sans-Serif; + font-size: 11px; + + /* IE5.0 has the wierdest box model for inline elements */ + padding: expression(constExpression(ie50 ? "0px" : "2px")); +} + +.webfx-menu-bar a, +.webfx-menu-bar a:visited { + /* i want a vertical bar */ + display: block; + + /* border: 1px solid black; /*rgb(0,0,0);/*rgb(255,128,0);*/ + /* border: 1px solid black; /* #ffffff; */ + /* border-bottom: 1px solid black; */ + border-bottom: 1px solid white; + /* border-bottom: 1px solid rgb(0,66,174); + /* border-bottom: 1px solid black; + border-bottom: 1px solid black; + border-bottom: 1px solid black; */ + + padding: 1px 5px 1px 5px; + + /* color: black; */ + color: white; + text-decoration: none; + + /* IE5.0 Does not paint borders and padding on inline elements without a height/width */ + height: expression(constExpression(ie50 ? "17px" : "auto")); +} + +.webfx-menu-bar a:hover { + /* color: black; */ + color: white; + /* background: rgb(120,172,255); */ + /* background: #BC79B8; */ + background: #7E0079; + /* border-left: 1px solid rgb(234,242,255); + border-right: 1px solid rgb(0,66,174); + border-top: 1px solid rgb(234,242,255); + border-bottom: 1px solid rgb(0,66,174); */ +} + +.webfx-menu-bar a .arrow { + border: 0; + float: right; +/* vertical-align: top; */ + width: 3px; + margin-right: 3px; + margin-top: 4px; +} + +.webfx-menu-bar a:active, .webfx-menu-bar a:focus { + -moz-outline: none; + outline: none; + /* + ie does not support outline but ie55 can hide the outline using + a proprietary property on HTMLElement. Did I say that IE sucks at CSS? + */ + ie-dummy: expression(this.hideFocus=true); + + border-left: 1px solid rgb(0,66,174); + border-right: 1px solid rgb(234,242,255); + border-top: 1px solid rgb(0,66,174); + border-bottom: 1px solid rgb(234,242,255); +} + +.webfx-menu-title { + color: black; + /* background: #faf7fa; #f5ebf4; #efdfef; white; #BC79B8; */ + background: #7e0079; +/* border: 1px solid #7e0079; /*rgb(120,172,255);#ff8800;*/ + padding: 3px 1px 3px 6px; + display: block; + font-size: 13px; + font-family: Tahoma, Verdan, Helvetica, Sans-Serif; + text-decoration: none; + color: white; +/* border: 1px solid white; */ + border-bottom: 1px solid white; +} + diff --git a/httemplate/elements/xmenu.js b/httemplate/elements/xmenu.js new file mode 100644 index 000000000..134265f53 --- /dev/null +++ b/httemplate/elements/xmenu.js @@ -0,0 +1,668 @@ +// + + + + <% #include( '/elements/table.html', '#cccccc' ) %> <%= ntable('#cccccc') %> @@ -112,7 +102,7 @@ function achclose() { @@ -173,7 +163,7 @@ function achclose() { @@ -205,5 +195,5 @@ function achclose() {
    - - + +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/cust_bill.cgi b/httemplate/search/cust_bill.cgi deleted file mode 100755 index 5b0538ca3..000000000 --- a/httemplate/search/cust_bill.cgi +++ /dev/null @@ -1,165 +0,0 @@ -<% - -my $conf = new FS::Conf; -my $maxrecords = $conf->config('maxsearchrecordsperpage'); - -my $orderby = ''; #removeme - -my $limit = ''; -$limit .= "LIMIT $maxrecords" if $maxrecords; - -my $offset = $cgi->param('offset') || 0; -$limit .= " OFFSET $offset" if $offset; - -my($total, $tot_amount, $tot_balance); - -my(@cust_bill); -if ( $cgi->keywords ) { - my($query) = $cgi->keywords; - my $owed = "charged - ( select coalesce(sum(amount),0) from cust_bill_pay - where cust_bill_pay.invnum = cust_bill.invnum ) - - ( select coalesce(sum(amount),0) from cust_credit_bill - where cust_credit_bill.invnum = cust_bill.invnum )"; - my @where; - if ( $query =~ /^(OPEN(\d*)_)?(invnum|date|custnum)$/ ) { - my($open, $days, $field) = ($1, $2, $3); - $field = "_date" if $field eq 'date'; - $orderby = "ORDER BY cust_bill.$field"; - push @where, "0 != $owed" if $open; - push @where, "cust_bill._date < ". (time-86400*$days) if $days; - } else { - die "unknown query string $query"; - } - - my $extra_sql = scalar(@where) ? 'WHERE '. join(' AND ', @where) : ''; - - my $statement = "SELECT COUNT(*), sum(charged), sum($owed) - FROM cust_bill $extra_sql"; - my $sth = dbh->prepare($statement) or die dbh->errstr. " doing $statement"; - $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; - - ( $total, $tot_amount, $tot_balance ) = @{$sth->fetchrow_arrayref}; - - @cust_bill = qsearch( - 'cust_bill', - {}, - "cust_bill.*, $owed as owed", - "$extra_sql $orderby $limit" - ); -} else { - $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/; - my $invnum = $2; - @cust_bill = qsearchs('cust_bill', { 'invnum' => $invnum } ); - $total = scalar(@cust_bill); -} - -#if ( scalar(@cust_bill) == 1 ) { -if ( $total == 1 ) { - my $invnum = $cust_bill[0]->invnum; - print $cgi->redirect(popurl(2). "view/cust_bill.cgi?$invnum"); #redirect -} elsif ( scalar(@cust_bill) == 0 ) { -%> - -<% - eidiot("Invoice not found."); -} else { -%> - -<% - - #begin pager - my $pager = ''; - if ( $total != scalar(@cust_bill) && $maxrecords ) { - unless ( $offset == 0 ) { - $cgi->param('offset', $offset - $maxrecords); - $pager .= 'Previous '; - } - my $poff; - my $page; - for ( $poff = 0; $poff < $total; $poff += $maxrecords ) { - $page++; - if ( $offset == $poff ) { - $pager .= qq!$page !; - } else { - $cgi->param('offset', $poff); - $pager .= qq!$page !; - } - } - unless ( $offset + $maxrecords > $total ) { - $cgi->param('offset', $offset + $maxrecords); - $pager .= 'Next '; - } - } - #end pager - - print header("Invoice Search Results", menubar( - 'Main Menu', popurl(2) - )). - "$total matching invoices found
    ". - "\$$tot_balance total balance
    ". - "\$$tot_amount total amount
    ". - "
    $pager". table(). < - - - - - - - -END - - foreach my $cust_bill ( @cust_bill ) { - my($invnum, $owed, $charged, $date ) = ( - $cust_bill->invnum, - sprintf("%.2f", $cust_bill->getfield('owed')), - sprintf("%.2f", $cust_bill->charged), - $cust_bill->_date, - ); - my $pdate = time2str("%b %d %Y", $date); - - my $rowspan = 1; - - my $view = popurl(2). "view/cust_bill.cgi?$invnum"; - print < - - - - -END - my $custnum = $cust_bill->custnum; - my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); - if ( $cust_main ) { - my $cview = popurl(2). "view/cust_main.cgi?". $cust_main->custnum; - my ( $name, $company ) = ( - $cust_main->last. ', '. $cust_main->first, - $cust_main->company, - ); - print <$name - -END - } else { - print <WARNING: couldn't find cust_main.custnum $custnum (cust_bill.invnum $invnum) -END - } - - print ""; - } - $tot_balance = sprintf("%.2f", $tot_balance); - $tot_amount = sprintf("%.2f", $tot_amount); - print "
    + +
    + + +
    + <%= $title %> +

    - <%= $menubar ? "$menubar

    " : '' %> + <%= $menubar !~ /^\s*$/ ? "$menubar

    " : '' %> diff --git a/httemplate/elements/menubar.html b/httemplate/elements/menubar.html index 87a50312c..29facb6b6 100644 --- a/httemplate/elements/menubar.html +++ b/httemplate/elements/menubar.html @@ -2,6 +2,7 @@ my($item, $url, @html); while (@_) { ($item, $url) = splice(@_,0,2); + next if $item =~ /^\s*Main\s+Menu\s*$/i; push @html, qq!$item!; } %> diff --git a/httemplate/elements/select-access_group.html b/httemplate/elements/select-access_group.html new file mode 100644 index 000000000..b05f565ea --- /dev/null +++ b/httemplate/elements/select-access_group.html @@ -0,0 +1,15 @@ +<% + my( $groupnum, %opt ) = @_; + + %opt{'records'} = delete $opt{'access_group'} + if $opt{'access_group'}; + +%><%= include( '/elements/select-table.html', + 'table' => 'access_group', + 'name_col' => 'groupname', + 'value' => $groupnum, + 'empty_label' => '(none)', + #'hashref' => { 'disabled' => '' }, + %opt, + ) +%> diff --git a/httemplate/elements/tr-select-access_group.html b/httemplate/elements/tr-select-access_group.html new file mode 100644 index 000000000..0beec0842 --- /dev/null +++ b/httemplate/elements/tr-select-access_group.html @@ -0,0 +1,22 @@ +<% + my( $groupnum, %opt ) = @_; + + $opt{'access_group'} ||= [ qsearch( 'access_group', {} ) ]; # { disabled=>'' } ) + + #warn "***** tr-select-access_group: \n". Dumper(%opt); +%> + +<% if ( scalar(@{ $opt{'access_group'} }) == 0 ) { %> + + + +<% } else { %> + +
    <%= $opt{'label'} || 'Access group' %> + <%= include( '/elements/select-access_group.html', $groupnum, %opt ) %> +
    CVV2 - (help) + (help)
    ABA/Routing number - (help) + (help)
    BalanceAmountDateContact nameCompany
    $invnum\$$owed\$$charged$pdate$company
    $pager
    ". table(). <       Total
    BalanceTotal
    Amount - \$$tot_balance\$$tot_amount - - - -END - -} - -%> diff --git a/httemplate/search/cust_main-otaker.cgi b/httemplate/search/cust_main-otaker.cgi index 03c2619af..6ac0bde18 100755 --- a/httemplate/search/cust_main-otaker.cgi +++ b/httemplate/search/cust_main-otaker.cgi @@ -1,28 +1,23 @@ - - - Customer Search - - - - Customer Search - -
    -
    - Search for Order taker: - - <% my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_main") - or die dbh->errstr; - $sth->execute() or die $sth->errstr; -# my @otakers = map { $_->[0] } @{$sth->selectall_arrayref}; - %> - -

    +<%= include('/elements/header.html', 'Customer Search' ) %> -

    - - +
    +Search for Order taker: + + +<% my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_main") + or die dbh->errstr; + $sth->execute() or die $sth->errstr; + #my @otakers = map { $_->[0] } @{$sth->fetchall_arrayref}; +%> + + +

    + +

    + +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/cust_main-payinfo.html b/httemplate/search/cust_main-payinfo.html deleted file mode 100755 index b82b610d8..000000000 --- a/httemplate/search/cust_main-payinfo.html +++ /dev/null @@ -1,20 +0,0 @@ - - - Customer Search - - - - Customer Search - -
    -
    - Search for Credit card #: - - - -

    - -

    - - - diff --git a/httemplate/search/cust_main-quickpay.html b/httemplate/search/cust_main-quickpay.html deleted file mode 100755 index 154a64199..000000000 --- a/httemplate/search/cust_main-quickpay.html +++ /dev/null @@ -1,44 +0,0 @@ - - - Quick payment entry - - - - Quick payment entry - -

    - Main Menu

    -
    - - Search for last name: - - using search method: - -

    Search for company: - - using search method: - -

    - -

    - -
    Explanation of search methods: -
      -
    • All - Try all search methods. -
    • Fuzzy - Searches for matches that are close to your text. -
    • Substring - Searches for matches that contain your text. -
    • Exact - Finds exact matches only, but much faster than the other search methods. -
    - - - diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 36ad39da8..8b70ff490 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -220,14 +220,13 @@ if ( scalar(@cust_main) == 1 && ! $cgi->param('referral_custnum') ) { eidiot "No matching customers found!\n"; } else { %> - -<% +<%= include('/elements/header.html', "Customer Search Results", '' ) %> - $total ||= scalar(@cust_main); - print header("Customer Search Results",menubar( - 'Main Menu', popurl(2) - )), "$total matching customers found "; + <% $total ||= scalar(@cust_main); %> + <%= $total %> matching customers found + + <% #begin pager my $pager = ''; if ( $total != scalar(@cust_main) && $maxrecords ) { @@ -368,12 +367,14 @@ END my $pcompany = $company ? qq!$company! : ' '; - print < + - $custnum - $last, $first - $pcompany -END + ><%= $custnum %> + ><%= "$last, $first" %> + ><%= $pcompany %> + + <% if ( defined dbdef->table('cust_main')->column('ship_last') ) { my($ship_last,$ship_first,$ship_company)=( $cust_main->ship_last || $cust_main->getfield('last'), @@ -383,15 +384,18 @@ END my $pship_company = $ship_company ? qq!$ship_company! : ' '; - print <$ship_last, $ship_first - $pship_company -END - } + %> - foreach my $addl_col ( @addl_cols ) { - print ""; - if ( $addl_col eq 'tickets' ) { + ><%= "$ship_last, $ship_first" %> + ><%= $pship_company %> + + <% } + + foreach my $addl_col ( @addl_cols ) { %> + + ALIGN=right> + + <% if ( $addl_col eq 'tickets' ) { if ( @custom_priorities ) { print &itable('', 0); foreach my $priority ( @custom_priorities, '' ) { @@ -461,10 +465,14 @@ END } print ""; } + + %> - print "$pager"; + <%= $pager %> -} + <%= include('/elements/footer.html') %> + +<% } #undef $cache; #does this help? diff --git a/httemplate/search/cust_pay.html b/httemplate/search/cust_pay.html deleted file mode 100755 index 6414cf771..000000000 --- a/httemplate/search/cust_pay.html +++ /dev/null @@ -1,18 +0,0 @@ - - - Check # Search - - - - Check # Search - -

    -
    - Search for check #: - - -

    -
    - - - diff --git a/httemplate/search/cust_pkg_report.cgi b/httemplate/search/cust_pkg_report.cgi index 412c3f79d..d9aada5f4 100755 --- a/httemplate/search/cust_pkg_report.cgi +++ b/httemplate/search/cust_pkg_report.cgi @@ -1,23 +1,22 @@ - - - Packages - - -

    Packages

    -
    - - Return packages with next bill date:

    - - <%= include( '/elements/tr-input-beginning_ending.html' ) %> - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - ) - %> -
    -
    +<%= include('/elements/header.html', 'Packages' ) %> -
    +
    + - - +Return packages with next bill date: +

    + + <%= include( '/elements/tr-input-beginning_ending.html' ) %> + <%= include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + ) + %> +
    + +
    + + +
    + +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/report_cust_bill.html b/httemplate/search/report_cust_bill.html index a7be76689..f1b7bfa14 100644 --- a/httemplate/search/report_cust_bill.html +++ b/httemplate/search/report_cust_bill.html @@ -1,28 +1,28 @@ - - Invoice report criteria - - -

    Invoice report criteria

    -
    - - - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - 'label' => 'Invoices for agent: ', - ) - %> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> - - - - - - - - -
    Show only open invoices
    Show only the single most recent invoice per-customer
    -
    -
    - - +<%= include('/elements/header.html', 'Invoice report criteria' ) %> +
    + + + + <%= include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'Invoices for agent: ', + ) + %> + <%= include( '/elements/tr-input-beginning_ending.html' ) %> + + + + + + + + +
    Show only open invoices
    Show only the single most recent invoice per-customer
    + +
    + + +
    + +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/report_cust_credit.html b/httemplate/search/report_cust_credit.html index 56bbd0ac0..8ca52dc9a 100644 --- a/httemplate/search/report_cust_credit.html +++ b/httemplate/search/report_cust_credit.html @@ -1,36 +1,38 @@ - - - Credit report criteria - - -

    Credit report criteria

    -
    - - - - +<%= include('/elements/header.html', 'Credit report' ) %> + + + + +
    Credits by employee:
    + + + <% my $sth = dbh->prepare("SELECT DISTINCT otaker FROM cust_credit") or die dbh->errstr; $sth->execute or die $sth->errstr; my @otakers = map { $_->[0] } @{$sth->fetchall_arrayref}; %> - - - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - 'label' => 'for agent: ', - ) - %> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> -
    Credits by employee: -
    -
    -
    - - + + + + + <%= include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + <%= include( '/elements/tr-input-beginning_ending.html' ) %> + + +
    + + + + +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/report_cust_pay.html b/httemplate/search/report_cust_pay.html index 5d8b74e77..8adf7dc13 100644 --- a/httemplate/search/report_cust_pay.html +++ b/httemplate/search/report_cust_pay.html @@ -1,38 +1,43 @@ - - - Payment report criteria - - -

    Payment report criteria

    -
    - - - - - - - <%= include( '/elements/tr-select-agent.html', - $cgi->param('agentnum'), - 'label' => 'for agent: ', - ) - %> - <%= include( '/elements/tr-input-beginning_ending.html' ) %> -
    Payments of type: -
    -
    -
    - - +<%= include('/elements/header.html', 'Payment report' ) %> + +
    + + + + + + + + + + <%= include( '/elements/tr-select-agent.html', + $cgi->param('agentnum'), + 'label' => 'for agent: ', + ) + %> + + <%= include( '/elements/tr-input-beginning_ending.html' ) %> + +
    Payments of type: + +
    + +
    + + +
    + +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/report_prepaid_income.html b/httemplate/search/report_prepaid_income.html index 57c318eba..4359918f9 100644 --- a/httemplate/search/report_prepaid_income.html +++ b/httemplate/search/report_prepaid_income.html @@ -1,13 +1,13 @@ - - - Prepaid Income (Unearned Revenue) Report - - - - - - -

    Prepaid Income (Unearned Revenue) Report

    +<%= include('/elements/header.html', 'Prepaid Income (Unearned Revenue) Report', + '', + '', + ' + + + + ' +) %> +
    @@ -32,8 +32,6 @@ }); - - - -
    + +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/report_tax.html b/httemplate/search/report_tax.html index 7a8ecd4f0..bdeb8e237 100755 --- a/httemplate/search/report_tax.html +++ b/httemplate/search/report_tax.html @@ -1,40 +1,35 @@ - - - Tax Report Criteria - - -

    Tax Report Criteria

    - - -
    - - <%= include( '/elements/tr-select-agent.html' ) %> - - <%= include( '/elements/tr-input-beginning_ending.html' ) %> - - <% my $conf = new FS::Conf; - if ( $conf->exists('enable_taxclasses') ) { - %> - - - - - <% } %> - - <% my @pkg_class = qsearch('pkg_class', {}); - if ( @pkg_class ) { - %> - - - - - <% } %> - -
    Show tax classes
    Show package classes
    - -
    -
    - - - +<%= include('/elements/header.html', 'Tax Report' ) %> +
    + + + + <%= include( '/elements/tr-select-agent.html' ) %> + + <%= include( '/elements/tr-input-beginning_ending.html' ) %> + + <% my $conf = new FS::Conf; + if ( $conf->exists('enable_taxclasses') ) { + %> + + + + + <% } %> + + <% my @pkg_class = qsearch('pkg_class', {}); + if ( @pkg_class ) { + %> + + + + + <% } %> + +
    Show tax classes
    Show package classes
    + +
    + +
    + +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/sqlradius.html b/httemplate/search/sqlradius.html index 8f4878dbc..645505101 100644 --- a/httemplate/search/sqlradius.html +++ b/httemplate/search/sqlradius.html @@ -1,9 +1,5 @@ -<%= include( '/elements/header.html', 'Search RADIUS sessions', '', '', ' - - - - -') %> +<%= include( '/elements/header.html', 'Search RADIUS sessions' ) %> +
    <% #include( '/elements/table.html' ) %> <%= ntable('#cccccc') %> @@ -47,48 +43,10 @@ <% } %> - - From: - - - - - - - - m/d/y - - - To: - - - - - - - - m/d/y -
    (leave one or both dates blank for an open-ended search) - - +<%= include( '/elements/tr-input-beginning_ending.html' ) %> +
    - - - +<%= include('/elements/footer.html') %> diff --git a/httemplate/search/svc_acct.html b/httemplate/search/svc_acct.html deleted file mode 100755 index c504c2f34..000000000 --- a/httemplate/search/svc_acct.html +++ /dev/null @@ -1,19 +0,0 @@ - - - Account Search - - - - Account Search - -

    -
    - Search for username: - - -

    - -

    - - - diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi index f261ea9f3..b02eea8bd 100755 --- a/httemplate/search/svc_domain.cgi +++ b/httemplate/search/svc_domain.cgi @@ -61,7 +61,7 @@ my $link_cust = sub { $svc_x->custnum ? [ "${p}view/cust_main.cgi?", 'custnum' ] : ''; }; -%><%= include ('elements/search.html', +%><%= include( 'elements/search.html', 'title' => "Domain Search Results", 'name' => 'domains', 'query' => $sql_query, diff --git a/httemplate/search/svc_domain.html b/httemplate/search/svc_domain.html deleted file mode 100755 index b759102f4..000000000 --- a/httemplate/search/svc_domain.html +++ /dev/null @@ -1,19 +0,0 @@ - - - Domain Search - - - - Domain Search - -

    -
    - Search for domain: - - -

    - -

    - - - diff --git a/httemplate/search/svc_external.cgi b/httemplate/search/svc_external.cgi index 8dbb949c8..7968f3c43 100755 --- a/httemplate/search/svc_external.cgi +++ b/httemplate/search/svc_external.cgi @@ -38,17 +38,18 @@ if ( $query eq 'svcnum' ) { } if ( scalar(@svc_external) == 1 ) { - print $cgi->redirect(popurl(2). "view/svc_external.cgi?". $svc_external[0]->svcnum); - #exit; + + %><%= $cgi->redirect(popurl(2). "view/svc_external.cgi?". $svc_external[0]->svcnum) %><% + } elsif ( scalar(@svc_external) == 0 ) { -%> - -<% - eidiot "No matching external services found!\n"; -} else { -%> - -<%= include("/elements/header.html","External Search Results",'') %> + + %><%= include('/elements/header.html', 'External Search Results' ) %> + + No matching external services found + +<% } else { + + %><%= include('/elements/header.html', 'External Search Results', '') %> <%= scalar(@svc_external) %> matching external services found diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html index ece1b62bb..32e0ee1fc 100755 --- a/httemplate/view/cust_main/packages.html +++ b/httemplate/view/cust_main/packages.html @@ -67,7 +67,7 @@ foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { - -
    > + > <%=$pkg->{pkgnum}%>: <%=$pkg->{pkg}%> - <%=$pkg->{comment}%>
    <% unless ($pkg->{cancel}) { %> @@ -75,7 +75,7 @@ foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { ( <%=pkg_dates_link($pkg)%> | <%=pkg_customize_link($pkg,$cust_main->custnum)%> ) <% } %>
    > + > <% diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html index a0bec3906..f0cd993ff 100644 --- a/httemplate/view/cust_main/payment_history.html +++ b/httemplate/view/cust_main/payment_history.html @@ -58,7 +58,10 @@ <% } %> -
    Post credit +
    + +Post credit +
    <% @@ -115,8 +118,10 @@ foreach my $cust_pay ($cust_main->cust_pay) { #completely unapplied $pre = 'Unapplied '; $post = ''; - $apply = qq! (apply)'; + $apply = qq! (apply)!; + } elsif ( scalar(@cust_bill_pay) == 1 && scalar(@cust_pay_refund) == 0 && $cust_pay->unapplied == 0 ) { @@ -151,8 +156,9 @@ foreach my $cust_pay ($cust_main->cust_pay) { $desc .= '  '. '$'. $cust_pay->unapplied. ' unapplied'. - qq! (apply)'. + qq! (apply)!. '
    '; } } @@ -264,8 +270,9 @@ foreach my $cust_credit ($cust_main->cust_credit) { #completely unapplied $pre = 'Unapplied '; $post = ''; - $apply = qq! (apply)'; + $apply = qq! (apply)!; } elsif ( scalar(@cust_credit_bill) == 1 && scalar(@cust_credit_refund) == 0 && $cust_credit->credited == 0 ) { @@ -299,8 +306,9 @@ foreach my $cust_credit ($cust_main->cust_credit) { if ( $cust_credit->credited > 0 ) { $desc .= '  $'. $cust_credit->credited. ' unapplied'. - qq! (apply)'. + qq! (apply)!. '
    '; } } -- cgit v1.2.1 From 6d777ed1fafabab8c308c9ffa24f1dd48f33a9a5 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 15 May 2006 11:05:04 +0000 Subject: more ACL and re-skinning work, now with RT! --- httemplate/elements/freeside.css | 15 ++++++ httemplate/elements/header.html | 21 +++++--- httemplate/elements/xmenu.css | 16 ++++-- httemplate/search/cust_main.cgi | 9 +++- rt/FREESIDE_MODIFIED | 3 ++ rt/html/Elements/FreesideNewCust | 3 ++ rt/html/Elements/FreesideSearch | 10 ++++ rt/html/Elements/Header | 10 ++-- rt/html/Elements/PageLayout | 25 ++++++--- rt/html/Elements/SimpleSearch | 10 +++- rt/html/Elements/Tabs | 8 ++- rt/html/Elements/TitleBoxStart | 2 +- rt/html/NoAuth/webrt.css | 114 ++++++++++++++++++++++++++++----------- rt/html/Search/Bulk.html | 2 +- 14 files changed, 188 insertions(+), 60 deletions(-) create mode 100644 httemplate/elements/freeside.css create mode 100644 rt/html/Elements/FreesideNewCust create mode 100644 rt/html/Elements/FreesideSearch diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css new file mode 100644 index 000000000..5908e6aab --- /dev/null +++ b/httemplate/elements/freeside.css @@ -0,0 +1,15 @@ +* { + font-family: Arial, Verdana, Helvetica, sans-serif; +} + +A:link IMG, A:visited { border-style: none } +A:focus {text-decoration: underline } + +a:link, a:visited { + /* text-decoration: none; */ + color: #000000; +} +/* a:hover { text-decoration: underline } */ + +/* a:focus { background-color: #ccccee } */ + diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index 49814577e..0eb5695ce 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -16,6 +16,7 @@ + <% tie my %report_menu, 'Tie::IxHash', @@ -183,10 +184,16 @@ + + @@ -200,7 +207,7 @@ freeside @@ -248,13 +255,13 @@ diff --git a/httemplate/elements/xmenu.css b/httemplate/elements/xmenu.css index 5bb8a0deb..60881b813 100644 --- a/httemplate/elements/xmenu.css +++ b/httemplate/elements/xmenu.css @@ -55,9 +55,14 @@ border: 1px solid white; } -.webfx-menu a:visited, -.webfx-menu a:visited:hover { +.webfx-menu a:visited { color: black; + border: 1px solid white; +} + +.webfx-menu a:hover { + color: black; + border: 1px solid #7e0079; } .webfx-menu a:hover { @@ -98,11 +103,12 @@ /* border: 1px solid #7E0079; */ /* border: 1px solid #000000; */ /* border: none */ + color: white; padding: 2px; font-family: Verdana, Helvetica, Sans-Serif; - font-size: 11px; + /* font-size: 11px; */ /* IE5.0 has the wierdest box model for inline elements */ padding: expression(constExpression(ie50 ? "0px" : "2px")); @@ -132,6 +138,10 @@ height: expression(constExpression(ie50 ? "17px" : "auto")); } +.webfx-menu-bar a:link { + color: white; +} + .webfx-menu-bar a:hover { /* color: black; */ color: white; diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 8b70ff490..733e5dc15 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -186,6 +186,13 @@ if ( $cgi->param('browse') push @cust_main, @{&companysearch}; } + if ( $cgi->param('search_cust') ) { + $sortby = \*company_sort; + $orderby = "ORDER BY LOWER(company || ' ' || last || ' ' || first )"; + warn "smart searching for: ". $cgi->param('search_cust'); + push @cust_main, smart_search( 'search' => $cgi->param('search_cust') ); + } + @cust_main = grep { $_->ncancelled_pkgs || ! $_->all_pkgs } @cust_main if ! $cgi->param('cancelled') && ( @@ -313,7 +320,7 @@ END $conf->config('ticket_system-custom_priority_field-values'); } - print "

    ". $pager. &table(). <
    ". $pager. include('/elements/table-grid.html'). <
    diff --git a/rt/FREESIDE_MODIFIED b/rt/FREESIDE_MODIFIED index a013b832c..82f0ff2e6 100644 --- a/rt/FREESIDE_MODIFIED +++ b/rt/FREESIDE_MODIFIED @@ -21,3 +21,6 @@ html/Ticket/Elements/ShowCustomers html/Ticket/ModifyCustomers.html html/NoAuth/images/small-logo.png html/NoAuth/webrt.css + +html/Elements/TitleBoxStart +html/Search/Bulk.html diff --git a/rt/html/Elements/FreesideNewCust b/rt/html/Elements/FreesideNewCust new file mode 100644 index 000000000..851c66425 --- /dev/null +++ b/rt/html/Elements/FreesideNewCust @@ -0,0 +1,3 @@ + + diff --git a/rt/html/Elements/FreesideSearch b/rt/html/Elements/FreesideSearch new file mode 100644 index 000000000..f0efb60d4 --- /dev/null +++ b/rt/html/Elements/FreesideSearch @@ -0,0 +1,10 @@ + + + +  + diff --git a/rt/html/Elements/Header b/rt/html/Elements/Header index a2563fee3..bad8b58eb 100644 --- a/rt/html/Elements/Header +++ b/rt/html/Elements/Header @@ -67,7 +67,7 @@ function hideshow(num) { <& /Elements/Callback, _CallbackName => 'Head', %ARGS &> - -
    - <%= $conf->config('company_name') %> Billing + <%= $conf->config('company_name') || 'ExampleCo' %> Logged in as <%= getotaker %> 
    Preferences 

    - +
    - +
    (bill) name
    +
    - +
    freeside<% &RT::URI::freeside::FreesideGetConfig('company_name') %> Ticketing<% &RT::URI::freeside::FreesideGetConfig('company_name') || 'ExampleCo' %> % if ($session{'CurrentUser'} && $session{'CurrentUser'}->Id && $LoggedIn) { <&|/l&>Skip Menu | +<&|/l, "".$session{'CurrentUser'}->Name."" &>Logged in as [_1] +
    %if ($session{'CurrentUser'}->HasRight( Right => 'ModifySelf', Object => $RT::System )) { <&|/l&>Preferences % } @@ -89,8 +91,6 @@ ONLOAD=" % unless ($RT::WebExternalAuth and !$RT::WebFallbackToInternalAuth) { | <&|/l&>Logout % } -
    -<&|/l, "".$session{'CurrentUser'}->Name."" &>Logged in as [_1] % } else { <&|/l&>Not logged in. % } diff --git a/rt/html/Elements/PageLayout b/rt/html/Elements/PageLayout index 94bdbe194..5289f78f3 100644 --- a/rt/html/Elements/PageLayout +++ b/rt/html/Elements/PageLayout @@ -43,32 +43,43 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} - -
    <%$AppName%> + + + + + + % foreach my $action (sort keys %{$topactions}) { - % } +
    <%$AppName%> + <%$topactions->{"$action"}->{'html'} |n %>
    + + + + + %# Vertical menu - +
    + <& /Elements/Menu, toptabs => $toptabs, current_toptab => $current_toptab &> - - + + + + <% foreach my $custnum ( @custnums ) { %> + <% my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } ); %> + <% next unless $cust_main; %> + + + + + + <% + if ( $bgcolor eq $bgcolor1 ) { + $bgcolor = $bgcolor2; + } else { + $bgcolor = $bgcolor1; + } + %> + + <% } %> + +
    + <%$title%>
    + % if ($actions) { % my @actions; @@ -80,7 +91,7 @@ % } % } %#<% join(" | ", @actions) | n %> -<% '['. join("] [", @actions). ']' | n %> +<% '['. join("] [", @actions). '] ' | n %> % if ($subactions) { % my @actions; % foreach my $action (sort keys %{$subactions}) { diff --git a/rt/html/Elements/SimpleSearch b/rt/html/Elements/SimpleSearch index e76f801df..55d65fc89 100644 --- a/rt/html/Elements/SimpleSearch +++ b/rt/html/Elements/SimpleSearch @@ -43,7 +43,13 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -
    - + + +  
    diff --git a/rt/html/Elements/Tabs b/rt/html/Elements/Tabs index f5839a9e5..22720072c 100644 --- a/rt/html/Elements/Tabs +++ b/rt/html/Elements/Tabs @@ -57,9 +57,13 @@ <%INIT> my $action; my $basetopactions = { - A => { html => $m->scomp('/Elements/CreateTicket') +# A => { html => $m->scomp('/Elements/CreateTicket') +# }, + A => { html => $m->scomp('/Elements/FreesideNewCust') }, - B => { html => $m->scomp('/Elements/SimpleSearch') + B => { html => $m->scomp('/Elements/FreesideSearch') + }, + C => { html => $m->scomp('/Elements/SimpleSearch') } }; my $basetabs = { diff --git a/rt/html/Elements/TitleBoxStart b/rt/html/Elements/TitleBoxStart index 804e5cfaa..d98fe2744 100644 --- a/rt/html/Elements/TitleBoxStart +++ b/rt/html/Elements/TitleBoxStart @@ -78,7 +78,7 @@ $title_class => '' $titleright_href => undef $titleright => undef -$contentbg => "#dddddd" +$contentbg => "#d4d4d4" $color => "#336699" <%init> diff --git a/rt/html/NoAuth/webrt.css b/rt/html/NoAuth/webrt.css index bc688ac1d..4748694e5 100644 --- a/rt/html/NoAuth/webrt.css +++ b/rt/html/NoAuth/webrt.css @@ -43,7 +43,13 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} -SPAN.nav { font-family: Verdana, Arial, Helvetica, sans-serif; + +/* * { + font-family: Arial, Verdana, Helvetica, sans-serif; + font-size: 1.2em; +} */ + +SPAN.nav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 12px; %# color: #FFFFFF; color: #000000; @@ -51,7 +57,7 @@ SPAN.nav { font-family: Verdana, Arial, Helvetica, sans-serif; white-space: nowrap} .nav2 { font-size: 10px; white-space: nowrap} -.nav { font-family: Verdana, Arial, Helvetica, sans-serif; +.nav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 13px; %# font-weight: normal; font-weight: bold; @@ -59,17 +65,17 @@ SPAN.nav { font-family: Verdana, Arial, Helvetica, sans-serif; color: #000000; text-decoration: none; white-space: nowrap} -.currentnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.currentnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 13px; font-weight: bold; color: #FFFF66; text-decoration: none; white-space: nowrap} -.topnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.topnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 16px; font-weight: normal; -%# color: #FFFFFF; - color: #000000; + color: #FFFFFF; +%# color: #000000; text-decoration: none; white-space: nowrap} @@ -81,37 +87,43 @@ SPAN.nav { font-family: Verdana, Arial, Helvetica, sans-serif; a.topnav-0 { font-family: Verdana, sans-serif; font-size: 16px; font-weight: normal; - color: #000000; +%# color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} a.topnav-1 { font-family: Verdana, sans-serif; font-size: 14px; font-weight: normal; - color: #000000; +%# color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} a.topnav-2 { font-family: Verdana, sans-serif; font-size: 12px; font-weight: normal; - color: #000000; +%# color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} a.topnav-3 { font-family: Verdana, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; +%# color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} a.topnav-4 { font-family: Verdana, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; +%# color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} a.topnav-5 { font-family: Verdana, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; +%# color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} li.topnav-0-minor { @@ -175,7 +187,7 @@ li.topnav-5-major { padding-bottom: .5em; } -.currenttopnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.currenttopnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 16px; font-weight: bold; %# color: #FFFF66; @@ -191,37 +203,37 @@ li.topnav-5-major { a.currenttopnav-0 { font-family: Verdana, sans-serif; font-size: 16px; font-weight: bold; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} a.currenttopnav-1 { font-family: Verdana, sans-serif; font-size: 14px; font-weight: bold; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} a.currenttopnav-2 { font-family: Verdana, sans-serif; font-size: 12px; font-weight: normal; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} a.currenttopnav-3 { font-family: Verdana, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} a.currenttopnav-4 { font-family: Verdana, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} a.currenttopnav-5 { font-family: Verdana, sans-serif; font-size: 11px; font-weight: normal; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} li.currenttopnav-0-minor { @@ -285,18 +297,18 @@ li.currenttopnav-5-major { padding-bottom: .5em; } -.topactions { font-family: Verdana, Arial, Helvetica, sans-serif; +.topactions { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 10px; color: #FFFFFF; text-decoration: none; white-space: nowrap} -.subnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.subnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 11px; font-weight: normal; color: #FFFFFF; text-decoration: none; white-space: nowrap} -.currentsubnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.currentsubnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 11px; font-weight: bold; color: #FFFF66; @@ -355,6 +367,20 @@ li.currenttopnav-5-major { vertical-align: top; text-align: right; } +.black { + background-color: #000000; + color: #ffffff; + background-position: left top; + vertical-align: top; + text-align: left; + } +.blackright { + background-color: #000000; + color: #ffffff; + background-position: left top; + vertical-align: top; + text-align: right; + } .mediumgray { background-color: #cccccc; background-position: left top; @@ -367,6 +393,30 @@ li.currenttopnav-5-major { vertical-align: top; text-align: right; } +.darkmediumgray { + background-color: #aaaaaa; + background-position: left top; + vertical-align: top; + text-align: left; + } +.darkmediumgrayright { + background-color: #aaaaaa; + background-position: left top; + vertical-align: top; + text-align: right; + } +.bggray { + background-color: #e8e8e8; + background-position: left top; + vertical-align: top; + text-align: left; + } +.bggrayright { + background-color: #e8e8e8; + background-position: left top; + vertical-align: top; + text-align: right; + } .white { background-color: #ffffff; background-position: left top; @@ -396,26 +446,26 @@ div.downloadattachment { } -td { font-family: Verdana, Arial, Helvetica, sans-serif; +td { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 11px; background-position: left top; } .black { background-color: #000000; background-position: left top; } -span.rtname { font-family: Verdana, Arial, Helvetica, sans-serif; +span.rtname { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 18px; font-weight: normal; color: #ffffff} -span.title { font-family: Verdana, Arial, Helvetica, sans-serif; +span.title { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 20px; font-weight: bold; color: #ffffff} -.header { font-family: Verdana, Arial, Helvetica, sans-serif; +.header { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 12px; font-weight: bold; color: #0066CC} -.subheader { font-family: Verdana, Arial, Helvetica, sans-serif; +.subheader { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 11px; font-weight: bold; color: #0066CC } @@ -426,7 +476,7 @@ span.title { font-family: Verdana, Arial, Helvetica, sans-serif; .labeltop { font-weight: normal; text-align: right; vertical-align: top } -.productnav { font-family: Verdana, Arial, Helvetica, sans-serif; +.productnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 11px; color: #000000; text-align: center; @@ -450,6 +500,7 @@ TD.mainbody { padding-right: 1em; margin-left: 1em; margin-right: 1em; + background-color: #e8e8e8; } td.boxcontainer + td.boxcontainer { @@ -492,7 +543,7 @@ TD.titlebox { SPAN.message { font-size: 100%; - font-family: Verdana, Arial, Helvetica, sans-serif; + font-family: Arial, Verdana, Helvetica, sans-serif; } @@ -549,8 +600,9 @@ A:link IMG, A:visited IMG { border-style: none } a:focus {text-decoration: underline } A IMG { color: white } /* The only way to hide the border in NS 4.x */ -a:link { text-decoration: none} -a:visited { text-decoration: none} +/* a:link { text-decoration: none} */ +/* a:visited { text-decoration: none} */ + a:hover { text-decoration: underline} /* a:focus { background-color: #ccccee } */ diff --git a/rt/html/Search/Bulk.html b/rt/html/Search/Bulk.html index f9eef26b6..b7c64e3f8 100644 --- a/rt/html/Search/Bulk.html +++ b/rt/html/Search/Bulk.html @@ -68,7 +68,7 @@ $Tickets->RedoSearch(); while (my $Ticket = $Tickets->Next) { $i++; if ($i % 2) { - $bgcolor = "#dddddd"; + $bgcolor = "#d4d4d4"; } else { $bgcolor = "#ffffff"; -- cgit v1.2.1 From 64dc1bb0f70ccc0b828cc1d758cd82f040e0ec33 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 15 May 2006 13:57:15 +0000 Subject: move most of the crap on the "main menu" to the sidebar --- httemplate/elements/freeside.css | 2 +- httemplate/elements/header.html | 169 +--------------------- httemplate/elements/menu.html | 298 +++++++++++++++++++++++++++++++++++++++ httemplate/index.html | 204 +-------------------------- 4 files changed, 304 insertions(+), 369 deletions(-) create mode 100644 httemplate/elements/menu.html diff --git a/httemplate/elements/freeside.css b/httemplate/elements/freeside.css index 5908e6aab..6f7cadb2e 100644 --- a/httemplate/elements/freeside.css +++ b/httemplate/elements/freeside.css @@ -3,7 +3,7 @@ } A:link IMG, A:visited { border-style: none } -A:focus {text-decoration: underline } +/* A:focus {text-decoration: underline } */ a:link, a:visited { /* text-decoration: none; */ diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index 0eb5695ce..2be2c7938 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -13,175 +13,8 @@ - - - - - <% - tie my %report_menu, 'Tie::IxHash', - 'Report one' => [ 'there', 'theretip' ], - 'Report too' => [ 'here', 'heretip' ], - ; - - tie my %config_employees, 'Tie::IxHash', - 'View/Edit employees' => [ $fsurl.'browse/access_user.html', 'Setup internal users' ], - 'View/Edit employee groups' => [ $fsurl.'browse/access_group.html', 'Employee groups allow you to control access to the backend' ], - ; - - tie my %config_export_svc_pkg, 'Tie::IxHash', - 'View/Edit exports' => [ $fsurl.'browse/part_export.cgi', 'Provisioning services to external machines, databases and APIs' ], - 'View/Edit service definitions' => [ $fsurl.'browse/part_svc.cgi', 'Services are items you offer to your customers' ], - 'View/Edit package definitions' => [ $fsurl.'browse/part_pkg.cgi', 'One or more services are grouped together into a package and given pricing information. Customers purchase packages, not services' ], - 'View/Edit package classes' => [ $fsurl.'browse/pkg_class.html', 'Package classes define groups of packages, for reporting and convenience purposes.' ], - ; - - tie my %config_agent, 'Tie::IxHash', - 'View/Edit agent types' => [ $fsurl.'browse/agent_type.cgi', 'Agent types define groups of package definitions that you can then assign to particular agents' ], - 'View/Edit agents' => [ $fsurl.'browse/agent.cgi', 'Agents are resellers of your service. Agents may be limited to a subset of your full offerings (via their type)' ], - ; - - tie my %config_billing, 'Tie::IxHash', - 'View/Edit payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors' ], - 'View/Edit invoice events' => [ $fsurl.'browse/part_bill_event.cgi', 'Actions for overdue invoices' ], - 'View/Edit prepaid cards' => [ $fsurl.'browse/prepay_credit.html', 'View outstanding cards, generate new cards' ], - 'View/Edit call rates and regions' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans, regions and prefixes for VoIP and call billing' ], - 'View/Edit locales and tax rates' => [ $fsurl.'browse/cust_main_county.cgi', 'Change tax rates, or break down a country into states, or a state into counties and assign different tax rates to each' ], - ; - - tie my %config_dialup, 'Tie::IxHash', - 'View/Edit access numbers' => [ $fsurl.'browse/svc_acct_pop.cgi', 'Points of Presence' ], - ; - - tie my %config_broadband, 'Tie::IxHash', - 'View/Edit routers' => [ $fsurl.'browse/router.cgi', 'Broadband access routers' ], - 'View/Edit address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ], - ; - - tie my %config_misc, 'Tie::IxHash', - 'View/Edit advertising sources' => [ $fsurl.'browse/part_referral.cgi', 'Where a customer heard about your service. Tracked for informational purposes' ], - 'View/Edit virtual fields' => [ $fsurl.'browse/part_virtual_field.cgi', 'Locally defined fields', ], - 'View/Edit message catalog' => [ $fsurl.'browse/msgcat.cgi', 'Change error messages and other customizable labels' ], - 'View/Edit inventory classes and inventory' => [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ], - ; - - tie my %config_menu, 'Tie::IxHash', - 'Settings' => [ $fsurl.'config/config-view.cgi', 'XXXconfigittip' ], - 'separator' => '', #its a separator! - 'Employees' => [ \%config_employees, 'XXXtooltip' ], - 'Provisioning, services and packages' - => [ \%config_export_svc_pkg, 'XXXtootip' ], - 'Resellers' => [ \%config_agent, 'XXXtootip' ], - 'Billing' => [ \%config_billing, 'XXXtootip' ], - 'Dialup' => [ \%config_dialup, 'XXXtootip' ], - 'Fixed (username-less) broadband' - => [ \%config_broadband, 'XXXtootip' ], - 'Miscellaneous' => [ \%config_misc, 'XXXtootip' ], - ; - - tie my %menu, 'Tie::IxHash', - 'Home' => [ $fsurl, 'hometip', ], - 'Top item one' => [ 'nowhere_yet', 'nowheretip', ], - 'Top item too' => [ 'nowhere_yet_either', 'eithertip', ], - 'Reports' => [ \%report_menu, 'reportmenutip' ], - 'Configuration' => [ \%config_menu, 'configmenutip' ], - ; - - use vars qw($gmenunum); - $gmenunum = 0; - - sub submenu { - my($submenu, $title) = @_; - my $menunum = $gmenunum++; - - #return two args: html, menuname - - "var myMenu$menunum = new WebFXMenu;\n". - #"myMenu$menunum.useAutoPosition = true;\n". - "myMenu$menunum.emptyText = '$title';\n". - - ( - join("\n", map { - - if ( !ref( $submenu->{$_} ) ) { - - "myMenu$menunum.add(new WebFXMenuSeparator());"; - - } else { - - my($url_or_submenu, $tooltip ) = @{ $submenu->{$_} }; - if ( ref($url_or_submenu) ) { - - my($subhtml, $submenuname ) = submenu($url_or_submenu, $_); #mmm, recursion - - "$subhtml\n". - "myMenu$menunum.add(new WebFXMenuItem(\"$_\", null, \"$tooltip\", $submenuname ));"; - - } else { - - "myMenu$menunum.add(new WebFXMenuItem(\"$_\", \"$url_or_submenu\", \"$tooltip\" ));"; - - } - - } - - } keys %$submenu ) - ). "\n". - "myMenu$menunum.width = 224\n", - - "myMenu$menunum"; - - } - - %> - + <%= include('menu.html', 'freeside_baseurl' => $fsurl ) %> + + + + + diff --git a/httemplate/index.html b/httemplate/index.html index 33083f6e5..95d4580bd 100644 --- a/httemplate/index.html +++ b/httemplate/index.html @@ -4,72 +4,24 @@
    [ Sales / Customer service ] -<% if ( $conf->config('ticket_system') ) { %> - [ Support / Ticketing ] -<% } %> [ Bookkeeping / Collections ] [ Reports ] -[ Sysadmin ]
    Sales / Customer service
    -
    New Customer -
    -
    Customer # or all customers by customer number
    -
    Last name or all customers by last name
    -
    Company or all customers by company
    -<% if ( $conf->exists('address2-search') ) { %> -
    Unit
    -<% } %> -
    Phone #
    -
    Username or all accounts by username or uid
    -
    Domain or all domains
    -
    IP Address or all services by svcnum or address block
    -
    all mail forwards by svcnum
    -
    all virtual hosts by svcnum
    -
    all external services by svcnum
    +
    Username
    +
    Domain
    +
    IP Address




    -<% if ( $conf->config('ticket_system') ) { %> [ Sales / Customer service ] -[ Support / Ticketing ] -[ Bookkeeping / Collections ] -[ Reports ] -[ Sysadmin ] - - - -
    Support/Ticketing
    - <% if ( $conf->config('ticket_system') eq 'RT_Internal' ) { %> -
    Ticketing Main -

    - Reports -
    - -


    - -<% } %> - - -[ Sales / Customer service ] -<% if ( $conf->config('ticket_system') ) { %> - [ Support / Ticketing ] -<% } %> [ Bookkeeping / Collections ] [ Reports ] -[ Sysadmin ]
    Bookkeeping / Collections
    @@ -78,30 +30,12 @@
    Credit card #
    Invoice #
    Check #
    -
    View pending credit card batch

    Packages (by next bill date range) -

    Invoice reports - - Advanced invoice reports

    +
    Invoice event reports - Sales, Credits and Receipts Summary -

    Sales report (by agent, package class and/or date range) -

    Credit report (by employee and/or date range) -

    Payment report (by type and/or date range) -

    Accounts Receivable Aging Summary -

    Prepaid Income (Unearned Revenue) Report -

    Sales Tax Liability Report

    @@ -113,150 +47,20 @@ [ Sales / Customer service ] -<% if ( $conf->config('ticket_system') ) { %> - [ Support / Ticketing ] -<% } %> [ Bookkeeping / Collections ] [ Reports ] -[ Sysadmin ]
    Reports

    RADIUS sessions

    - Auditing pre-Freeside services with no customer record - - Packages - - Package definitions (by number of active packages)

    - Service definitions (by number of active services)

    - Customers - - Zip code distribution

    SQL query: SELECT

    - -


    - -[ Sales / Customer service ] -<% if ( $conf->config('ticket_system') ) { %> - [ Support / Ticketing ] -<% } %> -[ Bookkeeping / Collections ] -[ Reports ] -[ Sysadmin ] - - - -
    Sysadmin
    -
    - - - View pending job queue -
    Batch import customers from CSV file -
    Batch import charges from CSV file -
    Download database dump -



    - Configuration -

    - Employees - - Provisioning, services and packages - - Resellers -
      -
    • View/Edit agent types - - Agent types define groups of package definitions that you can - then assign to particular agents. -
    • View/Edit agents - - Agents are resellers of your service. Agents may be limited - to a subset of your full offerings (via their type). -
    - Billing - - Dialup - - Fixed (username-less) broadband - - Miscellaneous - -
    -
    -















    -















    -















    -















    -















    -















    -















    -















    - <%= include('/elements/footer.html') %> -- cgit v1.2.1 From 50f25b285b2caf77d267ed66f03e56924ad7229f Mon Sep 17 00:00:00 2001 From: jeff Date: Sat, 20 May 2006 20:06:30 +0000 Subject: first stab at BoM download --- FS/FS.pm | 4 +- FS/FS/Schema.pm | 15 ++++- FS/FS/cust_bill.pm | 20 +++++- FS/FS/cust_pay_batch.pm | 40 +++++++++--- FS/FS/pay_batch.pm | 121 +++++++++++++++++++++++++++++++++++ FS/MANIFEST | 6 ++ FS/t/pay_batch.t | 5 ++ README.1.5.7.lastbit | 5 ++ README.1.5.8 | 9 +++ htetc/handler.pl | 1 + httemplate/browse/cust_pay_batch.cgi | 6 +- httemplate/docs/schema.html | 10 ++- httemplate/docs/upgrade10.html | 8 +++ httemplate/misc/download-batch.cgi | 69 +++++++++++++++++++- 14 files changed, 298 insertions(+), 21 deletions(-) create mode 100644 FS/FS/pay_batch.pm create mode 100644 FS/t/pay_batch.t diff --git a/FS/FS.pm b/FS/FS.pm index f41245e22..d8999ca5e 100644 --- a/FS/FS.pm +++ b/FS/FS.pm @@ -142,7 +142,9 @@ L - Credit application to invoice class L - Refund application to payment class -L - Credit card transaction queue class +L - Credit card transaction queue class + +L - Credit card transaction member queue class L - Prepaid "calling card" credit class. diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index e81185666..17d541e8c 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -538,10 +538,21 @@ sub tables_hashref { 'index' => [ [ 'paynum' ], [ 'invnum' ] ], }, + 'pay_batch' => { #batches of payments to an external processor + 'columns' => [ + 'batchnum', 'serial', '', '', '', '', + 'status', 'char', 'NULL', 1, '', '', + ], + 'primary_key' => 'batchnum', + 'unique' => [], + 'index' => [], + }, + 'cust_pay_batch' => { #what's this used for again? list of customers #in current CARD batch? (necessarily CARD?) 'columns' => [ 'paybatchnum', 'serial', '', '', '', '', + 'batchnum', 'int', '', '', '', '', 'invnum', 'int', '', '', '', '', 'custnum', 'int', '', '', '', '', 'last', 'varchar', '', $char_d, '', '', @@ -553,7 +564,7 @@ sub tables_hashref { 'zip', 'varchar', 'NULL', 10, '', '', 'country', 'char', '', 2, '', '', # 'trancode', 'int', '', '', '', '' - 'cardnum', 'varchar', '', 16, '', '', + 'payinfo', 'varchar', '', 512, '', '', #'exp', @date_type, '', '' 'exp', 'varchar', '', 11, '', '', 'payname', 'varchar', 'NULL', $char_d, '', '', @@ -561,7 +572,7 @@ sub tables_hashref { ], 'primary_key' => 'paybatchnum', 'unique' => [], - 'index' => [ ['invnum'], ['custnum'] ], + 'index' => [ ['batchnum'], ['invnum'], ['custnum'] ], }, 'cust_pkg' => { diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index bcae4d646..c2a39afda 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -13,7 +13,7 @@ use HTML::Entities; use Locale::Country; use FS::UID qw( datasrc ); use FS::Misc qw( send_email send_fax ); -use FS::Record qw( qsearch qsearchs ); +use FS::Record qw( qsearch qsearchs dbh ); use FS::cust_main_Mixin; use FS::cust_main; use FS::cust_bill_pkg; @@ -1282,8 +1282,22 @@ L). sub batch_card { my $self = shift; my $cust_main = $self->cust_main; + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $pay_batch = qsearchs('pay_batch'=> ''); + + unless ($pay_batch) { + $pay_batch = new FS::pay_batch; + my $error = $pay_batch->insert; + if ( $error ) { + die "error creating new batch: $error\n"; + } + } my $cust_pay_batch = new FS::cust_pay_batch ( { + 'batchnum' => $pay_batch->getfield('batchnum'), 'invnum' => $self->getfield('invnum'), 'custnum' => $cust_main->getfield('custnum'), 'last' => $cust_main->getfield('last'), @@ -1294,7 +1308,7 @@ sub batch_card { 'state' => $cust_main->getfield('state'), 'zip' => $cust_main->getfield('zip'), 'country' => $cust_main->getfield('country'), - 'cardnum' => $cust_main->payinfo, + 'payinfo' => $cust_main->payinfo, 'exp' => $cust_main->getfield('paydate'), 'payname' => $cust_main->getfield('payname'), 'amount' => $self->owed, @@ -1302,6 +1316,8 @@ sub batch_card { my $error = $cust_pay_batch->insert; die $error if $error; + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; } diff --git a/FS/FS/cust_pay_batch.pm b/FS/FS/cust_pay_batch.pm index 25b796ba9..117d72561 100644 --- a/FS/FS/cust_pay_batch.pm +++ b/FS/FS/cust_pay_batch.pm @@ -37,7 +37,7 @@ following fields are currently supported: =item paybatchnum - primary key (automatically assigned) -=item cardnum +=item payinfo =item exp - card expiration @@ -119,7 +119,7 @@ sub check { my $error = $self->ut_numbern('paybatchnum') || $self->ut_numbern('trancode') #depriciated - || $self->ut_number('cardnum') + || $self->ut_number('payinfo') || $self->ut_money('amount') || $self->ut_number('invnum') || $self->ut_number('custnum') @@ -137,14 +137,19 @@ sub check { $self->first =~ /^([\w \,\.\-\']+)$/ or return "Illegal first name"; $self->first($1); - my $cardnum = $self->cardnum; - $cardnum =~ s/\D//g; - $cardnum =~ /^(\d{13,16})$/ - or return "Illegal credit card number"; - $cardnum = $1; - $self->cardnum($cardnum); - validate($cardnum) or return "Illegal credit card number"; - return "Unknown card type" if cardtype($cardnum) eq "Unknown"; + # FIXME + # there is no point in false laziness here + # we will effectively set "check_payinfo to 0" + # we can change that when we finish the refactor + + #my $cardnum = $self->cardnum; + #$cardnum =~ s/\D//g; + #$cardnum =~ /^(\d{13,16})$/ + # or return "Illegal credit card number"; + #$cardnum = $1; + #$self->cardnum($cardnum); + #validate($cardnum) or return "Illegal credit card number"; + #return "Unknown card type" if cardtype($cardnum) eq "Unknown"; if ( $self->exp eq '' ) { return "Expiration date required"; #unless @@ -305,6 +310,21 @@ sub import_results { local $FS::UID::AutoCommit = 0; my $dbh = dbh; + my $pay_batch = qsearchs('pay_batch',{'batchnum'=> $paybatch}); + unless ($pay_batch && $pay_batch->status eq 'I') { + $dbh->rollback if $oldAutoCommit; + return "batch $paybatch is not in transit"; + }; + + my %batchhash = $pay_batch->hash; + $batchhash{'status'} = 'R'; # Resolved + my $newbatch = new FS::pay_batch ( \%batchhash ); + my $error = $newbatch->replace($paybatch); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error + } + my $total = 0; my $line; while ( defined($line=<$fh>) ) { diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm new file mode 100644 index 000000000..192c5df83 --- /dev/null +++ b/FS/FS/pay_batch.pm @@ -0,0 +1,121 @@ +package FS::pay_batch; + +use strict; +use vars qw( @ISA ); +use FS::Record qw( qsearch qsearchs ); + +@ISA = qw(FS::Record); + +=head1 NAME + +FS::pay_batch - Object methods for pay_batch records + +=head1 SYNOPSIS + + use FS::pay_batch; + + $record = new FS::pay_batch \%hash; + $record = new FS::pay_batch { 'column' => 'value' }; + + $error = $record->insert; + + $error = $new_record->replace($old_record); + + $error = $record->delete; + + $error = $record->check; + +=head1 DESCRIPTION + +An FS::pay_batch object represents an example. FS::pay_batch inherits from +FS::Record. The following fields are currently supported: + +=over 4 + +=item batchnum - primary key + +=item status - + + +=back + +=head1 METHODS + +=over 4 + +=item new HASHREF + +Creates a new example. To add the example to the database, see L<"insert">. + +Note that this stores the hash reference, not a distinct copy of the hash it +points to. You can ask the object for a copy with the I method. + +=cut + +# the new method can be inherited from FS::Record, if a table method is defined + +sub table { 'pay_batch'; } + +=item insert + +Adds this record to the database. If there is an error, returns the error, +otherwise returns false. + +=cut + +# the insert method can be inherited from FS::Record + +=item delete + +Delete this record from the database. + +=cut + +# the delete method can be inherited from FS::Record + +=item replace OLD_RECORD + +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. + +=cut + +# the replace method can be inherited from FS::Record + +=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. + +=cut + +# the check method should currently be supplied - FS::Record contains some +# data checking routines + +sub check { + my $self = shift; + + my $error = + $self->ut_numbern('batchnum') + || $self->ut_enum('status', [ '', 'I', 'R' ]) + ; + return $error if $error; + + $self->SUPER::check; +} + +=back + +=head1 BUGS + +The author forgot to customize this manpage. + +=head1 SEE ALSO + +L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/MANIFEST b/FS/MANIFEST index bd810a8db..4e71e720b 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -340,3 +340,9 @@ t/access_groupagent.t FS/access_right.pm t/access_right.t FS/m2m_Common.pm +FS/pay_batch.pm +t/pay_batch.t +FS/pay_batch.pm +t/pay_batch.t +FS/pay_batch.pm +t/pay_batch.t diff --git a/FS/t/pay_batch.t b/FS/t/pay_batch.t new file mode 100644 index 000000000..c43133dc2 --- /dev/null +++ b/FS/t/pay_batch.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::pay_batch; +$loaded=1; +print "ok 1\n"; diff --git a/README.1.5.7.lastbit b/README.1.5.7.lastbit index ad29e4c02..872d30ac3 100644 --- a/README.1.5.7.lastbit +++ b/README.1.5.7.lastbit @@ -62,3 +62,8 @@ ALTER TABLE virtual_field ALTER vfieldnum SET NOT NULL; ALTER TABLE virtual_field ADD PRIMARY KEY (vfieldnum); -- ALTER TABLE h_virtual_field ADD vfieldnum int; +ALTER TABLE cust_pay_batch RENAME COLUMN cardnum TO payinfo; +ALTER TABLE cust_pay_batch ALTER COLUMN payinfo varchar(512) NULL; +ALTER TABLE h_cust_pay_batch RENAME COLUMN cardnum TO payinfo; +ALTER TABLE h_cust_pay_batch ALTER COLUMN payinfo varchar(512) NULL; + diff --git a/README.1.5.8 b/README.1.5.8 index 6ad19b063..4335a5677 100644 --- a/README.1.5.8 +++ b/README.1.5.8 @@ -8,6 +8,15 @@ install DBIx::DBSchema 0.27 (or later) install HTML::Widgets:SelectLayers 0.05 (or later) install Business::CreditCard 0.28 (or later) +ALTER TABLE cust_pay_batch ADD COLUMN batchnum int; +ALTER TABLE cust_pay_batch ALTER COLUMN batchnum SET NOT NULL; +ALTER TABLE cust_pay_batch ADD COLUMN payinfo varchar(512); +UPDATE cust_pay_batch SET payinfo = cardnum; +ALTER TABLE cust_pay_batch DROP COLUMN cardnum; +ALTER TABLE h_cust_pay_batch ADD COLUMN payinfo varchar(512); +UPDATE h_cust_pay_batch SET payinfo = cardnum; +ALTER TABLE h_cust_pay_batch DROP COLUMN cardnum; + make install-perl-modules run "freeside-upgrade username" to uprade your database schema diff --git a/htetc/handler.pl b/htetc/handler.pl index cbe2dd35d..d400a55ea 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -149,6 +149,7 @@ sub handler use FS::part_svc; use FS::part_svc_router; use FS::part_virtual_field; + use FS::pay_batch; use FS::pkg_svc; use FS::port; use FS::queue qw(joblisting); diff --git a/httemplate/browse/cust_pay_batch.cgi b/httemplate/browse/cust_pay_batch.cgi index 0f05ecb25..6ee983ab4 100755 --- a/httemplate/browse/cust_pay_batch.cgi +++ b/httemplate/browse/cust_pay_batch.cgi @@ -3,7 +3,9 @@
    Download batch in format


    @@ -11,7 +13,9 @@ Download batch in format
    Format

    @@ -47,7 +51,7 @@ $<%= sprintf("%.2f", $total) %> total in pending batch
    foreach my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } qsearch('cust_pay_batch', {} ) ) { - my $cardnum = $cust_pay_batch->cardnum; + my $cardnum = $cust_pay_batch->payinfo; #$cardnum =~ s/.{4}$/xxxx/; $cardnum = 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4)); diff --git a/httemplate/docs/schema.html b/httemplate/docs/schema.html index cdb59a2e9..d9e35efc7 100644 --- a/httemplate/docs/schema.html +++ b/httemplate/docs/schema.html @@ -199,10 +199,16 @@
  • amount
  • _date -
  • cust_pay_batch - Pending batch +
  • pay_batch - Pending batch +
      +
    • batchnum +
    • status +
    +
  • cust_pay_batch - Pending batch members
    • paybatchnum -
    • cardnum +
    • batchnum +
    • payinfo - account number
    • exp - card expiration
    • amount
    • invnum - invoice diff --git a/httemplate/docs/upgrade10.html b/httemplate/docs/upgrade10.html index 7cd1d8e50..2a4b0d975 100644 --- a/httemplate/docs/upgrade10.html +++ b/httemplate/docs/upgrade10.html @@ -46,6 +46,14 @@ DROP INDEX cust_bill_pkg1;
       ALTER TABLE cust_main ALTER COLUMN payinfo varchar(512) NULL;
       ALTER TABLE h_cust_main ALTER COLUMN payinfo varchar(512) NULL;
      +ALTER TABLE cust_pay_batch ADD COLUMN batchnum int NOT NULL;
      +ALTER TABLE cust_pay_batch ALTER COLUMN batchnum SET NOT NULL;
      +ALTER TABLE cust_pay_batch ADD COLUMN payinfo varchar(512) NULL;
      +UPDATE cust_pay_batch SET payinfo = cardnum;
      +ALTER TABLE cust_pay_batch DROP COLUMN cardnum;
      +ALTER TABLE h_cust_pay_batch ADD COLUMN payinfo varchar(512) NULL;
      +UPDATE h_cust_pay_batch SET payinfo = cardnum;
      +ALTER TABLE h_cust_pay_batch DROP COLUMN cardnum;
       
      On older Pg versions that don't support altering columns directly, you will need to dump the database, edit the schema definitions in the dump file, and reload. diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi index 306ef5d63..5a98f1d51 100644 --- a/httemplate/misc/download-batch.cgi +++ b/httemplate/misc/download-batch.cgi @@ -1,16 +1,79 @@ <% +my $conf=new FS::Conf; + #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes http_header('Content-Type' => 'text/plain' ); +#need default +my $formatconfig = "batchconfig".$cgi->param('format'); + +die "No batch configuration exists.\n$formatconfig\n" unless $conf->exists($formatconfig); +my $format = $conf->config($formatconfig); + +my $oldAutoCommit = $FS::UID::AutoCommit; +local $FS::UID::AutoCommit = 0; +my $dbh = dbh; + +my $pay_batch = qsearchs('pay_batch', {'status'=>''} ); +die "No pending batch. \n" unless $pay_batch; + +my %batchhash = $pay_batch->hash; +$batchhash{'status'} = 'I'; +my $new = new FS::pay_batch \%batchhash; +my $error = $new->replace($pay_batch); +die "error updating batch status: $error\n" if $error; + +my $batchtotal=0; +my $batchcount=0; + +my (@date)=localtime(); +my $jdate = sprintf("%03d", $date[5] % 100).sprintf("%03d", $date[7]); + +if ($format eq "BoM") { + my($reformat,$origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) = $conf->config('batchconfig'); + printf "A%10s%04u%06u%05u%54s\n",$origid,$pay_batch->batchnum,$jdate,$datacenter,""; + printf "XD%03u%06u%-15s%-30s%09u%-12s \n",$typecode,$jdate,$shortname,$longname,$mybank,$myacct; +}elsif ($format eq "CSV file for TD Canada Trust Merchant PC Batch"){ + 1; +}else{ + die "Unknown format for batch in batchconfig. \n"; +} + + for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } - qsearch('cust_pay_batch', {} ) + qsearch('cust_pay_batch', + {'batchnum'=>$pay_batch->batchnum} ) ) { $cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; my( $mon, $y ) = ( $2, $1 ); $mon = "0$mon" if $mon < 10; my $exp = "$mon$y"; +$batchcount++; +$batchtotal += $cust_pay_batch->amount; + +if ($format eq "BoM") { + my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo ); + printf "D%010u%09u%-12s%-29s%-19s\n",$cust_pay_batch->amount*100,$aba,$account,$cust_pay_batch->payname,$cust_pay_batch->invnum; +}elsif ($format eq "CSV file for TD Canada Trust Merchant PC Batch"){ +%>,,,,<%= $cust_pay_batch->payinfo %>,<%= $exp %>,<%= $cust_pay_batch->amount %>,<%= $cust_pay_batch->paybatchnum %> +<% }else{ + die "I'm already dead, but you did not know that.\n"; +} + +} + +if ($format eq "BoM") { + printf "YD%08u%014u%56s\n",$batchcount,$batchtotal*100,""; + printf "Z%014u%05u%014u%05u%41s\n",$batchtotal*100,$batchcount,"0","0",""; +}elsif ($format eq "CSV file for TD Canada Trust Merchant PC Batch"){ + 1; +} else{ + die "I'm already dead (again), but you did not know that.\n"; +} + +$dbh->commit or die $dbh->errstr if $oldAutoCommit; + +%> -%>,,,,<%= $cust_pay_batch->cardnum %>,<%= $exp %>,<%= $cust_pay_batch->amount %>,<%= $cust_pay_batch->paybatchnum %> -<% } %> -- cgit v1.2.1 From 2594960a919709a148ff56c1176e9df6f7176753 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 21 May 2006 18:40:09 +0000 Subject: tyop --- FS/FS/part_export/communigate_pro_singledomain.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/FS/part_export/communigate_pro_singledomain.pm b/FS/FS/part_export/communigate_pro_singledomain.pm index 6a1bf60eb..e25043fbb 100644 --- a/FS/FS/part_export/communigate_pro_singledomain.pm +++ b/FS/FS/part_export/communigate_pro_singledomain.pm @@ -20,7 +20,7 @@ tie my %options, 'Tie::IxHash', %FS::part_export::communigate_pro::options, 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 multipledomains, +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 -- cgit v1.2.1 From e631aea6e8c9dbc41192948468314afd7b806613 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 22 May 2006 00:45:37 +0000 Subject: 1.7.0? why not! --- httemplate/elements/menu.html | 19 +++---- httemplate/index.html | 117 ++++++++++++++++++++---------------------- 2 files changed, 65 insertions(+), 71 deletions(-) diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 8502c0746..29ef53575 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -121,6 +121,7 @@ #
      --> tie my %tools_menu, 'Tie::IxHash', + 'Quick payment entry' => [ $fsurl.'misc/batch-cust_pay.html', 'Enter multiple payments in a batch' ], 'Job Queue' => [ $fsurl.'search/queue.html', 'View pending job queue' ], 'Importing' => [ \%tools_importing, 'Import tools' ], 'Exporting' => [ \%tools_exporting, 'Export tools' ], @@ -168,17 +169,17 @@ ; tie my %config_menu, 'Tie::IxHash', - 'Settings' => [ $fsurl.'config/config-view.cgi', 'XXXconfigittip' ], + 'Settings' => [ $fsurl.'config/config-view.cgi', '' ], 'separator' => '', #its a separator! - 'Employees' => [ \%config_employees, 'XXXtooltip' ], + 'Employees' => [ \%config_employees, '' ], 'Provisioning, services and packages' - => [ \%config_export_svc_pkg, 'XXXtootip' ], - 'Resellers' => [ \%config_agent, 'XXXtootip' ], - 'Billing' => [ \%config_billing, 'XXXtootip' ], - 'Dialup' => [ \%config_dialup, 'XXXtootip' ], + => [ \%config_export_svc_pkg, '' ], + 'Resellers' => [ \%config_agent, '' ], + 'Billing' => [ \%config_billing, '' ], + 'Dialup' => [ \%config_dialup, '' ], 'Fixed (username-less) broadband' - => [ \%config_broadband, 'XXXtootip' ], - 'Miscellaneous' => [ \%config_misc, 'XXXtootip' ], + => [ \%config_broadband, '' ], + 'Miscellaneous' => [ \%config_misc, '' ], ; tie my %menu, 'Tie::IxHash', @@ -253,7 +254,7 @@ webfxMenuImagePath = "<%=$fsurl%>images/"; webfxMenuUseHover = 1; webfxMenuShowTime = 300; - webfxMenuHideTime = 500; + webfxMenuHideTime = 300; var myBar = new WebFXMenuBar; diff --git a/httemplate/index.html b/httemplate/index.html index 95d4580bd..1a92b45f6 100644 --- a/httemplate/index.html +++ b/httemplate/index.html @@ -1,66 +1,59 @@ <% my $conf = new FS::Conf; %> -<%= include('/elements/header.html', 'Freeside Main Menu' ) %> - -
      - -[ Sales / Customer service ] -[ Bookkeeping / Collections ] -[ Reports ] - - - -
      Sales / Customer service
      -
      Username
      -
      Domain
      -
      IP Address
      -
      -
      - -


      - - -[ Sales / Customer service ] -[ Bookkeeping / Collections ] -[ Reports ] - - - -
      Bookkeeping / Collections
      -
      Quick payment entry -
      -
      Credit card #
      -
      Invoice #
      -
      Check #
      -
      - Invoice event reports - -

      -
      - - - -


      - - - -[ Sales / Customer service ] -[ Bookkeeping / Collections ] -[ Reports ] - - - -
      Reports
      -
      - RADIUS sessions

      -
      SQL query: SELECT
      - -
      -
      - -


      +<%= include('/elements/header.html', 'Billing Main' ) %> + +<% + + my $sth = dbh->prepare( + #"SELECT DISTINCT custnum FROM h_cust_main JOIN cust_main USING ( custnum ) + "SELECT custnum FROM h_cust_main JOIN cust_main USING ( custnum ) + WHERE ( history_action = 'insert' OR history_action = 'replace_new' ) + AND history_user = ? + ORDER BY history_date desc" # LIMIT 10 + ) or die dbh->errstr; + + $sth->execute( getotaker() ) or die $sth->errstr; + + my %saw = (); + my @custnums = grep { !$saw{$_}++ } map $_->[0], @{ $sth->fetchall_arrayref }; + + @custnums = splice(@custnums, 0, 10); + + if ( @custnums ) { + +%> + + <%= include('/elements/table-grid.html') %> + + <% my $bgcolor1 = '#eeeeee'; + my $bgcolor2 = '#ffffff'; + my $bgcolor; + %> + +
  • Customers I recently added or modified
    <%= $custnum %>: <%= $cust_main->name %>
    + +<% } %> <%= include('/elements/footer.html') %> -- cgit v1.2.1 From 64e9f3e7c823ef1315111d8139d50171215a2edf Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 22 May 2006 00:50:11 +0000 Subject: 1.7.0? why not? --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1163237b8..233b5764b 100644 --- a/Makefile +++ b/Makefile @@ -98,9 +98,9 @@ RT_PATH = /opt/rt3 #only used for dev kludge now, not a big deal FREESIDE_PATH = `pwd` -PERL_INC_DEV_KLUDGE = /usr/local/share/perl/5.8.7/ +PERL_INC_DEV_KLUDGE = /usr/local/share/perl/5.8.8/ -VERSION=1.7.0cvs +VERSION=1.7.0 TAG=freeside_1_7_0 help: -- cgit v1.2.1 From b9c46f3cf466c423bcddfa9ec32258670af5b6ae Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 22 May 2006 02:04:13 +0000 Subject: 1.5.8! --- Changes.1.5.8 | 63 +++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/Changes.1.5.8 b/Changes.1.5.8 index ab3572103..14240bf87 100644 --- a/Changes.1.5.8 +++ b/Changes.1.5.8 @@ -1,28 +1,41 @@ -- move account search (httemplate/search/svc_acct.cgi) to new template -- cust-fields configuration value to control which customer fields are shown on reports +Major new features and updates since 1.5.7: +- Added prepaid packages that set the RADIUS "Expiration" attribute and + auto-suspend on their next bill date +- Added banned card table and option to send customer cards there on cancel +- Bulk svcpart change +- Integrated RT upgraded to 3.4.4 + +UI: +- Re-did billing section of customer edit, should be much less confusing +- Redo quick payment entry page with ajax magic +- Redid account view and edit pages, add ability to edit uid/gid if conf + options for it are turned on +- cust-fields configuration value to control which customer fields are shown + on reports +- Moved account search (httemplate/search/svc_acct.cgi), cust_pkg search + (httemplate/search/cust_pkg.cgi) and others to new template - add unlinked mail forward (svc_forward) report -- move cust_pkg search (httemplate/search/cust_pkg.cgi) to new template -- add active/suspended/cancelled customer packages to agent browse -- add export to everyone.net outsource mail service -- add native Radiator export -- added agent/taxclass/card type-specific gateway overrides for people with +- Moved to XMLHttpRequest instead of hidden iframe transport for progress bar +- Also use XMLHttpRequest for retreiving states rather than send a huge page + for customer add/edit, much faster + +Resellers: +- Added active/suspended/cancelled customer packages to agent browse + +Billing: +- Added maximum "cap" options to RADIUS usage charges +- Added support for maestro/switch/solo cards, including start date and issue + number +- Added agent/taxclass/card type-specific gateway overrides for people with multiple payment gateways for different resellers, taxclasses and/or card types -- re-did billing section of customer edit and added maestro/switch/solo support -- add cpanel export -- added prepaid packages that set the RADIUS "Expiration" attribute and - auto-suspend on their next bill date -- added banned card table and option to send customer cards there on cancel -- moved to XMLHttpRequest instead of hidden iframe transport for progress bar, - should be more efficient and improve compatibility with Konq and maybe other - browsers? -- also use XMLHttpRequest for retreiving states rather than send a huge page - for customer add/edit, much faster -- redo account view and edit pages, add ability to edit uid/gid if conf options - for it are turned on -- redo quick payment entry page with ajax magic -- explicit payment types for cash and (optionally) western union -- bulk svcpart change -- tax report updated, per-agent option and most items now clickable -- direct radiator export -- added maximum "cap" options to RADIUS usage charges +- Explicit payment types for cash and (optionally) western union + +Reporting: +- Tax report updated, per-agent option and most items now clickable + +Exports: +- Added a Cpanel export +- Added a native Radiator export +- Added an export to everyone.net outsourced mail service + -- cgit v1.2.1 From cec2c86e7e14135b812d905afcca2b895236e23a Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 22 May 2006 02:06:30 +0000 Subject: docs are going in the wiki Real Soon Now anyway --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 233b5764b..421e9e310 100644 --- a/Makefile +++ b/Makefile @@ -345,7 +345,8 @@ upload-docs: forcehtmlman ssh 420.am rm -rf /var/www/www.sisd.com/freeside/docs scp -pr httemplate/docs 420.am:/var/www/www.sisd.com/freeside/docs -release: upload-docs +#release: upload-docs +release: cd /home/ivan/freeside #cvs tag ${TAG} cvs tag -F ${TAG} -- cgit v1.2.1 From 0e81fd2a8f84105982c0f2bc8e268a73da76f2d9 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 22 May 2006 18:05:26 +0000 Subject: justification --- Changes.1.5.8 | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Changes.1.5.8 b/Changes.1.5.8 index 14240bf87..6c6aeec5c 100644 --- a/Changes.1.5.8 +++ b/Changes.1.5.8 @@ -1,7 +1,8 @@ Major new features and updates since 1.5.7: - Added prepaid packages that set the RADIUS "Expiration" attribute and auto-suspend on their next bill date -- Added banned card table and option to send customer cards there on cancel +- Added banned card table and option to send customer cards there on + cancel - Bulk svcpart change - Integrated RT upgraded to 3.4.4 @@ -10,25 +11,26 @@ UI: - Redo quick payment entry page with ajax magic - Redid account view and edit pages, add ability to edit uid/gid if conf options for it are turned on -- cust-fields configuration value to control which customer fields are shown - on reports +- cust-fields configuration value to control which customer fields are + shown on reports - Moved account search (httemplate/search/svc_acct.cgi), cust_pkg search (httemplate/search/cust_pkg.cgi) and others to new template - add unlinked mail forward (svc_forward) report -- Moved to XMLHttpRequest instead of hidden iframe transport for progress bar -- Also use XMLHttpRequest for retreiving states rather than send a huge page - for customer add/edit, much faster +- Moved to XMLHttpRequest instead of hidden iframe transport for + progress bar +- Also use XMLHttpRequest for retreiving states rather than send a huge + page for customer add/edit, much faster Resellers: - Added active/suspended/cancelled customer packages to agent browse Billing: - Added maximum "cap" options to RADIUS usage charges -- Added support for maestro/switch/solo cards, including start date and issue - number -- Added agent/taxclass/card type-specific gateway overrides for people with - multiple payment gateways for different resellers, taxclasses and/or card - types +- Added support for maestro/switch/solo cards, including start date and + issue number +- Added agent/taxclass/card type-specific gateway overrides for people + with multiple payment gateways for different resellers, taxclasses + and/or card types - Explicit payment types for cash and (optionally) western union Reporting: -- cgit v1.2.1 From 1ba0e4e0b6374e8b7c55a5545d90dbb0fc7397e6 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 22 May 2006 18:27:46 +0000 Subject: better error message for banned cards --- FS/FS/cust_main.pm | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 65ccb343b..2763c1386 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1215,7 +1215,12 @@ sub check { if cardtype($self->payinfo) eq "Unknown"; my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref); - return "Banned credit card" if $ban; + if ( $ban ) { + return 'Banned credit card: banned on '. + time2str('%a %h %o at %r', $ban->_date). + ' by '. $ban->otaker. + ' (ban# '. $ban->bannum. ')'; + } if ( defined $self->dbdef_table->column('paycvv') ) { if (length($self->paycvv) && !$self->is_encrypted($self->paycvv)) { @@ -1272,7 +1277,12 @@ sub check { $self->paycvv('') if $self->dbdef_table->column('paycvv'); my $ban = qsearchs('banned_pay', $self->_banned_pay_hashref); - return "Banned ACH account" if $ban; + if ( $ban ) { + return 'Banned ACH account: banned on '. + time2str('%a %h %o at %r', $ban->_date). + ' by '. $ban->otaker. + ' (ban# '. $ban->bannum. ')'; + } } elsif ( $self->payby eq 'LECB' ) { -- cgit v1.2.1 From 74d840d48a629908e202de13bce668ccd945843b Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 22 May 2006 20:38:21 +0000 Subject: missing > tag on INPUT --- rt/html/Elements/FreesideNewCust | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rt/html/Elements/FreesideNewCust b/rt/html/Elements/FreesideNewCust index 851c66425..af8f9f1a7 100644 --- a/rt/html/Elements/FreesideNewCust +++ b/rt/html/Elements/FreesideNewCust @@ -1,3 +1,3 @@
    - 
    -- cgit v1.2.1 From 6fc7405849aee1f88c5ee844d0e091d00df7986e Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 23 May 2006 15:54:50 +0000 Subject: adding batch upgrade instructions to 1.7.0 instructions too --- README.1.7.0 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.1.7.0 b/README.1.7.0 index 992a988c8..a6e18d05d 100644 --- a/README.1.7.0 +++ b/README.1.7.0 @@ -1,6 +1,16 @@ install DBIx::DBSchema 0.31 (or later) install Color::Scheme +install Data::Table + +ALTER TABLE cust_pay_batch ADD COLUMN batchnum int; +ALTER TABLE cust_pay_batch ALTER COLUMN batchnum SET NOT NULL; +ALTER TABLE cust_pay_batch ADD COLUMN payinfo varchar(512); +UPDATE cust_pay_batch SET payinfo = cardnum; +ALTER TABLE cust_pay_batch DROP COLUMN cardnum; +ALTER TABLE h_cust_pay_batch ADD COLUMN payinfo varchar(512); +UPDATE h_cust_pay_batch SET payinfo = cardnum; +ALTER TABLE h_cust_pay_batch DROP COLUMN cardnum; make install-perl-modules run "freeside-upgrade username" to uprade your database schema @@ -17,6 +27,9 @@ Optional for better zip code report performance: CREATE INDEX cust_main16 on cust_main ( zip ); CREATE INDEX cust_main17 on cust_main ( ship_zip ); +Optional to eliminate harmless but noisy warnings: +UPDATE cust_main_county SET exempt_amount = 0 WHERE exempt_amount IS NULL; + ------ make install-docs -- cgit v1.2.1 From 7b5aa356c7ed954bb60ce22d44d6de5f4852fafd Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 24 May 2006 10:22:54 +0000 Subject: removing duplicate entries --- FS/MANIFEST | 4 ---- 1 file changed, 4 deletions(-) diff --git a/FS/MANIFEST b/FS/MANIFEST index 4e71e720b..9e3285dbb 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -342,7 +342,3 @@ t/access_right.t FS/m2m_Common.pm FS/pay_batch.pm t/pay_batch.t -FS/pay_batch.pm -t/pay_batch.t -FS/pay_batch.pm -t/pay_batch.t -- cgit v1.2.1 From 3a820c27d5290f9d2761636b2b4fe865caeb0804 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 2 Jun 2006 13:20:24 +0000 Subject: add a service search --- httemplate/elements/header.html | 19 +++++++++++++------ httemplate/search/svc_Smart.html | 22 ++++++++++++++++++++++ rt/FREESIDE_MODIFIED | 5 +++++ rt/html/Elements/FreesideSvcSearch | 10 ++++++++++ rt/html/Elements/PageLayout | 10 +++++----- rt/html/Elements/Tabs | 4 +++- 6 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 httemplate/search/svc_Smart.html create mode 100644 rt/html/Elements/FreesideSvcSearch diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index 2be2c7938..3aa81be3b 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -21,9 +21,12 @@ if ( what.value = '(cust #, name or company)' ) what.value = ''; } - - + +  + diff --git a/rt/html/Elements/PageLayout b/rt/html/Elements/PageLayout index 5289f78f3..dd0fd7504 100644 --- a/rt/html/Elements/PageLayout +++ b/rt/html/Elements/PageLayout @@ -45,14 +45,14 @@ %# END BPS TAGGED BLOCK }}} - + - +%# -% foreach my $action (sort keys %{$topactions}) { - % } diff --git a/rt/html/Elements/Tabs b/rt/html/Elements/Tabs index 22720072c..dcb652e12 100644 --- a/rt/html/Elements/Tabs +++ b/rt/html/Elements/Tabs @@ -63,7 +63,9 @@ my $basetopactions = { }, B => { html => $m->scomp('/Elements/FreesideSearch') }, - C => { html => $m->scomp('/Elements/SimpleSearch') + C => { html => $m->scomp('/Elements/FreesideSvcSearch') + }, + D => { html => $m->scomp('/Elements/SimpleSearch') } }; my $basetabs = { -- cgit v1.2.1 From f2dd532f7c009a653864b23d8858fe9e6c8c3985 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 6 Jun 2006 10:30:09 +0000 Subject: fix unmatched =back somehow futzing things up with automated install. wtf?! --- FS/FS/cdr.pm | 2 -- 1 file changed, 2 deletions(-) diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm index 70c8a0f09..5eb0cf393 100644 --- a/FS/FS/cdr.pm +++ b/FS/FS/cdr.pm @@ -119,8 +119,6 @@ following fields are currently supported: #Telstra =1, Optus = 2, RSL COM = 3 -=back - =item upstream_rateid - Upstream Rate ID =item svcnum - Link to customer service (see L) -- cgit v1.2.1 From c28ebb03fafe6a85753fc3f21e3abcc0477eccfc Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 6 Jun 2006 10:46:39 +0000 Subject: attempt to fix weird black RT navigation links --- rt/html/NoAuth/webrt.css | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/rt/html/NoAuth/webrt.css b/rt/html/NoAuth/webrt.css index 4748694e5..cf4cfc00a 100644 --- a/rt/html/NoAuth/webrt.css +++ b/rt/html/NoAuth/webrt.css @@ -75,7 +75,6 @@ SPAN.nav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 16px; font-weight: normal; color: #FFFFFF; -%# color: #000000; text-decoration: none; white-space: nowrap} @@ -87,42 +86,36 @@ SPAN.nav { font-family: Arial, Verdana, Helvetica, sans-serif; a.topnav-0 { font-family: Verdana, sans-serif; font-size: 16px; font-weight: normal; -%# color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap} a.topnav-1 { font-family: Verdana, sans-serif; font-size: 14px; font-weight: normal; -%# color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap} a.topnav-2 { font-family: Verdana, sans-serif; font-size: 12px; font-weight: normal; -%# color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap} a.topnav-3 { font-family: Verdana, sans-serif; font-size: 11px; font-weight: normal; -%# color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap} a.topnav-4 { font-family: Verdana, sans-serif; font-size: 11px; font-weight: normal; -%# color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap} a.topnav-5 { font-family: Verdana, sans-serif; font-size: 11px; font-weight: normal; -%# color: #000000; color: #FFFFFF; text-decoration: none; white-space: nowrap} @@ -190,8 +183,7 @@ li.topnav-5-major { .currenttopnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 16px; font-weight: bold; -%# color: #FFFF66; - color: #000000; background-color: #cccccc; + color: #ffffff; background-color: #7e0079; text-decoration: none; white-space: nowrap} -- cgit v1.2.1 From 6dfc9e121e60141830169f4237a5b1e381d6ba5b Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 6 Jun 2006 10:54:02 +0000 Subject: another attempt to fix weird black RT navigation links --- rt/html/NoAuth/webrt.css | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/rt/html/NoAuth/webrt.css b/rt/html/NoAuth/webrt.css index cf4cfc00a..712360cd5 100644 --- a/rt/html/NoAuth/webrt.css +++ b/rt/html/NoAuth/webrt.css @@ -59,10 +59,8 @@ SPAN.nav { font-family: Arial, Verdana, Helvetica, sans-serif; white-space: nowrap} .nav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 13px; -%# font-weight: normal; font-weight: bold; -%# color: #FFFFFF; - color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} .currentnav { font-family: Arial, Verdana, Helvetica, sans-serif; -- cgit v1.2.1 From 997c4e4178b74747b14177671069d55532d0ff10 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 6 Jun 2006 10:57:03 +0000 Subject: yet another attempt to fix weird black RT navigation links --- rt/html/NoAuth/webrt.css | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/rt/html/NoAuth/webrt.css b/rt/html/NoAuth/webrt.css index 712360cd5..f49d14531 100644 --- a/rt/html/NoAuth/webrt.css +++ b/rt/html/NoAuth/webrt.css @@ -51,8 +51,7 @@ SPAN.nav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 12px; -%# color: #FFFFFF; - color: #000000; + color: #FFFFFF; text-decoration: none; white-space: nowrap} .nav2 { font-size: 10px; @@ -468,7 +467,7 @@ span.title { font-family: Arial, Verdana, Helvetica, sans-serif; vertical-align: top } .productnav { font-family: Arial, Verdana, Helvetica, sans-serif; font-size: 11px; - color: #000000; + color: #FFFFFF; text-align: center; vertical-align: middle; text-decoration: none} @@ -576,7 +575,7 @@ DIV.endmatter { margin-left: -7% } } -A { font-weight: bold; color: #000000; +A { font-weight: bold; } .currenttab { color: #ffffff;} -- cgit v1.2.1 From 08c01dea3d6936dafe890f5c8a6076f636f5116c Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 6 Jun 2006 11:08:55 +0000 Subject: yay this should finally fix the weird black navigation links; hide the Mason stuff from browsers when it doesn't get processed for some reason --- rt/html/NoAuth/webrt.css | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/rt/html/NoAuth/webrt.css b/rt/html/NoAuth/webrt.css index f49d14531..f22ce4ced 100644 --- a/rt/html/NoAuth/webrt.css +++ b/rt/html/NoAuth/webrt.css @@ -1,3 +1,4 @@ +/* %# BEGIN BPS TAGGED BLOCK {{{ %# %# COPYRIGHT: @@ -43,6 +44,7 @@ %# those contributions and any derivatives thereof. %# %# END BPS TAGGED BLOCK }}} +*/ /* * { font-family: Arial, Verdana, Helvetica, sans-serif; @@ -75,10 +77,12 @@ SPAN.nav { font-family: Arial, Verdana, Helvetica, sans-serif; text-decoration: none; white-space: nowrap} +/* %# .topnav is the original RT class for the sidebar navigation tabs. %# Font-sizing by level depth was originally hard-coded into Elements/Menu. %# This modification sets a different class name for each level, allowing %# style sheet control over the formats. +*/ a.topnav-0 { font-family: Verdana, sans-serif; font-size: 16px; @@ -184,10 +188,12 @@ li.topnav-5-major { text-decoration: none; white-space: nowrap} +/* %# .currenttopnav is the original RT class for the sidebar navigation tabs. %# Font-sizing by level depth was originally hard-coded into Elements/Menu. %# This modification sets a different class name for each level, allowing %# style sheet control over the formats +*/ a.currenttopnav-0 { font-family: Verdana, sans-serif; font-size: 16px; @@ -315,12 +321,12 @@ li.currenttopnav-5-major { } .blue { background-color: #4682B4; -%# background-color: #eeeeee; +/* %# background-color: #eeeeee; */ background-position: left top; vertical-align: top; text-align: left; } -%# Actually the "topactions" section +/* %# Actually the "topactions" section */ .blueright { background-color: #4682B4; background-position: left top; vertical-align: top; @@ -604,7 +610,7 @@ SPAN.date { font-size: 0.8em } span.title { font-size: 1.6em; vertical-align: middle; -%# color: #ffffff; +/* %# color: #ffffff; */ color: #000000; } span.productname { font-size: 2em; @@ -703,6 +709,7 @@ textarea.messagebox { width: 100%; } +/* %# Provide a callback for adding/modifying the style sheet. %# http://www.w3.org/TR/REC-CSS1 - section 3.2, says: %# "latter specified rule wins" @@ -714,3 +721,4 @@ inherit => undef $r->content_type('text/css'); #$r->headers_out->{'Expires'} = '+30m'; +*/ -- cgit v1.2.1 From 1f2020811b5ac14613e854fb6861cbd37d580015 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 6 Jun 2006 11:16:36 +0000 Subject: whew, this can go back --- rt/html/NoAuth/webrt.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rt/html/NoAuth/webrt.css b/rt/html/NoAuth/webrt.css index f22ce4ced..04c959a05 100644 --- a/rt/html/NoAuth/webrt.css +++ b/rt/html/NoAuth/webrt.css @@ -581,7 +581,7 @@ DIV.endmatter { margin-left: -7% } } -A { font-weight: bold; +A { font-weight: bold; color: #000000 } .currenttab { color: #ffffff;} -- cgit v1.2.1 From 83f6b21826ce123e5936aaedaf58086189be11f2 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 8 Jun 2006 10:32:46 +0000 Subject: fix link to prepaid card setup --- httemplate/elements/menu.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 29ef53575..c09fcee8c 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -147,7 +147,7 @@ tie my %config_billing, 'Tie::IxHash', 'View/Edit payment gateways' => [ $fsurl.'browse/payment_gateway.html', 'Credit card and electronic check processors' ], 'View/Edit invoice events' => [ $fsurl.'browse/part_bill_event.cgi', 'Actions for overdue invoices' ], - 'View/Edit prepaid cards' => [ $fsurl.'browse/prepay_credit.html', 'View outstanding cards, generate new cards' ], + 'View/Edit prepaid cards' => [ $fsurl.'search/prepay_credit.html', 'View outstanding cards, generate new cards' ], 'View/Edit call rates and regions' => [ $fsurl.'browse/rate.cgi', 'Manage rate plans, regions and prefixes for VoIP and call billing' ], 'View/Edit locales and tax rates' => [ $fsurl.'browse/cust_main_county.cgi', 'Change tax rates, or break down a country into states, or a state into counties and assign different tax rates to each' ], ; -- cgit v1.2.1 From 44c866ddd722374379813d774f609ef43f39dfbc Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 16 Jun 2006 00:27:43 +0000 Subject: s/printf/sprintf/ and make the config a little less strange --- httemplate/misc/download-batch.cgi | 66 ++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi index 5a98f1d51..37b31c196 100644 --- a/httemplate/misc/download-batch.cgi +++ b/httemplate/misc/download-batch.cgi @@ -5,11 +5,12 @@ my $conf=new FS::Conf; #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes http_header('Content-Type' => 'text/plain' ); -#need default -my $formatconfig = "batchconfig".$cgi->param('format'); - -die "No batch configuration exists.\n$formatconfig\n" unless $conf->exists($formatconfig); -my $format = $conf->config($formatconfig); +my $format; +if ( $cgi->param('format') =~ /^([\w ]+)$/ ) { + $format = $1; +} else { + $format = $conf->config('batch_default_format'); +} my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; @@ -31,11 +32,14 @@ my (@date)=localtime(); my $jdate = sprintf("%03d", $date[5] % 100).sprintf("%03d", $date[7]); if ($format eq "BoM") { - my($reformat,$origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) = $conf->config('batchconfig'); - printf "A%10s%04u%06u%05u%54s\n",$origid,$pay_batch->batchnum,$jdate,$datacenter,""; - printf "XD%03u%06u%-15s%-30s%09u%-12s \n",$typecode,$jdate,$shortname,$longname,$mybank,$myacct; + + my($reformat,$origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) = $conf->config("batchconfig-$format"); + %><%= sprintf( "A%10s%04u%06u%05u%54s\n",$origid,$pay_batch->batchnum,$jdate,$datacenter,""). + sprintf( "XD%03u%06u%-15s%-30s%09u%-12s \n",$typecode,$jdate,$shortname,$longname,$mybank,$myacct ) + %><% + }elsif ($format eq "CSV file for TD Canada Trust Merchant PC Batch"){ - 1; +# 1; }else{ die "Unknown format for batch in batchconfig. \n"; } @@ -46,30 +50,36 @@ for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } {'batchnum'=>$pay_batch->batchnum} ) ) { -$cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; -my( $mon, $y ) = ( $2, $1 ); -$mon = "0$mon" if $mon < 10; -my $exp = "$mon$y"; -$batchcount++; -$batchtotal += $cust_pay_batch->amount; + $cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/; + my( $mon, $y ) = ( $2, $1 ); + $mon = "0$mon" if $mon < 10; + my $exp = "$mon$y"; + $batchcount++; + $batchtotal += $cust_pay_batch->amount; + + if ($format eq "BoM") { -if ($format eq "BoM") { - my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo ); - printf "D%010u%09u%-12s%-29s%-19s\n",$cust_pay_batch->amount*100,$aba,$account,$cust_pay_batch->payname,$cust_pay_batch->invnum; -}elsif ($format eq "CSV file for TD Canada Trust Merchant PC Batch"){ -%>,,,,<%= $cust_pay_batch->payinfo %>,<%= $exp %>,<%= $cust_pay_batch->amount %>,<%= $cust_pay_batch->paybatchnum %> -<% }else{ - die "I'm already dead, but you did not know that.\n"; -} + my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo ); + %><%= sprintf( "D%010u%09u%-12s%-29s%-19s\n",$cust_pay_batch->amount*100,$aba,$account,$cust_pay_batch->payname,$cust_pay_batch->invnum %><% + + } elsif ($format eq "CSV file for TD Canada Trust Merchant PC Batch") { + + %>,,,,<%= $cust_pay_batch->payinfo %>,<%= $exp %>,<%= $cust_pay_batch->amount %>,<%= $cust_pay_batch->paybatchnum %><% + + } else { + die "I'm already dead, but you did not know that.\n"; + } } if ($format eq "BoM") { - printf "YD%08u%014u%56s\n",$batchcount,$batchtotal*100,""; - printf "Z%014u%05u%014u%05u%41s\n",$batchtotal*100,$batchcount,"0","0",""; -}elsif ($format eq "CSV file for TD Canada Trust Merchant PC Batch"){ - 1; -} else{ + + %><%= sprintf( "YD%08u%014u%56s\n",$batchcount,$batchtotal*100,"" ). + sprintf( "Z%014u%05u%014u%05u%41s\n",$batchtotal*100,$batchcount,"0","0","" ) %><% + +} elsif ($format eq "CSV file for TD Canada Trust Merchant PC Batch"){ + #1; +} else { die "I'm already dead (again), but you did not know that.\n"; } -- cgit v1.2.1 From 2aee6063dd65e588b884181542815f98319419bf Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 16 Jun 2006 00:33:31 +0000 Subject: oops i'm gonna do that too, now that the batch format file is not the same as the batch params files --- httemplate/misc/download-batch.cgi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi index 37b31c196..88b10eecf 100644 --- a/httemplate/misc/download-batch.cgi +++ b/httemplate/misc/download-batch.cgi @@ -33,7 +33,8 @@ my $jdate = sprintf("%03d", $date[5] % 100).sprintf("%03d", $date[7]); if ($format eq "BoM") { - my($reformat,$origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) = $conf->config("batchconfig-$format"); + my($origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) = + $conf->config("batchconfig-$format"); %><%= sprintf( "A%10s%04u%06u%05u%54s\n",$origid,$pay_batch->batchnum,$jdate,$datacenter,""). sprintf( "XD%03u%06u%-15s%-30s%09u%-12s \n",$typecode,$jdate,$shortname,$longname,$mybank,$myacct ) %><% -- cgit v1.2.1 From 1904b218cacfe4e96f3fd3b57f7242617099c485 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 16 Jun 2006 00:47:39 +0000 Subject: and fix the name for TD Canada Trust. and that's it for now. really. --- httemplate/misc/download-batch.cgi | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi index 88b10eecf..2d6f8a286 100644 --- a/httemplate/misc/download-batch.cgi +++ b/httemplate/misc/download-batch.cgi @@ -6,10 +6,10 @@ my $conf=new FS::Conf; http_header('Content-Type' => 'text/plain' ); my $format; -if ( $cgi->param('format') =~ /^([\w ]+)$/ ) { +if ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) { $format = $1; } else { - $format = $conf->config('batch_default_format'); + $format = $conf->config('batch-default_format'); } my $oldAutoCommit = $FS::UID::AutoCommit; @@ -39,7 +39,7 @@ if ($format eq "BoM") { sprintf( "XD%03u%06u%-15s%-30s%09u%-12s \n",$typecode,$jdate,$shortname,$longname,$mybank,$myacct ) %><% -}elsif ($format eq "CSV file for TD Canada Trust Merchant PC Batch"){ +}elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){ # 1; }else{ die "Unknown format for batch in batchconfig. \n"; @@ -63,7 +63,7 @@ for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo ); %><%= sprintf( "D%010u%09u%-12s%-29s%-19s\n",$cust_pay_batch->amount*100,$aba,$account,$cust_pay_batch->payname,$cust_pay_batch->invnum %><% - } elsif ($format eq "CSV file for TD Canada Trust Merchant PC Batch") { + } elsif ($format eq "csv-td_canada_trust-merchant_pc_batch") { %>,,,,<%= $cust_pay_batch->payinfo %>,<%= $exp %>,<%= $cust_pay_batch->amount %>,<%= $cust_pay_batch->paybatchnum %><% @@ -78,7 +78,7 @@ if ($format eq "BoM") { %><%= sprintf( "YD%08u%014u%56s\n",$batchcount,$batchtotal*100,"" ). sprintf( "Z%014u%05u%014u%05u%41s\n",$batchtotal*100,$batchcount,"0","0","" ) %><% -} elsif ($format eq "CSV file for TD Canada Trust Merchant PC Batch"){ +} elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){ #1; } else { die "I'm already dead (again), but you did not know that.\n"; -- cgit v1.2.1 From aaad08cae3a0d46d012de5b78360101cda836c30 Mon Sep 17 00:00:00 2001 From: jeff Date: Fri, 16 Jun 2006 01:23:41 +0000 Subject: value issues and many bits remain --- FS/FS/cust_bill.pm | 3 ++- FS/FS/pay_batch.pm | 2 +- httemplate/misc/download-batch.cgi | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index c2a39afda..8a05fcf31 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -1286,10 +1286,11 @@ sub batch_card { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - my $pay_batch = qsearchs('pay_batch'=> ''); + my $pay_batch = qsearchs('pay_batch', {'status' => 'O'}); unless ($pay_batch) { $pay_batch = new FS::pay_batch; + $pay_batch->setfield('status' => 'O'); my $error = $pay_batch->insert; if ( $error ) { die "error creating new batch: $error\n"; diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm index 192c5df83..41b312c7a 100644 --- a/FS/FS/pay_batch.pm +++ b/FS/FS/pay_batch.pm @@ -98,7 +98,7 @@ sub check { my $error = $self->ut_numbern('batchnum') - || $self->ut_enum('status', [ '', 'I', 'R' ]) + || $self->ut_enum('status', [ 'O', 'I', 'R' ]) ; return $error if $error; diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi index 2d6f8a286..6172b1335 100644 --- a/httemplate/misc/download-batch.cgi +++ b/httemplate/misc/download-batch.cgi @@ -16,7 +16,7 @@ my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; -my $pay_batch = qsearchs('pay_batch', {'status'=>''} ); +my $pay_batch = qsearchs('pay_batch', {'status'=>'O'} ); die "No pending batch. \n" unless $pay_batch; my %batchhash = $pay_batch->hash; -- cgit v1.2.1 From c738a3c4923774b64960aa87fa58bd0751487edb Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 18 Jun 2006 12:54:49 +0000 Subject: ACLs: finish group edit (agents + rights) & browse --- FS/FS/AccessRight.pm | 139 ++++++++++++++++--------- FS/FS/access_group.pm | 57 ++++++++-- FS/FS/access_groupagent.pm | 24 +++-- FS/FS/m2name_Common.pm | 95 +++++++++++++++++ FS/FS/part_pkg.pm | 53 ++++++---- FS/MANIFEST | 3 + htetc/handler.pl | 4 + httemplate/browse/access_group.html | 45 ++++++++ httemplate/browse/access_user.html | 2 +- httemplate/edit/access_group.html | 36 +++++++ httemplate/edit/elements/edit.html | 21 ++++ httemplate/edit/part_pkg.cgi | 2 +- httemplate/edit/process/access_group.html | 10 ++ httemplate/edit/process/elements/process.html | 15 ++- httemplate/elements/checkboxes-table-name.html | 85 +++++++++++++++ httemplate/elements/checkboxes-table.html | 28 +++-- 16 files changed, 520 insertions(+), 99 deletions(-) create mode 100644 FS/FS/m2name_Common.pm create mode 100644 httemplate/elements/checkboxes-table-name.html diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 01d63e35d..5229e1e65 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -1,7 +1,7 @@ package FS::AccessRight; use strict; -user vars qw(@rights %rights); +use vars qw(@rights); # %rights); use Tie::IxHash; =head1 NAME @@ -19,59 +19,94 @@ assigned to users and/or groups. =cut +#@rights = ( +# 'Reports' => [ +# '_desc' => 'Access to high-level reporting', +# ], +# 'Configuration' => [ +# '_desc' => 'Access to configuration', +# +# 'Settings' => {}, +# +# 'agent' => [ +# '_desc' => 'Master access to reseller configuration', +# 'agent_type' => {}, +# 'agent' => {}, +# ], +# +# 'export_svc_pkg' => [ +# '_desc' => 'Access to export, service and package configuration', +# 'part_export' => {}, +# 'part_svc' => {}, +# 'part_pkg' => {}, +# 'pkg_class' => {}, +# ], +# +# 'billing' => [ +# '_desc' => 'Access to billing configuration', +# 'payment_gateway' => {}, +# 'part_bill_event' => {}, +# 'prepay_credit' => {}, +# 'rate' => {}, +# 'cust_main_county' => {}, +# ], +# +# 'dialup' => [ +# '_desc' => 'Access to dialup configuraiton', +# 'svc_acct_pop' => {}, +# ], +# +# 'broadband' => [ +# '_desc' => 'Access to broadband configuration', +# 'router' => {}, +# 'addr_block' => {}, +# ], +# +# 'misc' => [ +# 'part_referral' => {}, +# 'part_virtual_field' => {}, +# 'msgcat' => {}, +# 'inventory_class' => {}, +# ], +# +# }, +# +#); +# +##turn it into a more hash-like structure, but ordered via IxHash + +#well, this is what we have for now. could be ordered better, could be lots of +# things better, but this ACL system does 99% of what folks need and the UI +# isn't *that* bad @rights = ( - 'Reports' => [ - '_desc' => 'Access to high-level reporting', - ], - 'Configuration' => [ - '_desc' => 'Access to configuration', - - 'Settings' => {}, - - 'agent' => [ - '_desc' => 'Master access to reseller configuration', - 'agent_type' => {}, - 'agent' => {}, - ], - - 'export_svc_pkg' => [ - '_desc' => 'Access to export, service and package configuration', - 'part_export' => {}, - 'part_svc' => {}, - 'part_pkg' => {}, - 'pkg_class' => {}, - ], - - 'billing' => [ - '_desc' => 'Access to billing configuration', - 'payment_gateway' => {}, - 'part_bill_event' => {}, - 'prepay_credit' => {}, - 'rate' => {}, - 'cust_main_county' => {}, - ], - - 'dialup' => [ - '_desc' => 'Access to dialup configuraiton', - 'svc_acct_pop' => {}, - ], - - 'broadband' => [ - '_desc' => 'Access to broadband configuration', - 'router' => {}, - 'addr_block' => {}, - ], - - 'misc' => [ - 'part_referral' => {}, - 'part_virtual_field' => {}, - 'msgcat' => {}, - 'inventory_class' => {}, - ], - - }, + 'New customer', + 'View customer', + #'View Customer | View tickets', + 'Edit customer', + 'Cancel customer', + 'Delete customer', + + 'Order customer package', + 'Change customer package', + 'Edit customer package dates', + 'Customize customer package', + 'Suspend customer package', + 'Unsuspend customer package', + 'Cancel customer package immediately', + 'Cancel customer package later', + + 'Provision service', + 'Unprovision service', + #legacy link stuff + + 'Post payment', + 'Process payment', + 'Post credit', + #more financial stuff ); -#turn it into a more hash-like structure, but ordered via IxHash +sub rights { + @rights; +} diff --git a/FS/FS/access_group.pm b/FS/FS/access_group.pm index 9d870e57f..25190406f 100644 --- a/FS/FS/access_group.pm +++ b/FS/FS/access_group.pm @@ -3,8 +3,11 @@ package FS::access_group; use strict; use vars qw( @ISA ); use FS::Record qw( qsearch qsearchs ); +use FS::m2name_Common; +use FS::access_groupagent; +use FS::access_right; -@ISA = qw(FS::Record); +@ISA = qw(FS::m2m_Common FS::m2name_Common FS::Record); =head1 NAME @@ -27,15 +30,14 @@ FS::access_group - Object methods for access_group records =head1 DESCRIPTION -An FS::access_group object represents an example. FS::access_group inherits from +An FS::access_group object represents an access group. FS::access_group inherits from FS::Record. The following fields are currently supported: =over 4 =item groupnum - primary key -=item groupname - - +=item groupname - Access group name =back @@ -45,7 +47,7 @@ FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new example. To add the example to the database, see L<"insert">. +Creates a new access group. To add the access group to the database, see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. @@ -84,7 +86,7 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid access group. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. @@ -105,12 +107,51 @@ sub check { $self->SUPER::check; } +=item access_groupagent + +Returns all associated FS::access_groupagent records. + +=cut + +sub access_groupagent { + my $self = shift; + qsearch('access_groupagent', { 'groupnum' => $self->groupnum } ); +} + +=item access_rights + +Returns all associated FS::access_right records. + +=cut + +sub access_rights { + my $self = shift; + qsearch('access_right', { 'righttype' => 'FS::access_group', + 'rightobjnum' => $self->groupnum + } + ); +} + +=item access_right RIGHTNAME + +Returns the specified FS::access_right record. Can be used as a boolean, to +test if this group has the given RIGHTNAME. + +=cut + +sub access_right { + my( $self, $name ) = shift; + qsearchs('access_right', { 'righttype' => 'FS::access_group', + 'rightobjnum' => $self->groupnum, + 'rightname' => $name, + } + ); +} + =back =head1 BUGS -The author forgot to customize this manpage. - =head1 SEE ALSO L, schema.html from the base documentation. diff --git a/FS/FS/access_groupagent.pm b/FS/FS/access_groupagent.pm index 6b5def1a3..3de8feeed 100644 --- a/FS/FS/access_groupagent.pm +++ b/FS/FS/access_groupagent.pm @@ -3,6 +3,7 @@ package FS::access_groupagent; use strict; use vars qw( @ISA ); use FS::Record qw( qsearch qsearchs ); +use FS::agent; @ISA = qw(FS::Record); @@ -27,7 +28,7 @@ FS::access_groupagent - Object methods for access_groupagent records =head1 DESCRIPTION -An FS::access_groupagent object represents an example. FS::access_groupagent inherits from +An FS::access_groupagent object represents an group reseller virtualization. FS::access_groupagent inherits from FS::Record. The following fields are currently supported: =over 4 @@ -47,7 +48,7 @@ FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new example. To add the example to the database, see L<"insert">. +Creates a new group reseller virtualization. To add the record to the database, see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. @@ -86,7 +87,7 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid group reseller virtualization. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. @@ -100,20 +101,29 @@ sub check { my $error = $self->ut_numbern('groupagentnum') - || $self->ut_number('groupnum') - || $self->ut_number('agentnum') + || $self->ut_foreign_key('groupnum', 'access_group', 'groupnum') + || $self->ut_foreign_key('agentnum', 'agent', 'agentnum') ; return $error if $error; $self->SUPER::check; } +=item agent + +Returns the associated FS::agent object. + +=cut + +sub agent { + my $self = shift; + qsearchs('agent', { 'agentnum' => $self->agentnum } ); +} + =back =head1 BUGS -The author forgot to customize this manpage. - =head1 SEE ALSO L, schema.html from the base documentation. diff --git a/FS/FS/m2name_Common.pm b/FS/FS/m2name_Common.pm new file mode 100644 index 000000000..7c9637e27 --- /dev/null +++ b/FS/FS/m2name_Common.pm @@ -0,0 +1,95 @@ +package FS::m2name_Common; + +use strict; +use vars qw( @ISA $DEBUG ); +use FS::Schema qw( dbdef ); +use FS::Record qw( qsearch qsearchs ); #dbh ); + +@ISA = qw( FS::Record ); + +$DEBUG = 0; + +=head1 NAME + +FS::m2name_Common - Base class for tables with a related table listing names + +=head1 SYNOPSIS + +use FS::m2name_Common; + +@ISA = qw( FS::m2name_Common ); + +=head1 DESCRIPTION + +FS::m2name_Common is intended as a base class for classes which have a +related table that lists names. + +=head1 METHODS + +=over 4 + +=item process_m2name + +=cut + +sub process_m2name { + my( $self, %opt ) = @_; + + my $self_pkey = $self->dbdef_table->primary_key; + my $link_sourcekey = $opt{'num_col'} || $self_pkey; + + my $link_table = $self->_load_table($opt{'link_table'}); + + my $link_static = $opt{'link_static'} || {}; + + foreach my $name ( @{ $opt{'names_list'} } ) { + + my $obj = qsearchs( $link_table, { + $link_sourcekey => $self->$self_pkey(), + $opt{'name_col'} => $name, + %$link_static, + }); + + if ( $obj && ! $opt{'params'}->{"$link_table.$name"} ) { + + my $d_obj = $obj; #need to save $obj for below. + my $error = $d_obj->delete; + die "error deleting $d_obj for $link_table.$name: $error" if $error; + + } elsif ( $opt{'params'}->{"$link_table.$name"} && ! $obj ) { + + #ok to clobber it now (but bad form nonetheless?) + #$obj = new "FS::$link_table" ( { + $obj = "FS::$link_table"->new( { + $link_sourcekey => $self->$self_pkey(), + $opt{'name_col'} => $name, + %$link_static, + }); + my $error = $obj->insert; + die "error inserting $obj for $link_table.$name: $error" if $error; + } + + } + + ''; +} + +sub _load_table { + my( $self, $table ) = @_; + eval "use FS::$table"; + die $@ if $@; + $table; +} + +=back + +=head1 BUGS + +=head1 SEE ALSO + +L + +=cut + +1; + diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 05dc59913..de4d047de 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -1,7 +1,7 @@ package FS::part_pkg; use strict; -use vars qw( @ISA %freq %plans $DEBUG ); +use vars qw( @ISA %plans $DEBUG ); use Carp qw(carp cluck confess); use Tie::IxHash; use FS::Conf; @@ -571,6 +571,32 @@ sub is_free { } } + +sub freqs_href { + #method, class method or sub? #my $self = shift; + + tie my %freq, 'Tie::IxHash', + '0' => '(no recurring fee)', + '1h' => 'hourly', + '1d' => 'daily', + '1w' => 'weekly', + '2w' => 'biweekly (every 2 weeks)', + '1' => 'monthly', + '2' => 'bimonthly (every 2 months)', + '3' => 'quarterly (every 3 months)', + '6' => 'semiannually (every 6 months)', + '12' => 'annually', + '24' => 'biannually (every 2 years)', + '36' => 'triannually (every 3 years)', + '48' => '(every 4 years)', + '60' => '(every 5 years)', + '120' => '(every 10 years)', + ; + + \%freq; + +} + =item freq_pretty Returns an english representation of the I field, such as "monthly", @@ -578,29 +604,14 @@ Returns an english representation of the I field, such as "monthly", =cut -tie %freq, 'Tie::IxHash', - '0' => '(no recurring fee)', - '1h' => 'hourly', - '1d' => 'daily', - '1w' => 'weekly', - '2w' => 'biweekly (every 2 weeks)', - '1' => 'monthly', - '2' => 'bimonthly (every 2 months)', - '3' => 'quarterly (every 3 months)', - '6' => 'semiannually (every 6 months)', - '12' => 'annually', - '24' => 'biannually (every 2 years)', - '36' => 'triannually (every 3 years)', - '48' => '(every 4 years)', - '60' => '(every 5 years)', - '120' => '(every 10 years)', -; - sub freq_pretty { my $self = shift; my $freq = $self->freq; - if ( exists($freq{$freq}) ) { - $freq{$freq}; + + my $freqs_href = $self->freqs_href; + + if ( exists($freqs_href->{$freq}) ) { + $freqs_href->{$freq}; } else { my $interval = 'month'; if ( $freq =~ /^(\d+)([hdw])$/ ) { diff --git a/FS/MANIFEST b/FS/MANIFEST index 9e3285dbb..098fe4a64 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -342,3 +342,6 @@ t/access_right.t FS/m2m_Common.pm FS/pay_batch.pm t/pay_batch.t +FS/ConfDefaults.pm +t/ConfDefaults.t +FS/m2name_Common.pm diff --git a/htetc/handler.pl b/htetc/handler.pl index d400a55ea..1dfa1376e 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -182,6 +182,10 @@ sub handler use FS::pkg_class; use FS::access_user; use FS::access_group; + use FS::access_usergroup; + use FS::access_groupagent; + use FS::access_right; + use FS::AccessRight; if ( %%%RT_ENABLED%%% ) { eval ' diff --git a/httemplate/browse/access_group.html b/httemplate/browse/access_group.html index 6ba89ea81..9ebb2b882 100644 --- a/httemplate/browse/access_group.html +++ b/httemplate/browse/access_group.html @@ -4,6 +4,45 @@ my $html_init = "Internal access groups control access to the back-office interface.

    ". qq!Add an internal access group

    !; +#false laziness w/access_user.html & agent_type.cgi +my $agents_sub = sub { + my $access_group = shift; + + [ map { + my $access_groupagent = $_; + my $agent = $access_groupagent->agent; + [ + { + 'data' => $agent->agent, + 'align' => 'left', + 'link' => $p. 'edit/agent.cgi?'. $agent->agentnum, + }, + ]; + } + grep { $_->agent } #? + $access_group->access_groupagent, + + ]; + +}; + +my $rights_sub = sub { + my $access_group = shift; + + [ map { my $access_right = $_; + [ + { + 'data' => $access_right->rightname, + 'align' => 'left', + }, + ]; + } + $access_group->access_rights, + + ]; + +}; + my $count_query = 'SELECT COUNT(*) FROM access_group'; my $link = [ $p.'edit/access_group.html?', 'groupnum' ]; @@ -22,12 +61,18 @@ my $link = [ $p.'edit/access_group.html?', 'groupnum' ]; 'count_query' => $count_query, 'header' => [ '#', 'Group name', + 'Agents', + 'Rights', ], 'fields' => [ 'groupnum', 'groupname', + $agents_sub, + $rights_sub, ], 'links' => [ $link, $link, + '', + '', ], ) %> diff --git a/httemplate/browse/access_user.html b/httemplate/browse/access_user.html index 38d5430b1..be11bf82a 100644 --- a/httemplate/browse/access_user.html +++ b/httemplate/browse/access_user.html @@ -4,7 +4,7 @@ my $html_init = "Internal users have access to the back-office interface. Typically, this is your employees and contractors, but in a VISP setup, you can also add accounts for your reseller's employees. It is highly recommended to add a separate account for each person rather than using role accounts.

    ". qq!Add an internal user

    !; -#false laziness w/agent_type.cgi +#false laziness w/access_group.html & agent_type.cgi my $groups_sub = sub { my $access_user = shift; diff --git a/httemplate/edit/access_group.html b/httemplate/edit/access_group.html index 11b8df7bc..d7f7667f4 100644 --- a/httemplate/edit/access_group.html +++ b/httemplate/edit/access_group.html @@ -5,6 +5,42 @@ 'groupnum' => 'Group number', 'groupname' => 'Group name', }, + 'viewall_dir' => 'browse', + + 'html_bottom' => + sub { + my $access_group = shift; + + "
    Group virtualized to customers of agents:
    ". + ntable("#cccccc",2). + '
    <%$AppName%> - <%$AppName%> +%# +% my $notfirst = 0; foreach my $action (sort keys %{$topactions}) { + > <%$topactions->{"$action"}->{'html'} |n %>
    '. + include( '/elements/checkboxes-table.html', + 'source_obj' => $access_group, + 'link_table' => 'access_groupagent', + 'target_table' => 'agent', + 'name_col' => 'agent', + 'target_link' => $p.'edit/agent.cgi?', + 'disable-able' => 1, + ). + '
    '. + + "
    Group rights:
    ". + ntable("#cccccc",2). + '
    '. + include( '/elements/checkboxes-table-name.html', + 'source_obj' => $access_group, + 'link_table' => 'access_right', + 'link_static' => { 'righttype' => + 'FS::access_group', + }, + 'num_col' => 'rightobjnum', + 'name_col' => 'rightname', + 'names_list' => [ FS::AccessRight->rights() ], + ). + '
    ' + + ; + }, ) %> diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 120c03a3c..94bf6eecd 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -16,6 +16,18 @@ # # 'menubar' => '', #menubar arrayref # + # #run when re-displaying with an error + # 'error_callback' => sub { my $cgi, $object = @_; }, + # + # #run when editing + # 'edit_callback' => sub { my $cgi, $object = @_; }, + # + # #run when adding + # 'new_callback' => sub { my $cgi, $object = @_; }, + # + # #broken'html_table_bottom' => '', #string or listref of additinal HTML to + # #add before
    + # # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' # # 'html_bottom' => '', #string @@ -43,16 +55,25 @@ map { $_ => scalar($cgi->param($_)) } fields($table) }); + &{$opt{'error_callback'}}($cgi, $object) + if $opt{'error_callback'}; + } elsif ( $cgi->keywords ) { #editing my( $query ) = $cgi->keywords; $query =~ /^(\d+)$/; $object = qsearchs( $table, { $pkey => $1 } ); + &{$opt{'edit_callback'}}($cgi, $object) + if $opt{'edit_callback'}; + } else { #adding $object = $class->new( {} ); + &{$opt{'new_callback'}}($cgi, $object) + if $opt{'new_callback'}; + } my $action = $object->$pkey() ? 'Edit' : 'Add'; diff --git a/httemplate/edit/part_pkg.cgi b/httemplate/edit/part_pkg.cgi index 462d5161f..b085d2260 100755 --- a/httemplate/edit/part_pkg.cgi +++ b/httemplate/edit/part_pkg.cgi @@ -237,7 +237,7 @@ if ( dbdef->table('pkg_svc')->column('primary_svc') ) { push @form_radio, 'pkg_svc_primary'; } -tie my %freq, 'Tie::IxHash', %FS::part_pkg::freq; +tie my %freq, 'Tie::IxHash', %{FS::part_pkg->freqs_href()}; if ( $part_pkg->dbdef_table->column('freq')->type =~ /(int)/i ) { delete $freq{$_} foreach grep { ! /^\d+$/ } keys %freq; } diff --git a/httemplate/edit/process/access_group.html b/httemplate/edit/process/access_group.html index e8c6d07b1..9bb9d1dda 100644 --- a/httemplate/edit/process/access_group.html +++ b/httemplate/edit/process/access_group.html @@ -1,5 +1,15 @@ <%= include( 'elements/process.html', 'table' => 'access_group', 'viewall_dir' => 'browse', + 'process_m2m' => { 'link_table' => 'access_groupagent', + 'target_table' => 'agent', + }, + 'process_m2name' => { + 'link_table' => 'access_right', + 'link_static' => { 'righttype' => 'FS::access_group', }, + 'num_col' => 'rightobjnum', + 'name_col' => 'rightname', + 'names_list' => [ FS::AccessRight->rights() ], + }, ) %> diff --git a/httemplate/edit/process/elements/process.html b/httemplate/edit/process/elements/process.html index 59ad35ee4..a6e3b50e3 100644 --- a/httemplate/edit/process/elements/process.html +++ b/httemplate/edit/process/elements/process.html @@ -16,7 +16,14 @@ # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' # 'process_m2m' => { 'link_table' => 'link_table_name', # 'target_table' => 'target_table_name', - # }. + # }, + # 'process_m2name' => { 'link_table' => 'link_table_name', + # 'link_static' => { 'column' => 'value' }, + # 'num_col' => 'column', #if column name is different in + # #link_table than source_table + # 'name_col' => 'name_column', + # 'names_list' => [ 'list', 'names' ], + # }, my(%opt) = @_; @@ -52,6 +59,12 @@ ); } + if ( !$error && $opt{'process_m2name'} ) { + $error = $new->process_m2name( %{ $opt{'process_m2name'} }, + 'params' => scalar($cgi->Vars), + ); + } + if ( $error ) { $cgi->param('error', $error); print $cgi->redirect(popurl(2). "$table.html?". $cgi->query_string ); diff --git a/httemplate/elements/checkboxes-table-name.html b/httemplate/elements/checkboxes-table-name.html new file mode 100644 index 000000000..8e9dd29d2 --- /dev/null +++ b/httemplate/elements/checkboxes-table-name.html @@ -0,0 +1,85 @@ +<% + + ## + # required + ## + # 'link_table' => 'table_name', + # + # 'name_col' => 'name_column', + # #or + # 'name_callback' => sub { }, + # + # 'names_list' => [ 'value', 'other value' ], + # + ## + # recommended (required?) + ## + # 'source_obj' => $obj, + # #or? + # #'source_table' => 'table_name', + # #'sourcenum' => '4', #current value of primary key in source_table + # # # (none is okay, just pass it if you have it) + ## + # optional + ## + # 'num_col' => 'col_name' #if column name is different in link_table than + # #source_table + # 'link_static' => { 'column' => 'value' }, + + my( %opt ) = @_; + + my( $source_pkey, $sourcenum, $source_obj ); + if ( $opt{'source_obj'} ) { + + $source_obj = $opt{'source_obj'}; + #$source_table = $source_obj->dbdef_table->table; + $source_pkey = $source_obj->dbdef_table->primary_key; + $sourcenum = $source_obj->$source_pkey(); + + } else { + + #$source_obj? + $source_pkey = $opt{'source_table'} + ? dbdef->table($opt{'source_table'})->primary_key + : ''; + $sourcenum = $opt{'sourcenum'}; + } + + $source_pkey = $opt{'num_col'} || $source_pkey; + + my $link_static = $opt{'link_static'} || {}; + +%> + +<% foreach my $name ( @{ $opt{'names_list'} } ) { + + my $checked; + if ( $cgi->param('error') ) { + + $checked = $cgi->param($opt{'link_table'}. ".$name" ) + ? 'CHECKED' + : ''; + + } else { + + $checked = + qsearchs( $opt{'link_table'}, { + $source_pkey => $sourcenum, + $opt{'name_col'} => $name, + %$link_static, + } ) + ? 'CHECKED' + : '' + + } + +%> + + " <%= $checked %> VALUE="ON"> + + <%= $name %> + +
    + +<% } %> + diff --git a/httemplate/elements/checkboxes-table.html b/httemplate/elements/checkboxes-table.html index d26ebef35..16376fa3d 100644 --- a/httemplate/elements/checkboxes-table.html +++ b/httemplate/elements/checkboxes-table.html @@ -68,16 +68,28 @@ ) { my $targetnum = $target_obj->$target_pkey(); + + my $checked; + if ( $cgi->param('error') ) { + + $checked = $cgi->param($target_pkey.$targetnum) + ? 'CHECKED' + : ''; + + } else { + + $checked = qsearchs( $opt{'link_table'}, { + $source_pkey => $sourcenum, + $target_pkey => $targetnum, + } ) + ? 'CHECKED' + : '' + + } + %> - $sourcenum, - $target_pkey => $targetnum, - }) - ? 'CHECKED ' - : '' - %> VALUE="ON"> + VALUE="ON"> <% if ( $opt{'target_link'} ) { %> -- cgit v1.2.1 From bb65b62d3d68ac0788f230fe2e35ebe1913fc0f5 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 18 Jun 2006 12:56:20 +0000 Subject: well, it isn't broken... --- httemplate/edit/elements/edit.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index 94bf6eecd..6fa2b3b6e 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -25,8 +25,9 @@ # #run when adding # 'new_callback' => sub { my $cgi, $object = @_; }, # - # #broken'html_table_bottom' => '', #string or listref of additinal HTML to - # #add before
    + # #uninmplemented + # #'html_table_bottom' => '', #string or listref of additinal HTML to + # # #add before
    # # 'viewall_dir' => '', #'search' or 'browse', defaults to 'search' # -- cgit v1.2.1 From c0e8da2f1e89729efa1032241e4239765a296514 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 19 Jun 2006 02:33:52 +0000 Subject: agent virtualization, take one (stuff from "inactive" changeset snuck into cust_main.pm and the package reporting changeset in search/cust_pkg.cgi here too) --- FS/FS/CurrentUser.pm | 50 ++++++++ FS/FS/Record.pm | 29 +++-- FS/FS/Schema.pm | 13 +- FS/FS/UID.pm | 19 ++- FS/FS/access_user.pm | 51 +++++++- FS/FS/cust_main.pm | 213 ++++++++++++++++++++----------- FS/FS/part_pkg.pm | 3 +- FS/MANIFEST | 1 + httemplate/elements/select-agent.html | 2 + httemplate/elements/select-table.html | 7 +- httemplate/elements/tr-select-agent.html | 11 +- httemplate/search/cust_bill.html | 19 ++- httemplate/search/cust_main.cgi | 49 ++++--- httemplate/search/cust_pkg.cgi | 162 ++++++++++++++++------- httemplate/search/svc_acct.cgi | 24 ++-- httemplate/search/svc_domain.cgi | 43 ++++--- httemplate/search/svc_forward.cgi | 24 ++-- 17 files changed, 514 insertions(+), 206 deletions(-) create mode 100644 FS/FS/CurrentUser.pm diff --git a/FS/FS/CurrentUser.pm b/FS/FS/CurrentUser.pm new file mode 100644 index 000000000..13d34167d --- /dev/null +++ b/FS/FS/CurrentUser.pm @@ -0,0 +1,50 @@ +package FS::CurrentUser; + +use vars qw($CurrentUser); + +#not at compile-time, circular dependancey causes trouble +#use FS::Record qw(qsearchs); +#use FS::access_user; + +=head1 NAME + +FS::CurrentUser - Package representing the current user + +=head1 SYNOPSIS + +=head1 DESCRIPTION + +=cut + +sub load_user { + my( $class, $user ) = @_; #, $pass + + #XXX remove me at some point + return "" if $user =~ /^fs_(queue|selfservice)$/; + + #not the best thing in the world... + eval "use FS::Record qw(qsearchs);"; + die $@ if $@; + eval "use FS::access_user;"; + die $@ if $@; + + $CurrentUser = qsearchs('access_user', { + 'username' => $user, + #'_password' => + } ); + + die "unknown user: $user" unless $CurrentUser; # or bad password + + $CurrentUser; +} + +=head1 BUGS + +Creepy crawlies + +=head1 SEE ALSO + +=cut + +1; + diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 67de071c6..fa0d2d87e 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -29,14 +29,12 @@ $me = '[FS::Record]'; $nowarn_identical = 0; -my $conf; my $rsa_module; my $rsa_loaded; my $rsa_encrypt; my $rsa_decrypt; FS::UID->install_callback( sub { - $conf = new FS::Conf; $File::CounterFile::DEFAULT_DIR = "/usr/local/etc/freeside/counters.". datasrc; } ); @@ -441,6 +439,7 @@ sub qsearch { } # Check for encrypted fields and decrypt them. + my $conf = new FS::Conf; if ($conf->exists('encryption') && eval 'defined(@FS::'. $table . '::encrypted_fields)') { foreach my $record (@return) { foreach my $field (eval '@FS::'. $table . '::encrypted_fields') { @@ -711,6 +710,7 @@ sub insert { # Encrypt before the database + my $conf = new FS::Conf; if ($conf->exists('encryption') && defined(eval '@FS::'. $table . 'encrypted_fields')) { foreach my $field (eval '@FS::'. $table . '::encrypted_fields') { $self->{'saved'} = $self->getfield($field); @@ -727,12 +727,18 @@ sub insert { my @values = map { _quote( $self->getfield($_), $table, $_) } @real_fields; #eslaf - my $statement = "INSERT INTO $table ( ". - join( ', ', @real_fields ). - ") VALUES (". - join( ', ', @values ). - ")" - ; + my $statement = "INSERT INTO $table "; + if ( @real_fields ) { + $statement .= + "( ". + join( ', ', @real_fields ). + ") VALUES (". + join( ', ', @values ). + ")" + ; + } else { + $statement .= 'DEFAULT VALUES'; + } warn "[debug]$me $statement\n" if $DEBUG > 1; my $sth = dbh->prepare($statement) or return dbh->errstr; @@ -995,6 +1001,7 @@ sub replace { return $error if $error; # Encrypt for replace + my $conf = new FS::Conf; my $saved = {}; if ($conf->exists('encryption') && defined(eval '@FS::'. $new->table . 'encrypted_fields')) { foreach my $field (eval '@FS::'. $new->table . '::encrypted_fields') { @@ -1635,7 +1642,8 @@ sub virtual_fields { "WHERE dbtable = '$table'"; my $dbh = dbh; my $result = $dbh->selectcol_arrayref($query); - confess $dbh->errstr if $dbh->err; + confess "Error executing virtual fields query: $query: ". $dbh->errstr + if $dbh->err; $virtual_fields_cache{$table} = $result; } @@ -1788,6 +1796,7 @@ sub encrypt { my ($self, $value) = @_; my $encrypted; + my $conf = new FS::Conf; if ($conf->exists('encryption')) { if ($self->is_encrypted($value)) { # Return the original value if it isn't plaintext. @@ -1821,6 +1830,7 @@ sub is_encrypted { sub decrypt { my ($self,$value) = @_; my $decrypted = $value; # Will return the original value if it isn't encrypted or can't be decrypted. + my $conf = new FS::Conf; if ($conf->exists('encryption') && $self->is_encrypted($value)) { $self->loadRSA; if (ref($rsa_decrypt) =~ /::RSA/) { @@ -1836,6 +1846,7 @@ sub loadRSA { #Initialize the Module $rsa_module = 'Crypt::OpenSSL::RSA'; # The Default + my $conf = new FS::Conf; if ($conf->exists('encryptionmodule') && $conf->config('encryptionmodule') ne '') { $rsa_module = $conf->config('encryptionmodule'); } diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 17d541e8c..7219274e6 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -16,12 +16,13 @@ use FS::UID qw(datasrc); $DEBUG = 0; $me = '[FS::Schema]'; -#ask FS::UID to run this stuff for us later -FS::UID->install_callback( sub { - #$conf = new FS::Conf; - &reload_dbdef("/usr/local/etc/freeside/dbdef.". datasrc) - unless $setup_hack; #$setup_hack needed now? -} ); +#hardcoded now... +##ask FS::UID to run this stuff for us later +#FS::UID->install_callback( sub { +# #$conf = new FS::Conf; +# &reload_dbdef("/usr/local/etc/freeside/dbdef.". datasrc) +# unless $setup_hack; #$setup_hack needed now? +#} ); =head1 NAME diff --git a/FS/FS/UID.pm b/FS/FS/UID.pm index 83b652b0f..21df9441b 100644 --- a/FS/FS/UID.pm +++ b/FS/FS/UID.pm @@ -13,6 +13,7 @@ use Exporter; use Carp qw(carp croak cluck); use DBI; use FS::Conf; +use FS::CurrentUser; @ISA = qw(Exporter); @EXPORT_OK = qw(checkeuid checkruid cgisuidsetup adminsuidsetup forksuidsetup @@ -87,6 +88,12 @@ sub forksuidsetup { $dbh = &myconnect; + use FS::Schema qw(reload_dbdef); + reload_dbdef("/usr/local/etc/freeside/dbdef.$datasrc") + unless $FS::Schema::setup_hack; + + FS::CurrentUser->load_user($user); + foreach ( keys %callback ) { &{$callback{$_}}; # breaks multi-database installs # delete $callback{$_}; #run once @@ -98,7 +105,11 @@ sub forksuidsetup { } sub myconnect { - DBI->connect( getsecrets, {'AutoCommit' => 0, 'ChopBlanks' => 1, } ) + DBI->connect( getsecrets, { 'AutoCommit' => 0, + 'ChopBlanks' => 1, + 'ShowErrorStatement' => 1, + } + ) or die "DBI->connect error: $DBI::errstr\n"; } @@ -256,10 +267,10 @@ sub getsecrets { $user = $setuser if $setuser; die "No user!" unless $user; my($conf) = new FS::Conf $conf_dir; - my($line) = grep /^\s*$user\s/, $conf->config('mapsecrets'); + my($line) = grep /^\s*($user|\*)\s/, $conf->config('mapsecrets'); die "User $user not found in mapsecrets!" unless $line; - $line =~ /^\s*$user\s+(.*)$/; - $secrets = $1; + $line =~ /^\s*($user|\*)\s+(.*)$/; + $secrets = $2; die "Illegal mapsecrets line for user?!" unless $secrets; ($datasrc, $db_user, $db_pass) = $conf->config($secrets) or die "Can't get secrets: $secrets: $!\n"; diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm index ca311d3b8..c95d02984 100644 --- a/FS/FS/access_user.pm +++ b/FS/FS/access_user.pm @@ -2,7 +2,7 @@ package FS::access_user; use strict; use vars qw( @ISA ); -use FS::Record qw( qsearch qsearchs ); +use FS::Record qw( qsearch qsearchs dbh ); use FS::m2m_Common; use FS::access_usergroup; @@ -29,7 +29,7 @@ FS::access_user - Object methods for access_user records =head1 DESCRIPTION -An FS::access_user object represents an example. FS::access_user inherits from +An FS::access_user object represents an internal access user. FS::access_user inherits from FS::Record. The following fields are currently supported: =over 4 @@ -52,7 +52,7 @@ FS::Record. The following fields are currently supported: =item new HASHREF -Creates a new example. To add the example to the database, see L<"insert">. +Creates a new internal access user. To add the user to the database, see L<"insert">. Note that this stores the hash reference, not a distinct copy of the hash it points to. You can ask the object for a copy with the I method. @@ -91,7 +91,7 @@ returns the error, otherwise returns false. =item check -Checks all fields to make sure this is a valid example. If there is +Checks all fields to make sure this is a valid internal access user. If there is an error, returns the error, otherwise returns false. Called by the insert and replace methods. @@ -151,12 +151,51 @@ sub access_usergroup { # #} +=item agentnums + +Returns a list of agentnums this user can view (via group membership). + +=cut + +sub agentnums { + my $self = shift; + my $sth = dbh->prepare( + "SELECT DISTINCT agentnum FROM access_usergroup + JOIN access_groupagent USING ( groupnum ) + WHERE usernum = ?" + ) or die dbh->errstr; + $sth->execute($self->usernum) or die $sth->errstr; + map { $_->[0] } @{ $sth->fetchall_arrayref }; +} + +=item agentnums_href + +Returns a hashref of agentnums this user can view. + +=cut + +sub agentnums_href { + my $self = shift; + { map { $_ => 1 } $self->agentnums }; +} + +=item agentnums_sql + +Returns an sql fragement to select only agentnums this user can view. + +=cut + +sub agentnums_sql { + my $self = shift; + '( '. + join( ' OR ', map "agentnum = $_", $self->agentnums ). + ' )'; +} + =back =head1 BUGS -The author forgot to customize this manpage. - =head1 SEE ALSO L, schema.html from the base documentation. diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 2763c1386..8956d5b26 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -20,6 +20,7 @@ use Date::Parse; #use Date::Manip; use String::Approx qw(amatch); use Business::CreditCard 0.28; +use Locale::Country; use FS::UID qw( getotaker dbh ); use FS::Record qw( qsearchs qsearch dbdef ); use FS::Misc qw( send_email ); @@ -79,7 +80,7 @@ sub _cache { my $self = shift; my ( $hashref, $cache ) = @_; if ( exists $hashref->{'pkgnum'} ) { -# #@{ $self->{'_pkgnum'} } = (); + #@{ $self->{'_pkgnum'} } = (); my $subcache = $cache->subcache( 'pkgnum', 'cust_pkg', $hashref->{custnum}); $self->{'_pkgnum'} = $subcache; #push @{ $self->{'_pkgnum'} }, @@ -3186,6 +3187,7 @@ This interface may change in the future. sub invoicing_list { my( $self, $arrayref ) = @_; + if ( $arrayref ) { my @cust_main_invoice; if ( $self->custnum ) { @@ -3220,12 +3222,14 @@ sub invoicing_list { warn $error if $error; } } + if ( $self->custnum ) { map { $_->address } qsearch( 'cust_main_invoice', { 'custnum' => $self->custnum } ); } else { (); } + } =item check_invoicing_list ARRAYREF @@ -3303,6 +3307,18 @@ sub invoicing_list_addpost { $self->invoicing_list(\@invoicing_list); } +=item invoicing_list_emailonly + +Returns the list of email invoice recipients (invoicing_list without non-email +destinations such as POST and FAX). + +=cut + +sub invoicing_list_emailonly { + my $self = shift; + grep { $_ !~ /^([A-Z]+)$/ } $self->invoicing_list; +} + =item referral_cust_main [ DEPTH [ EXCLUDE_HASHREF ] ] Returns an array of customers referred by this customer (referral_custnum set @@ -3600,6 +3616,17 @@ sub ship_contact { : $self->contact; } +=item country_full + +Returns this customer's full country name + +=cut + +sub country_full { + my $self = shift; + code2country($self->country); +} + =item status Returns a status string for this customer, currently: @@ -3610,6 +3637,8 @@ Returns a status string for this customer, currently: =item active - One or more recurring packages is active +=item inactive - No active recurring packages, but otherwise unsuspended/uncancelled (the inactive status is new - previously inactive customers were mis-identified as cancelled) + =item suspended - All non-cancelled recurring packages are suspended =item cancelled - All recurring packages are cancelled @@ -3620,7 +3649,7 @@ Returns a status string for this customer, currently: sub status { my $self = shift; - for my $status (qw( prospect active suspended cancelled )) { + for my $status (qw( prospect active inactive suspended cancelled )) { my $method = $status.'_sql'; my $numnum = ( my $sql = $self->$method() ) =~ s/cust_main\.custnum/?/g; my $sth = dbh->prepare("SELECT $sql") or die dbh->errstr; @@ -3635,14 +3664,18 @@ Returns a hex triplet color string for this customer's status. =cut -my %statuscolor = ( - 'prospect' => '000000', - 'active' => '00CC00', - 'suspended' => 'FF9900', - 'cancelled' => 'FF0000', -); + sub statuscolor { my $self = shift; + + my %statuscolor = ( + 'prospect' => '7e0079', #'000000', #black? naw, purple + 'active' => '00CC00', #green + 'inactive' => '0000CC', #blue + 'suspended' => 'FF9900', #yellow + 'cancelled' => 'FF0000', #red + ); + $statuscolor{$self->status}; } @@ -3659,25 +3692,40 @@ with no packages ever ordered) =cut +use vars qw($select_count_pkgs); +$select_count_pkgs = + "SELECT COUNT(*) FROM cust_pkg + WHERE cust_pkg.custnum = cust_main.custnum"; + sub prospect_sql { " - 0 = ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - ) + 0 = ( $select_count_pkgs ) "; } =item active_sql -Returns an SQL expression identifying active cust_main records. +Returns an SQL expression identifying active cust_main records (customers with +no active recurring packages, but otherwise unsuspended/uncancelled). =cut sub active_sql { " - 0 < ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND ". FS::cust_pkg->active_sql. " + 0 < ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " ) "; } +=item inactive_sql + +Returns an SQL expression identifying inactive cust_main records (customers with +active recurring packages). + +=cut + +sub inactive_sql { " + 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " ) + AND + 0 < ( $select_count_pkgs AND ". FS::cust_pkg->inactive_sql. " ) +"; } + =item susp_sql =item suspended_sql @@ -3685,23 +3733,12 @@ Returns an SQL expression identifying suspended cust_main records. =cut -#my $recurring_sql = FS::cust_pkg->recurring_sql; -my $recurring_sql = " - '0' != ( select freq from part_pkg - where cust_pkg.pkgpart = part_pkg.pkgpart ) -"; sub suspended_sql { susp_sql(@_); } sub susp_sql { " - 0 < ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND $recurring_sql - AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) - ) - AND 0 = ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND ". FS::cust_pkg->active_sql. " - ) + 0 < ( $select_count_pkgs AND ". FS::cust_pkg->suspended_sql. " ) + AND + 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " ) "; } =item cancel_sql @@ -3712,16 +3749,21 @@ Returns an SQL expression identifying cancelled cust_main records. =cut sub cancelled_sql { cancel_sql(@_); } -sub cancel_sql { " - 0 < ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - ) - AND 0 = ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - AND $recurring_sql - AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) - ) -"; } +sub cancel_sql { + + my $recurring_sql = FS::cust_pkg->recurring_sql; + #my $recurring_sql = " + # '0' != ( select freq from part_pkg + # where cust_pkg.pkgpart = part_pkg.pkgpart ) + #"; + + " + 0 < ( $select_count_pkgs ) + AND 0 = ( $select_count_pkgs AND $recurring_sql + AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) + ) + "; +} =item uncancel_sql =item uncancelled_sql @@ -3732,15 +3774,12 @@ Returns an SQL expression identifying un-cancelled cust_main records. sub uncancelled_sql { uncancel_sql(@_); } sub uncancel_sql { " - ( 0 < ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum + ( 0 < ( $select_count_pkgs AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) ) - OR 0 = ( SELECT COUNT(*) FROM cust_pkg - WHERE cust_pkg.custnum = cust_main.custnum - ) + OR 0 = ( $select_count_pkgs ) ) "; } @@ -3802,11 +3841,18 @@ Returns a (possibly empty) array of FS::cust_main objects. sub smart_search { my %options = @_; my $search = delete $options{'search'}; - my @cust_main = (); + #here is the agent virtualization + my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql; + + my @cust_main = (); if ( $search =~ /^\s*(\d+)\s*$/ ) { # customer # search - push @cust_main, qsearch('cust_main', { 'custnum' => $1, %options } ); + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $1, %options }, + 'extra_sql' => " AND $agentnums_sql", #agent virtualization + } ); } elsif ( $search =~ /^\s*(\S.*\S)\s*$/ ) { #value search @@ -3820,50 +3866,65 @@ sub smart_search { if defined dbdef->table('cust_main')->column('ship_last'); $sql .= ' )'; - push @cust_main, qsearch( 'cust_main', \%options, '', $sql ); + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => \%options, + 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization + } ); unless ( @cust_main ) { #no exact match, trying substring/fuzzy #still some false laziness w/ search/cust_main.cgi #substring - push @cust_main, qsearch( 'cust_main', - { 'last' => { 'op' => 'ILIKE', - 'value' => "%$q_value%" }, - %options, - } - ); - push @cust_main, qsearch( 'cust_main', - { 'ship_last' => { 'op' => 'ILIKE', - 'value' => "%$q_value%" }, - %options, - - } - ) + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { 'last' => { 'op' => 'ILIKE', + 'value' => "%$q_value%" }, + %options, + }, + 'extra_sql' => " AND $agentnums_sql", #agent virtualizaiton + } ); + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { 'ship_last' => { 'op' => 'ILIKE', + 'value' => "%$q_value%" }, + %options, + }, + 'extra_sql' => " AND $agentnums_sql", #agent virtualization + } ) if defined dbdef->table('cust_main')->column('ship_last'); - push @cust_main, qsearch( 'cust_main', - { 'company' => { 'op' => 'ILIKE', - 'value' => "%$q_value%" }, - %options, - } - ); - push @cust_main, qsearch( 'cust_main', - { 'ship_company' => { 'op' => 'ILIKE', - 'value' => "%$q_value%" }, - %options, - } - ) + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { 'company' => { 'op' => 'ILIKE', + 'value' => "%$q_value%" }, + %options, + }, + 'extra_sql' => " AND $agentnums_sql", #agent virtualization + } ); + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { 'ship_company' => { 'op' => 'ILIKE', + 'value' => "%$q_value%" }, + %options, + }, + 'extra_sql' => " AND $agentnums_sql", #agent virtualization + } ) if defined dbdef->table('cust_main')->column('ship_last'); #fuzzy push @cust_main, FS::cust_main->fuzzy_search( - { 'last' => $value }, - \%options, + { 'last' => $value }, #fuzzy hashref + \%options, #hashref + '', #select + " AND $agentnums_sql", #extra_sql #agent virtualization ); push @cust_main, FS::cust_main->fuzzy_search( - { 'company' => $value }, - \%options, + { 'company' => $value }, #fuzzy hashref + \%options, #hashref + '', #select + " AND $agentnums_sql", #extra_sql #agent virtualization ); } diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index de4d047de..aa39d890b 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -608,7 +608,8 @@ sub freq_pretty { my $self = shift; my $freq = $self->freq; - my $freqs_href = $self->freqs_href; + #my $freqs_href = $self->freqs_href; + my $freqs_href = freqs_href(); if ( exists($freqs_href->{$freq}) ) { $freqs_href->{$freq}; diff --git a/FS/MANIFEST b/FS/MANIFEST index 098fe4a64..6db82710c 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -345,3 +345,4 @@ t/pay_batch.t FS/ConfDefaults.pm t/ConfDefaults.t FS/m2name_Common.pm +FS/CurrentUser.pm diff --git a/httemplate/elements/select-agent.html b/httemplate/elements/select-agent.html index aa480a54b..78ec25f82 100644 --- a/httemplate/elements/select-agent.html +++ b/httemplate/elements/select-agent.html @@ -11,6 +11,8 @@ 'value' => $agentnum, 'empty_label' => 'all', 'hashref' => { 'disabled' => '' }, + 'extra_sql' => ' AND '. + $FS::CurrentUser::CurrentUser->agentnums_sql, %select_opt, ) %> diff --git a/httemplate/elements/select-table.html b/httemplate/elements/select-table.html index 96f0177fb..6c8089b31 100644 --- a/httemplate/elements/select-table.html +++ b/httemplate/elements/select-table.html @@ -10,6 +10,7 @@ ##opt # 'empty_label' => '', #better specify it though, the default might change # 'hashref' => {}, + # 'extra_sql' => '', # 'records' => \@records, #instead of hashref # 'pre_options' => [ 'value' => 'option' ], #before normal options @@ -25,7 +26,11 @@ if ( $opt{'records'} ) { @records = @{ $opt{'records'} }; } else { - @records = qsearch( $opt{'table'}, ( $opt{'hashref'} || {} ) ); + @records = qsearch( { + 'table' => $opt{'table'}, + 'hashref' => ( $opt{'hashref'} || {} ), + 'extra_sql' => ( $opt{'extra_sql'} || '' ), + }); } my @pre_options = $opt{'pre_options'} ? @{ $opt{'pre_options'} } : (); diff --git a/httemplate/elements/tr-select-agent.html b/httemplate/elements/tr-select-agent.html index 2227262b6..83c8d1758 100644 --- a/httemplate/elements/tr-select-agent.html +++ b/httemplate/elements/tr-select-agent.html @@ -3,9 +3,16 @@ my @agents; if ( $opt{'agents'} ) { - @agents = @{ $opt{'agents'} }; + #@agents = @{ $opt{'agents'} }; + #here is the agent virtualization + my $agentnums_href = $FS::CurrentUser::CurrentUser->agentnums_href; + @agents = grep $agentnums_href->{$_->agentnum}, @{ $opt{'agents'} }; } else { - @agents = qsearch( 'agent', { disabled=>'' } ); + @agents = qsearch( { + 'table' => 'agent', + 'hashref' => { disabled=>'' }, + 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, + }); } %> diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html index 2108653a8..16128d5c8 100755 --- a/httemplate/search/cust_bill.html +++ b/httemplate/search/cust_bill.html @@ -1,4 +1,9 @@ <% + + my $join_cust_main = 'LEFT JOIN cust_main USING ( custnum )'; + #here is the agent virtualization + my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql; + my( $count_query, $sql_query ); my( $count_addl ) = ( '' ); my( $distinct ) = ( '' ); @@ -6,11 +11,15 @@ my($agentnum) = ( '' ); my($open, $days) = ( '', '' ); if ( $cgi->param('invnum') =~ /^\s*(FS-)?(\d+)\s*$/ ) { - $count_query = "SELECT COUNT(*) FROM cust_bill WHERE invnum = $2"; + $count_query = + "SELECT COUNT(*) FROM cust_bill $join_cust_main". + " WHERE invnum = $2 AND $agentnums_sql"; #agent virtualization $sql_query = { 'table' => 'cust_bill', + 'addl_from' => $join_cust_main, 'hashref' => { 'invnum' => $2 }, #'select' => '*', + 'extra_sql' => " AND $agentnums_sql", #agent virtualization }; } else { #if ( $cgi->param('begin') || $cgi->param('end') @@ -68,10 +77,10 @@ push @where, "cust_bill._date < ". (time-86400*$days) if $days; } + #here is the agent virtualization + push @where, $agentnums_sql; my $extra_sql = scalar(@where) ? 'WHERE '. join(' AND ', @where) : ''; - my $addl_from = 'left join cust_main using ( custnum )'; - if ( $cgi->param('newest_percust') ) { $distinct = 'DISTINCT ON ( cust_bill.custnum )'; $orderby = 'ORDER BY cust_bill.custnum ASC, cust_bill._date DESC'; @@ -85,11 +94,11 @@ '$%.2f total outstanding balance', ]; } - $count_query .= " FROM cust_bill $addl_from $extra_sql"; + $count_query .= " FROM cust_bill $join_cust_main $extra_sql"; $sql_query = { 'table' => 'cust_bill', - 'addl_from' => $addl_from, + 'addl_from' => $join_cust_main, 'hashref' => {}, 'select' => "$distinct ". join(', ', 'cust_bill.*', diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 733e5dc15..5b7ccec1c 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -117,6 +117,10 @@ if ( $cgi->param('browse') my $addl_qual = join(' AND ', @qual); + #here is the agent virtualization + $addl_qual .= ( $addl_qual ? ' AND ' : '' ). + $FS::CurrentUser::CurrentUser->agentnums_sql; + if ( $addl_qual ) { $qual .= ' AND ' if $qual; $qual .= $addl_qual; @@ -322,32 +326,43 @@ END print "

    ". $pager. include('/elements/table-grid.html'). < - - (bill) name - company + + (bill) name + company END if ( defined dbdef->table('cust_main')->column('ship_last') ) { print <(service) name - company + (service) name + company END } foreach my $addl_header ( @addl_headers ) { - print "$addl_header"; + print ''. "$addl_header"; } print <Packages - Services + Packages + Services END + my $bgcolor1 = '#eeeeee'; + my $bgcolor2 = '#ffffff'; + my $bgcolor; + my(%saw,$cust_main); foreach $cust_main ( sort $sortby grep(!$saw{$_->custnum}++, @cust_main) ) { + + if ( $bgcolor eq $bgcolor1 ) { + $bgcolor = $bgcolor2; + } else { + $bgcolor = $bgcolor1; + } + my($custnum,$last,$first,$company)=( $cust_main->custnum, $cust_main->getfield('last'), @@ -377,9 +392,9 @@ END %> - ><%= $custnum %> - ><%= "$last, $first" %> - ><%= $pcompany %> + ><%= $custnum %> + ><%= "$last, $first" %> + ><%= $pcompany %> <% if ( defined dbdef->table('cust_main')->column('ship_last') ) { @@ -393,14 +408,14 @@ END : ' '; %> - ><%= "$ship_last, $ship_first" %> - ><%= $pship_company %> + ><%= "$ship_last, $ship_first" %> + ><%= $pship_company %> <% } foreach my $addl_col ( @addl_cols ) { %> - ALIGN=right> + ALIGN=right> <% if ( $addl_col eq 'tickets' ) { if ( @custom_priorities ) { @@ -457,14 +472,14 @@ END #my(@cust_svc) = qsearch( 'cust_svc', { 'pkgnum' => $_->pkgnum } ); my $rowspan = scalar(@cust_svc) || 1; - print $n1, qq!$pkg - $comment!; + print $n1, qq!$pkg - $comment!; my($n2)=''; foreach my $cust_svc ( @cust_svc ) { my($label, $value, $svcdb) = $cust_svc->label; my($svcnum) = $cust_svc->svcnum; my($sview) = $p.'view'; - print $n2,qq!$label!, - qq!$value!; + print $n2,qq!$label!, + qq!$value!; $n2=""; } #print qq!\n!; diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index a2fb89c12..e8b3f490d 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -1,19 +1,80 @@ <% -my %part_pkg = map { $_->pkgpart => $_ } qsearch('part_pkg', {}); +# my %part_pkg = map { $_->pkgpart => $_ } qsearch('part_pkg', {}); my($query) = $cgi->keywords; -my $orderby; -my @where; -my $cjoin = ''; +my @where = (); + +## +# parse agent +## if ( $cgi->param('agentnum') =~ /^(\d+)$/ and $1 ) { - $cjoin = "LEFT JOIN cust_main USING ( custnum )"; push @where, "agentnum = $1"; } +## +# parse status +## + +if ( $cgi->param('magic') eq 'active' + || $cgi->param('status') eq 'active' ) { + + push @where, FS::cust_pkg->active_sql(); + +} elsif ( $cgi->param('magic') eq 'suspended' + || $cgi->param('status') eq 'suspended' ) { + + push @where, FS::cust_pkg->suspended_sql(); + +} elsif ( $cgi->param('magic') =~ /^cancell?ed$/ + || $cgi->param('status') =~ /^cancell?ed$/ ) { + + push @where, FS::cust_pkg->cancelled_sql(); + +} elsif ( $cgi->param('status') =~ /^(one-time charge|inactive)$/ ) { + + push @where, FS::cust_pkg->inactive_sql(); + +} + +### +# parse package class +### + +#false lazinessish w/graph/cust_bill_pkg.cgi +my $classnum = 0; +my @pkg_class = (); +if ( $cgi->param('classnum') =~ /^(\d*)$/ ) { + $classnum = $1; + if ( $classnum ) { #a specific class + push @where, "classnum = $classnum"; + + #@pkg_class = ( qsearchs('pkg_class', { 'classnum' => $classnum } ) ); + #die "classnum $classnum not found!" unless $pkg_class[0]; + #$title .= $pkg_class[0]->classname.' '; + + } elsif ( $classnum eq '' ) { #the empty class + + push @where, "classnum IS NULL"; + #$title .= 'Empty class '; + #@pkg_class = ( '(empty class)' ); + } elsif ( $classnum eq '0' ) { + #@pkg_class = qsearch('pkg_class', {} ); # { 'disabled' => '' } ); + #push @pkg_class, '(empty class)'; + } else { + die "illegal classnum"; + } +} +#eslaf + +### +# parse magic, legacy, etc. +### + +my $orderby; if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { $orderby = 'ORDER BY bill'; @@ -23,7 +84,8 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { #"bill <= $ending", "CASE WHEN bill IS NULL THEN 0 ELSE bill END >= $beginning ", "CASE WHEN bill IS NULL THEN 0 ELSE bill END <= $ending", - '( cancel IS NULL OR cancel = 0 )'; + #'( cancel IS NULL OR cancel = 0 )' + ; } else { @@ -33,30 +95,6 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { $orderby = 'ORDER BY pkgnum'; - if ( $cgi->param('magic') eq 'active' ) { - - #push @where, - # '( susp IS NULL OR susp = 0 )', - # '( cancel IS NULL OR cancel = 0)'; - push @where, FS::cust_pkg->active_sql(); - - } elsif ( $cgi->param('magic') eq 'suspended' ) { - - push @where, - 'susp IS NOT NULL', - 'susp != 0', - '( cancel IS NULL OR cancel = 0)'; - - } elsif ( $cgi->param('magic') =~ /^cancell?ed$/ ) { - - push @where, - 'cancel IS NOT NULL', - 'cancel != 0'; - - } else { - die "guru meditation #420"; - } - if ( $cgi->param('pkgpart') =~ /^(\d+)$/ ) { push @where, "pkgpart = $1"; } @@ -84,21 +122,35 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { } +## +# setup queries, links, subs, etc. for the search +## + +# here is the agent virtualization +push @where, $FS::CurrentUser::CurrentUser->agentnums_sql; + my $extra_sql = scalar(@where) ? ' WHERE '. join(' AND ', @where) : ''; -my $count_query = "SELECT COUNT(*) FROM cust_pkg $cjoin $extra_sql"; +my $addl_from = 'LEFT JOIN cust_main USING ( custnum ) '. + 'LEFT JOIN part_pkg USING ( pkgpart ) '. + 'LEFT JOIN pkg_class USING ( classnum ) '; + +my $count_query = "SELECT COUNT(*) FROM cust_pkg $addl_from $extra_sql"; my $sql_query = { 'table' => 'cust_pkg', 'hashref' => {}, 'select' => join(', ', 'cust_pkg.*', + ( map "part_pkg.$_", qw( pkg freq ) ), + 'pkg_class.classname', 'cust_main.custnum as cust_main_custnum', - FS::UI::Web::cust_sql_fields(), + FS::UI::Web::cust_sql_fields( + $cgi->param('cust_fields') + ), ), 'extra_sql' => "$extra_sql $orderby", - 'addl_from' => ' LEFT JOIN cust_main USING ( custnum ) ', - #' LEFT JOIN part_pkg USING ( pkgpart ) ' + 'addl_from' => $addl_from, }; my $link = sub { @@ -138,6 +190,10 @@ sub time_or_blank { }; } +### +# and finally, include the search template +### + %><%= include( 'elements/search.html', 'title' => 'Package Search Results', 'name' => 'packages', @@ -146,6 +202,7 @@ sub time_or_blank { #'redirect' => $link, 'header' => [ '#', 'Package', + 'Class', 'Status', 'Freq.', 'Setup', @@ -154,18 +211,25 @@ sub time_or_blank { 'Susp.', 'Expire', 'Cancel', - FS::UI::Web::cust_header(), + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ), 'Services', ], 'fields' => [ 'pkgnum', - sub { my $part_pkg = $part_pkg{shift->pkgpart}; - $part_pkg->pkg; # ' - '. $part_pkg->comment; + sub { #my $part_pkg = $part_pkg{shift->pkgpart}; + #$part_pkg->pkg; # ' - '. $part_pkg->comment; + $_[0]->pkg; # ' - '. $_[0]->comment; }, + 'classname', sub { ucfirst(shift->status); }, sub { #shift->part_pkg->freq_pretty; - my $part_pkg = $part_pkg{shift->pkgpart}; - $part_pkg->freq_pretty; + + #my $part_pkg = $part_pkg{shift->pkgpart}; + #$part_pkg->freq_pretty; + + FS::part_pkg::freq_pretty(shift); }, #sub { time2str('%b %d %Y', shift->setup); }, @@ -202,6 +266,7 @@ sub time_or_blank { }, ], 'color' => [ + '', '', '', sub { shift->statuscolor; }, @@ -212,12 +277,16 @@ sub time_or_blank { '', '', '', - ( map { '' } FS::UI::Web::cust_header() ), + ( map { '' } + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ) + ), '', ], - 'style' => [ '', '', 'b' ], - 'size' => [ '', '', '-1', ], - 'align' => 'rlclrrrrrr', + 'style' => [ '', '', '', 'b' ], + 'size' => [ '', '', '', '-1', ], + 'align' => 'rllclrrrrrr', 'links' => [ $link, $link, @@ -229,7 +298,12 @@ sub time_or_blank { '', '', '', - ( map { $clink } FS::UI::Web::cust_header() ), + '', + ( map { $clink } + FS::UI::Web::cust_header( + $cgi->param('cust_fields') + ) + ), '', ], ) diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi index b14591958..ef68ba05e 100755 --- a/httemplate/search/svc_acct.cgi +++ b/httemplate/search/svc_acct.cgi @@ -5,11 +5,9 @@ my $orderby = 'ORDER BY svcnum'; my($query)=$cgi->keywords; $query ||= ''; #to avoid use of unitialized value errors -my $cjoin = ''; my @extra_sql = (); if ( $query =~ /^UN_(.*)$/ ) { $query = $1; - $cjoin = 'LEFT JOIN cust_svc USING ( svcnum )'; push @extra_sql, 'pkgnum IS NULL'; } @@ -24,7 +22,6 @@ if ( $query eq 'svcnum' ) { push @extra_sql, "popnum = $1"; $orderby = "ORDER BY LOWER(username)"; } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { - $cjoin ||= 'LEFT JOIN cust_svc USING ( svcnum )'; push @extra_sql, "svcpart = $1"; $orderby = "ORDER BY uid"; #$orderby = "ORDER BY svcnum"; @@ -72,12 +69,20 @@ if ( $query eq 'svcnum' ) { } +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; + my $extra_sql = scalar(@extra_sql) ? ' WHERE '. join(' AND ', @extra_sql ) : ''; -my $count_query = "SELECT COUNT(*) FROM svc_acct $cjoin $extra_sql"; +my $count_query = "SELECT COUNT(*) FROM svc_acct $addl_from $extra_sql"; #if ( keys %svc_acct ) { # $count_query .= ' WHERE '. # join(' AND ', map "$_ = ". dbh->quote($svc_acct{$_}), @@ -94,10 +99,7 @@ my $sql_query = { FS::UI::Web::cust_sql_fields(), ), 'extra_sql' => "$extra_sql $orderby", - 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. - ' LEFT JOIN part_svc USING ( svcpart ) '. - ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) ', + 'addl_from' => $addl_from, }; my $link = [ "${p}view/svc_acct.cgi?", 'svcnum' ]; @@ -117,21 +119,21 @@ my $link_cust = sub { 'count_query' => $count_query, 'redirect' => $link, 'header' => [ '#', + 'Service', 'Account', 'UID', - 'Service', FS::UI::Web::cust_header(), ], 'fields' => [ 'svcnum', + 'svc', 'email', 'uid', - 'svc', \&FS::UI::Web::cust_fields, ], 'links' => [ $link, $link, $link, - '', + $link, ( map { $link_cust } FS::UI::Web::cust_header() ), diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi index b02eea8bd..1eda3733c 100755 --- a/httemplate/search/svc_domain.cgi +++ b/httemplate/search/svc_domain.cgi @@ -6,38 +6,50 @@ my($query)=$cgi->keywords; $query ||= ''; #to avoid use of unitialized value errors my $orderby = 'ORDER BY svcnum'; -my $join = ''; my %svc_domain = (); -my $extra_sql = ''; +my @extra_sql = (); if ( $query eq 'svcnum' ) { #$orderby = 'ORDER BY svcnum'; } elsif ( $query eq 'domain' ) { $orderby = 'ORDER BY domain'; -} elsif ( $query eq 'UN_svcnum' ) { +} elsif ( $query eq 'UN_svcnum' ) { #UN searches need to be acl'ed (and need to + #fix $agentnums_sql #$orderby = 'ORDER BY svcnum'; - $join = 'LEFT JOIN cust_svc USING ( svcnum )'; - $extra_sql = ' WHERE pkgnum IS NULL'; -} elsif ( $query eq 'UN_domain' ) { + push @extra_sql, 'pkgnum IS NULL'; +} elsif ( $query eq 'UN_domain' ) { #UN searches need to be acl'ed (and need to + #fix $agentnums_sql $orderby = 'ORDER BY domain'; - $join = 'LEFT JOIN cust_svc USING ( svcnum )'; - $extra_sql = ' WHERE pkgnum IS NULL'; + push @extra_sql, 'pkgnum IS NULL'; } elsif ( $cgi->param('svcpart') =~ /^(\d+)$/ ) { #$orderby = 'ORDER BY svcnum'; - $join = 'LEFT JOIN cust_svc USING ( svcnum )'; - $extra_sql = " WHERE svcpart = $1"; + push @extra_sql, "svcpart = $1"; } else { $cgi->param('domain') =~ /^([\w\-\.]+)$/; - $join = ''; $svc_domain{'domain'} = $1; } -my $count_query = "SELECT COUNT(*) FROM svc_domain $join $extra_sql"; +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; + +my $extra_sql = ''; +if ( @extra_sql ) { + $extra_sql = ( keys(%svc_domain) ? ' AND ' : ' WHERE ' ). + join(' AND ', @extra_sql ); +} + +my $count_query = "SELECT COUNT(*) FROM svc_domain $addl_from "; if ( keys %svc_domain ) { $count_query .= ' WHERE '. join(' AND ', map "$_ = ". dbh->quote($svc_domain{$_}), keys %svc_domain ); } +$count_query .= $extra_sql; my $sql_query = { 'table' => 'svc_domain', @@ -48,9 +60,7 @@ my $sql_query = { FS::UI::Web::cust_sql_fields(), ), 'extra_sql' => "$extra_sql $orderby", - 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum ) '. - 'LEFT JOIN cust_pkg USING ( pkgnum ) '. - 'LEFT JOIN cust_main USING ( custnum ) ', + 'addl_from' => $addl_from, }; my $link = [ "${p}view/svc_domain.cgi?", 'svcnum' ]; @@ -68,14 +78,17 @@ my $link_cust = sub { 'count_query' => $count_query, 'redirect' => $link, 'header' => [ '#', + 'Service', 'Domain', FS::UI::Web::cust_header(), ], 'fields' => [ 'svcnum', + 'svc', 'domain', \&FS::UI::Web::cust_fields, ], 'links' => [ $link, + $link, $link, ( map { $link_cust } FS::UI::Web::cust_header() diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi index a204e345f..4961967d7 100755 --- a/httemplate/search/svc_forward.cgi +++ b/httemplate/search/svc_forward.cgi @@ -5,14 +5,12 @@ my $conf = new FS::Conf; my($query)=$cgi->keywords; $query ||= ''; #to avoid use of unitialized value errors - my $orderby; -my $cjoin = ''; my @extra_sql = (); -if ( $query =~ /^UN_(.*)$/ ) { +if ( $query =~ /^UN_(.*)$/ ) { #UN searches need to be acl'ed (and need to + #fix $agentnums_sql $query = $1; - $cjoin = 'LEFT JOIN cust_svc USING ( svcnum )'; push @extra_sql, 'pkgnum IS NULL'; } @@ -22,12 +20,20 @@ if ( $query eq 'svcnum' ) { eidiot('unimplemented'); } +my $addl_from = ' LEFT JOIN cust_svc USING ( svcnum ) '. + ' LEFT JOIN part_svc USING ( svcpart ) '. + ' LEFT JOIN cust_pkg USING ( pkgnum ) '. + ' LEFT JOIN cust_main USING ( custnum ) '; + +#here is the agent virtualization +push @extra_sql, $FS::CurrentUser::CurrentUser->agentnums_sql; + my $extra_sql = scalar(@extra_sql) ? ' WHERE '. join(' AND ', @extra_sql ) : ''; -my $count_query = "SELECT COUNT(*) FROM svc_forward $cjoin $extra_sql"; +my $count_query = "SELECT COUNT(*) FROM svc_forward $addl_from $extra_sql"; my $sql_query = { 'table' => 'svc_forward', 'hashref' => {}, @@ -37,10 +43,7 @@ my $sql_query = { FS::UI::Web::cust_sql_fields(), ), 'extra_sql' => "$extra_sql $orderby", - 'addl_from' => ' LEFT JOIN cust_svc USING ( svcnum ) '. - ' LEFT JOIN part_svc USING ( svcpart ) '. - ' LEFT JOIN cust_pkg USING ( pkgnum ) '. - ' LEFT JOIN cust_main USING ( custnum ) ', + 'addl_from' => $addl_from, }; # Service #
    (click to view forward) @@ -100,16 +103,19 @@ my $link_cust = sub { 'count_query' => $count_query, 'redirect' => $link, 'header' => [ '#', + 'Service', 'Mail to', 'Forwards to', FS::UI::Web::cust_header(), ], 'fields' => [ 'svcnum', + 'svc', $format_src, $format_dst, \&FS::UI::Web::cust_fields, ], 'links' => [ $link, + $link, $link_src, $link_dst, ( map { $link_cust } -- cgit v1.2.1 From a59c9cdd72fdf85d81007ba86e81479f6ec8e6e5 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 19 Jun 2006 06:03:18 +0000 Subject: fix up the alternating colors on the customer search results --- httemplate/search/cust_main.cgi | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 5b7ccec1c..7d5941a16 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -473,6 +473,7 @@ END my $rowspan = scalar(@cust_svc) || 1; print $n1, qq!$pkg - $comment!; + my($n2)=''; foreach my $cust_svc ( @cust_svc ) { my($label, $value, $svcdb) = $cust_svc->label; @@ -482,9 +483,19 @@ END qq!$value!; $n2=""; } + + unless ( @cust_svc ) { + print qq! !; + } + #print qq!\n!; $n1=""; } + + unless ( @{$all_pkgs{$custnum}} ) { + print qq! !; + } + print ""; } -- cgit v1.2.1 From 6b12c14cc10503d6b0783e8ef71fe44d9a9b37b6 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 19 Jun 2006 08:05:28 +0000 Subject: add ability to select specific package defs. and package status to package report for qis --- FS/FS/Conf.pm | 29 +++-- FS/FS/ConfDefaults.pm | 68 +++++++++++ FS/FS/UI/Web.pm | 126 ++++++++++++++------- FS/FS/cust_main_Mixin.pm | 44 +++++++ FS/FS/cust_pkg.pm | 76 ++++++++++--- httemplate/config/config.cgi | 72 +++++++++--- httemplate/elements/menu.html | 4 +- httemplate/elements/select-cust-fields.html | 23 ++++ httemplate/elements/select-cust_pkg-status.html | 19 ++++ httemplate/elements/tr-select-cust-fields.html | 14 +++ httemplate/elements/tr-select-cust_pkg-status.html | 13 +++ httemplate/graph/cust_bill_pkg.cgi | 2 + httemplate/search/cust_pkg_report.cgi | 22 ---- httemplate/search/report_cust_pkg.html | 47 ++++++++ httemplate/view/cust_main/contacts.html | 2 +- 15 files changed, 457 insertions(+), 104 deletions(-) create mode 100644 FS/FS/ConfDefaults.pm create mode 100644 httemplate/elements/select-cust-fields.html create mode 100644 httemplate/elements/select-cust_pkg-status.html create mode 100644 httemplate/elements/tr-select-cust-fields.html create mode 100644 httemplate/elements/tr-select-cust_pkg-status.html delete mode 100755 httemplate/search/cust_pkg_report.cgi create mode 100755 httemplate/search/report_cust_pkg.html diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index e7b9fa556..57c18e678 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -4,6 +4,7 @@ use vars qw($default_dir @config_items $DEBUG ); use IO::File; use File::Basename; use FS::ConfItem; +use FS::ConfDefaults; $DEBUG = 0; @@ -1619,18 +1620,9 @@ httemplate/docs/config.html { 'key' => 'cust-fields', 'section' => 'UI', - 'description' => 'Which customer fields to display on reports', + 'description' => 'Which customer fields to display on reports by default', 'type' => 'select', - 'select_enum' => [ - 'Customer: Last, First or
    Company (Last, First)', - 'Cust# | Customer: custnum | Last, First or Company (Last, First)', - 'Name | Company: Last, First | Company', - 'Cust# | Name | Company: custnum | Last, First | Company', - '(bill) Customer | (service) Customer: Last, First or Company (Last, First) | (same for service address if present)', - 'Cust# | (bill) Customer | (service) Customer: custnum | Last, First or Company (Last, First) | (same for service address if present)', - '(bill) Name | (bill) Company | (service) Name | (service) Company: Last, First | Company | (same for service address if present)', - 'Cust# | (bill) Name | (bill) Company | (service) Name | (service) Company: custnum | Last, First | Company | (same for service address if present)', - ], + 'select_hash' => [ FS::ConfDefaults->cust_fields_avail() ], }, { @@ -1709,6 +1701,21 @@ httemplate/docs/config.html 'type' => 'checkbox', }, + { + 'key' => 'batch-default_format', + 'section' => 'billing', + 'description' => 'Default format for batches.', + 'type' => 'select', + 'select_enum' => [ 'csv-td_canada_trust-merchant_pc_batch', 'BoM' ] + }, + + { + 'key' => 'batchconfig-BoM', + 'section' => 'billing', + 'description' => 'Configuration for Bank of Montreal batching, seven lines: 1. Origin ID, 2. Datacenter, 3. Typecode, 4. Short name, 5. Long name, 6. Bank, 7. Bank account', + 'type' => 'textarea', + }, + ); 1; diff --git a/FS/FS/ConfDefaults.pm b/FS/FS/ConfDefaults.pm new file mode 100644 index 000000000..b9cbcfbdf --- /dev/null +++ b/FS/FS/ConfDefaults.pm @@ -0,0 +1,68 @@ +package FS::ConfDefaults; + +=head1 NAME + +FS::ConfDefaults - Freeside configuration default and available values + +=head1 SYNOPSIS + + use FS::ConfDefaults; + + @avail_cust_fields = FS::ConfDefaults->cust_fields_avail(); + +=head1 DESCRIPTION + +Just a small class to keep config default and available values + +=head1 METHODS + +=over 4 + +=item cust_fields_avail + +Returns a list, suitable for assigning to a hash, of available values and +labels for customer fields values. + +=cut + +# XXX should use msgcat for "Day phone" and "Night phone", but how? +sub cust_fields_avail { ( + + 'Customer' => + 'Last, First or Company (Last, First)', + 'Cust# | Customer' => + 'custnum | Last, First or Company (Last, First)', + + 'Name | Company' => + 'Last, First | Company', + 'Cust# | Name | Company' => + 'custnum | Last, First | Company', + + '(bill) Customer | (service) Customer' => + 'Last, First or Company (Last, First) | (same for service contact if present)', + 'Cust# | (bill) Customer | (service) Customer' => + 'custnum | Last, First or Company (Last, First) | (same for service contact if present)', + + '(bill) Name | (bill) Company | (service) Name | (service) Company' => + 'Last, First | Company | (same for service address if present)', + 'Cust# | (bill) Name | (bill) Company | (service) Name | (service) Company' => + 'custnum | Last, First | Company | (same for service address if present)', + + 'Cust# | Name | Company | Address 1 | Address 2 | City | State | Zip | Country | Day phone | Night phone | Invoicing email(s)' => + 'custnum | Last, First | Company | (all address fields ) | Day phone | Night phone | Invoicing email(s)', + +); } + +=back + +=head1 BUGS + +Not yet. + +=head1 SEE ALSO + +L + +=cut + +1; diff --git a/FS/FS/UI/Web.pm b/FS/FS/UI/Web.pm index 10ddbf33f..080ac6e64 100644 --- a/FS/FS/UI/Web.pm +++ b/FS/FS/UI/Web.pm @@ -8,6 +8,8 @@ use FS::Record qw(dbdef); #use FS::UI #@ISA = qw( FS::UI ); +$DEBUG = 0; + use Date::Parse; sub parse_beginning_ending { my($cgi) = @_; @@ -31,73 +33,116 @@ sub parse_beginning_ending { } ### -# cust_main report methods +# cust_main report subroutines ### -=item cust_header -Returns an array of customer information headers according to the -B configuration setting. +=item cust_header [ CUST_FIELDS_VALUE ] + +Returns an array of customer information headers according to the supplied +customer fields value, or if no value is supplied, the B +configuration value. =cut use vars qw( @cust_fields ); -sub cust_sql_fields { - my @fields = qw( last first company ); - push @fields, map "ship_$_", @fields - if dbdef->table('cust_main')->column('ship_last'); - map "cust_main.$_", @fields; -} - sub cust_header { warn "FS::svc_Common::cust_header called" if $DEBUG; - my $conf = new FS::Conf; - my %header2method = ( - 'Customer' => 'name', - 'Cust#' => 'custnum', - 'Name' => 'contact', - 'Company' => 'company', - '(bill) Customer' => 'name', - '(service) Customer' => 'ship_name', - '(bill) Name' => 'contact', - '(service) Name' => 'ship_contact', - '(bill) Company' => 'company', - '(service) Company' => 'ship_company', + 'Customer' => 'name', + 'Cust#' => 'custnum', + 'Name' => 'contact', + 'Company' => 'company', + '(bill) Customer' => 'name', + '(service) Customer' => 'ship_name', + '(bill) Name' => 'contact', + '(service) Name' => 'ship_contact', + '(bill) Company' => 'company', + '(service) Company' => 'ship_company', + 'Address 1' => 'address1', + 'Address 2' => 'address2', + 'City' => 'city', + 'State' => 'state', + 'Zip' => 'zip', + 'Country' => 'country_full', + 'Day phone' => 'daytime', # XXX should use msgcat, but how? + 'Night phone' => 'night', # XXX should use msgcat, but how? + 'Invoicing email(s)' => 'invoicing_list_emailonly', ); + my $cust_fields; my @cust_header; - if ( $conf->exists('cust-fields') - && $conf->config('cust-fields') =~ /^([\w \|\#\(\)]+):/ - ) - { - warn " found cust-fields configuration value" - if $DEBUG; + if ( @_ && $_[0] ) { - my $cust_fields = $1; - @cust_header = split(/ \| /, $cust_fields); - @cust_fields = map { $header2method{$_} } @cust_header; - } else { - warn " no cust-fields configuration value found; using default 'Customer'" + warn " using supplied cust-fields override". + " (ignoring cust-fields config file)" if $DEBUG; - @cust_header = ( 'Customer' ); - @cust_fields = ( 'name' ); + $cust_fields = shift; + + } else { + + my $conf = new FS::Conf; + if ( $conf->exists('cust-fields') + && $conf->config('cust-fields') =~ /^([\w \|\#\(\)]+):?/ + ) + { + warn " found cust-fields configuration value" + if $DEBUG; + $cust_fields = $1; + } else { + warn " no cust-fields configuration value found; using default 'Customer'" + if $DEBUG; + $cust_fields = 'Customer'; + } + } + @cust_header = split(/ \| /, $cust_fields); + @cust_fields = map { $header2method{$_} } @cust_header; + #my $svc_x = shift; @cust_header; } -=item cust_fields +=item cust_sql_fields [ CUST_FIELDS_VALUE ] + +Returns a list of fields for the SELECT portion of an SQL query. + +As with L, the fields returned are +defined by the supplied customer fields setting, or if no customer fields +setting is supplied, the cust-fields configuration value. + +=cut + +sub cust_sql_fields { + + my @fields = qw( last first company ); + push @fields, map "ship_$_", @fields; + push @fields, 'country'; + + cust_header(@_); + #inefficientish, but tiny lists and only run once per page + push @fields, + grep { my $field = $_; grep { $_ eq $field } @cust_fields } + qw( address1 address2 city state zip daytime night ); + + map "cust_main.$_", @fields; +} + +=item cust_fields SVC_OBJECT [ CUST_FIELDS_VALUE ] Given a svc_ object that contains fields from cust_main (say, from a JOINed search. See httemplate/search/svc_* for examples), returns an array -of customer information according to the cust-fields configuration -setting, or "(unlinked)" if this service is not linked to a customer. +of customer information, or "(unlinked)" if this service is not linked to a +customer. + +As with L, the fields returned are +defined by the supplied customer fields setting, or if no customer fields +setting is supplied, the cust-fields configuration value. =cut @@ -107,7 +152,8 @@ sub cust_fields { "(cust_fields: @cust_fields)" if $DEBUG > 1; - cust_header() unless @cust_fields; + #cust_header(@_) unless @cust_fields; #now need to cache to keep cust_fields + # #override incase we were passed as a sub my $seen_unlinked = 0; map { diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm index a114c5a8a..aa4143df1 100644 --- a/FS/FS/cust_main_Mixin.pm +++ b/FS/FS/cust_main_Mixin.pm @@ -89,6 +89,50 @@ sub ship_contact { : $self->cust_unlinked_msg; } +=item country_full + +Given an object that contains fields from cust_main (say, from a JOINed +search; see httemplate/search/ for examples), returns the equivalent of the +FS::cust_main I method, or "(unlinked)" if this object is not +linked to a customer. + +=cut + +sub country_full { + my $self = shift; + $self->cust_linked + ? FS::cust_main::country_full($self) + : $self->cust_unlinked_msg; +} + +=item invoicing_list_emailonly + +Given an object that contains fields from cust_main (say, from a JOINed +search; see httemplate/search/ for examples), returns the equivalent of the +FS::cust_main I method, or "(unlinked)" if this object is not +linked to a customer. + +=cut + +sub invoicing_list_emailonly { + my $self = shift; + warn "invoicing_list_email only called on $self, ". + "custnum ". $self->custnum. "\n"; + $self->cust_linked + ? FS::cust_main::invoicing_list_emailonly($self) + : $self->cust_unlinked_msg; +} + +#read-only +sub invoicing_list { + my $self = shift; + $self->cust_linked + ? FS::cust_main::invoicing_list($self) + : (); +} + +=cut + =back =head1 BUGS diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 783cc73a3..ed9f2cbc6 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -2,6 +2,7 @@ package FS::cust_pkg; use strict; use vars qw(@ISA $disable_agentcheck @SVCDB_CANCEL_SEQ $DEBUG); +use Tie::IxHash; use FS::UID qw( getotaker dbh ); use FS::Misc qw( send_email ); use FS::Record qw( qsearch qsearchs ); @@ -824,26 +825,45 @@ Returns a short status string for this package, currently: sub status { my $self = shift; + my $freq = length($self->freq) ? $self->freq : $self->part_pkg->freq; + return 'cancelled' if $self->get('cancel'); return 'suspended' if $self->susp; return 'not yet billed' unless $self->setup; - return 'one-time charge' if $self->part_pkg->freq =~ /^(0|$)/; + return 'one-time charge' if $freq =~ /^(0|$)/; return 'active'; } -=item statuscolor +=item statuses -Returns a hex triplet color string for this package's status. +Class method that returns the list of possible status strings for pacakges +(see L). For example: + + @statuses = FS::cust_pkg->statuses(); =cut -my %statuscolor = ( +tie my %statuscolor, 'Tie::IxHash', 'not yet billed' => '000000', 'one-time charge' => '000000', 'active' => '00CC00', 'suspended' => 'FF9900', 'cancelled' => 'FF0000', -); +; + +sub statuses { + my $self = shift; #could be class... + grep { $_ !~ /^(not yet billed)$/ } #this is a dumb status anyway + # mayble split btw one-time vs. recur + keys %statuscolor; +} + +=item statuscolor + +Returns a hex triplet color string for this package's status. + +=cut + sub statuscolor { my $self = shift; $statuscolor{$self->status}; @@ -1163,7 +1183,7 @@ sub reexport { =back -=head1 CLASS METHOD +=head1 CLASS METHODS =over 4 @@ -1178,6 +1198,17 @@ sub recurring_sql { " where cust_pkg.pkgpart = part_pkg.pkgpart ) "; } +=item onetime_sql + +Returns an SQL expression identifying one-time packages. + +=cut + +sub onetime_sql { " + '0' = ( select freq from part_pkg + where cust_pkg.pkgpart = part_pkg.pkgpart ) +"; } + =item active_sql Returns an SQL expression identifying active packages. @@ -1190,6 +1221,19 @@ sub active_sql { " AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 ) "; } +=item inactive_sql + +Returns an SQL expression identifying inactive packages (one-time packages +that are otherwise unsuspended/uncancelled). + +=cut + +sub inactive_sql { " + ". $_[0]->onetime_sql(). " + AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) + AND ( cust_pkg.susp IS NULL OR cust_pkg.susp = 0 ) +"; } + =item susp_sql =item suspended_sql @@ -1198,11 +1242,13 @@ Returns an SQL expression identifying suspended packages. =cut sub suspended_sql { susp_sql(@_); } -sub susp_sql { " - ". $_[0]->recurring_sql(). " - AND ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) - AND cust_pkg.susp IS NOT NULL AND cust_pkg.susp != 0 -"; } +sub susp_sql { + #$_[0]->recurring_sql(). ' AND '. + " + ( cust_pkg.cancel IS NULL OR cust_pkg.cancel = 0 ) + AND cust_pkg.susp IS NOT NULL AND cust_pkg.susp != 0 + "; +} =item cancel_sql =item cancelled_sql @@ -1212,10 +1258,10 @@ Returns an SQL exprression identifying cancelled packages. =cut sub cancelled_sql { cancel_sql(@_); } -sub cancel_sql { " - ". $_[0]->recurring_sql(). " - AND cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0 -"; } +sub cancel_sql { + #$_[0]->recurring_sql(). ' AND '. + "cust_pkg.cancel IS NOT NULL AND cust_pkg.cancel != 0"; +} =head1 SUBROUTINES diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi index 21f79a9dc..6008c0e42 100644 --- a/httemplate/config/config.cgi +++ b/httemplate/config/config.cgi @@ -55,25 +55,65 @@ function SafeOnsubmit() { #warn $i->key unless defined($type); %> <% if ( $type eq '' ) { %> - no type + + no type + <% } elsif ( $type eq 'textarea' ) { %> - + + + <% } elsif ( $type eq 'checkbox' ) { %> - exists($i->key) ? ' CHECKED' : '' %>> + + exists($i->key) ? ' CHECKED' : '' %>> + <% } elsif ( $type eq 'text' ) { %> - + + + <% } elsif ( $type eq 'select' || $type eq 'selectmultiple' ) { %> - > + <% + my %hash = (); + if ( $i->select_enum ) { + tie %hash, 'Tie::IxHash', + '' => '', map { $_ => $_ } @{ $i->select_enum }; + } elsif ( $i->select_hash ) { + if ( ref($i->select_hash) eq 'ARRAY' ) { + tie %hash, 'Tie::IxHash', + '' => '', @{ $i->select_hash }; + } else { + tie %hash, 'Tie::IxHash', + '' => '', %{ $i->select_hash }; + } + } else { + %hash = ( '' => 'WARNING: neither select_enum nor select_hash specified in Conf.pm for configuration option "'. $i->key. '"' ); + } + + my %saw = (); + foreach my $value ( keys %hash ) { + local($^W)=0; next if $saw{$value}++; + my $label = $hash{$value}; + %> + +
    ':'' -+OLlgfUtil(0,'','div',o3_capcolor,o3_captionfont,o3_captionsize))+o3_capicon+title -+OLlgfUtil(1,'','div')+(o3_captionfontclass?'':'')+''+closing+''; ++o3_captionfontclass+'">':OLlgfUtil(0,1,'','div',o3_capcolor,o3_captionfont, +o3_captionsize))+o3_capicon+title+OLlgfUtil(1,1,'','div')+''+closing+''; t=OLbgLGF()+(o3_capbelow?OLfgLGF(txt)+caption:caption+OLfgLGF(txt))+OLbaseLGF(); OLsetBackground('');return t; } @@ -302,8 +290,8 @@ var t;if(hasfullhtml){t=txt;}else{t='' -+OLlgfUtil(0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+txt+ -OLlgfUtil(1,'','div')+'';} OLsetBackground(image);return t; } @@ -317,14 +305,15 @@ function OLfgLGF(t){ return '' -+OLlgfUtil(0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+t -+OLlgfUtil(1,'','div')+''; ++OLlgfUtil(0,0,o3_textfontclass,'div',o3_textcolor,o3_textfont,o3_textsize)+t ++(OLprintPI?OLprintFgLGF():'')+OLlgfUtil(1,0,'','div')+''; } -function OLlgfUtil(end,tfc,ele,col,fac,siz){ -if(end)return ('');else return (tfc?'
    ': -('<'+(OLns4?'font color="'+col+'" face="'+OLquoteMultiNameFonts(fac)+'" size="'+siz:ele -+' style="color:'+col+';font-family:'+OLquoteMultiNameFonts(fac)+';font-size:'+siz+';' -+(ele=='span'?'text-decoration:underline;':''))+'">')); +function OLlgfUtil(end,stg,tfc,ele,col,fac,siz){ +if(end)return ('');else return (tfc?'
    ':('<'+(OLns4?(stg?'strong><':'')+'font color="'+col+'" face="' ++OLquoteMultiNameFonts(fac)+'" size="'+siz:ele+' style="color:'+col ++(stg?';font-weight:bold':'')+';font-family:'+OLquoteMultiNameFonts(fac)+';font-size:' ++siz+';'+(ele=='span'?'text-decoration:underline;':''))+'">')); } function OLquoteMultiNameFonts(f){ var i,v,pM=f.split(','); @@ -354,34 +343,33 @@ else{if(OLns6)over.style.width=o3_width+'px';over.style.backgroundImage='url('+i */ // Displays layer function OLdisp(s){ -if(OLallowmove==0){if(OLshadowPI)OLdispShadow();if(OLiframePI)OLdispIfs();OLplaceLayer(); +if(!OLallowmove){if(OLshadowPI)OLdispShadow();if(OLiframePI)OLdispIfs();OLplaceLayer(); if(OLndt)OLshowObject(over);else OLshowid=setTimeout("OLshowObject(over)",1); OLallowmove=(o3_sticky||o3_nofollow)?0:1;}OLndt=0;if(s!="")self.status=s; } // Decides placement of layer. function OLplaceLayer(){ -var snp,X,Y,pgLeft,pgTop,pWd=o3_width,pHt,iWd=100,iHt=100,SB=0,LM=0,CX=0,TM=0,BM=0,CY=0; -var o=OLfd(),nsb=(OLgek>=20010505&&!o3_frame.scrollbars.visible)?1:0; +var snp,X,Y,pgLeft,pgTop,pWd=o3_width,pHt,iWd=100,iHt=100,SB=0,LM=0,CX=0,TM=0,BM=0,CY=0, +o=OLfd(),nsb=(OLgek>=20010505&&!o3_frame.scrollbars.visible)?1:0; if(!OLkht&&o&&o.clientWidth)iWd=o.clientWidth; else if(o3_frame.innerWidth){SB=Math.ceil(1.4*(o3_frame.outerWidth-o3_frame.innerWidth)); if(SB>20)SB=20;iWd=o3_frame.innerWidth;} pgLeft=(OLie4)?o.scrollLeft:o3_frame.pageXOffset; -if(OLie55&&OLfilterPI&&o3_filtershadow)SB=CX=5;else +if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow)SB=CX=5;else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){SB+=((o3_shadowx>0)?o3_shadowx:0); LM=((o3_shadowx<0)?Math.abs(o3_shadowx):0);CX=Math.abs(o3_shadowx);} if(o3_ref!=""||o3_fixx> -1||o3_relx!=null||o3_midx!=null){ -if(o3_ref!=""){ -X=OLrefXY[0];if(OLie55&&OLfilterPI&&o3_filtershadow){if(o3_refp=='UR'||o3_refp=='LR')X -= 5;} -else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){ -if(o3_shadowx<0&&(o3_refp=='UL'||o3_refp=='LL'))X += o3_shadowx; -else if(o3_shadowx>0&&(o3_refp=='UR'||o3_refp=='LR'))X -= o3_shadowx;} +if(o3_ref!=""){X=OLrefXY[0];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){ +if(o3_refp=='UR'||o3_refp=='LR')X-=5;} +else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowx){ +if(o3_shadowx<0&&(o3_refp=='UL'||o3_refp=='LL'))X-=o3_shadowx;else +if(o3_shadowx>0&&(o3_refp=='UR'||o3_refp=='LR'))X-=o3_shadowx;} }else{if(o3_midx!=null){ X=parseInt(pgLeft+((iWd-pWd-SB-LM)/2)+o3_midx); }else{if(o3_relx!=null){ if(o3_relx>=0)X=pgLeft+o3_relx+LM;else X=pgLeft+o3_relx+iWd-pWd-SB; -}else{ -X=o3_fixx+LM;}}} +}else{X=o3_fixx+LM;}}} }else{ if(o3_hauto){ if(o3_hpos==LEFT&&OLx-pgLeft0)?o3_shadowy:0;CY=Math.abs(o3_shadowy);} if(o3_ref!=""||o3_fixy> -1||o3_rely!=null||o3_midy!=null){ -if(o3_ref!=""){ -Y=OLrefXY[1];if(OLie55&&OLfilterPI&&o3_filtershadow){if(o3_refp=='LL'||o3_refp=='LR')Y -= 5;} -else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){ -if(o3_shadowy<0&&(o3_refp=='UL'||o3_refp=='UR'))Y+=o3_shadowy;else +if(o3_ref!=""){Y=OLrefXY[1];if(OLie55&&OLfilterPI&&o3_filter&&o3_filtershadow){ +if(o3_refp=='LL'||o3_refp=='LR')Y-=5;}else if((OLshadowPI)&&bkdrop&&o3_shadow&&o3_shadowy){ +if(o3_shadowy<0&&(o3_refp=='UL'||o3_refp=='UR'))Y-=o3_shadowy;else if(o3_shadowy>0&&(o3_refp=='LL'||o3_refp=='LR'))Y-=o3_shadowy;} }else{if(o3_midy!=null){ Y=parseInt(pgTop+((iHt-pHt-CY)/2)+o3_midy); @@ -434,54 +421,56 @@ return (!OLop7&&fdc&&fdc!='BackCompat'&&fdd&&fdd.clientWidth)?fd.documentElement } // Gets location of REFerence object -function OLgetRefXY(r){ -var mn=r,mr=OLgetRef(mn),o,of,rXY; -if(!mr)return [null,null]; -o=mr;rXY=[o3_refx,o3_refy]; -if(OLns4){if(typeof mr.length!='undefined'&&mr.length>1){ -o=mr[0];rXY[0]+=mr[0].x+mr[1].pageX;rXY[1]+=mr[0].y+mr[1].pageY; -}else{if((mr.toString().indexOf('Image')!= -1)||(mr.toString().indexOf('Anchor')!= -1)){ -rXY[0]+=mr.x;rXY[1]+=mr.y;}else{rXY[0]+=mr.pageX;rXY[1]+=mr.pageY;}} -}else{rXY[0]+=OLpageLoc(mr,'Left');rXY[1]+=OLpageLoc(mr,'Top');} -of=OLgetRefOffsets(o);rXY[0]+=of[0];rXY[1]+=of[1]; +function OLgetRefXY(r,d){ +var o=OLgetRef(r,d),ob=o,rXY=[o3_refx,o3_refy],of; +if(!o)return [null,null]; +if(OLns4){if(typeof o.length!='undefined'&&o.length>1){ +ob=o[0];rXY[0]+=o[0].x+o[1].pageX;rXY[1]+=o[0].y+o[1].pageY; +}else{if((o.toString().indexOf('Image')!= -1)||(o.toString().indexOf('Anchor')!= -1)){ +rXY[0]+=o.x;rXY[1]+=o.y;}else{rXY[0]+=o.pageX;rXY[1]+=o.pageY;}} +}else{rXY[0]+=OLpageLoc(o,'Left');rXY[1]+=OLpageLoc(o,'Top');} +of=OLgetRefOffsets(ob);rXY[0]+=of[0];rXY[1]+=of[1]; return rXY; } -function OLgetRef(l){var r=OLgetRefById(l);return (r)?r:OLgetRefByName(l);} +function OLgetRef(l,d){var r=OLgetRefById(l,d);return (r)?r:OLgetRefByName(l,d);} // Seeks REFerence by id function OLgetRefById(l,d){ -var r="",j;l=(l||'overDiv');d=(d||o3_frame.document); -if(OLie4&&d.all){return d.all[l];}else if(d.getElementById){return d.getElementById(l); -}else if(d.layers&&d.layers.length>0){if(d.layers[l])return d.layers[l]; +l=(l||'overDiv');d=(d||o3_frame.document);var j,r; +if(OLie4&&d.all)return d.all[l];if(d.getElementById)return d.getElementById(l); +if(d.layers&&d.layers.length>0){if(d.layers[l])return d.layers[l]; for(j=0;j0){ -for(j=0;j0)return r;else if(r)return [r,d.layers[j]];}} +d=(d||o3_frame.document);var j,r,v=OLie4?d.all.tags('iframe'): +OLns6?d.getElementsByTagName('iframe'):null; +if(typeof d.images!='undefined'&&d.images[l])return d.images[l]; +if(typeof d.anchors!='undefined'&&d.anchors[l])return d.anchors[l]; +if(v)for(j=0;j0)for(j=0;j0)return r;else if(r)return [r,d.layers[j]];} return null; } // Gets layer vs REFerence offsets function OLgetRefOffsets(o){ -var mc=o3_refc.toUpperCase(),mp=o3_refp.toUpperCase(),mW=0,mH=0,pW=0,pH=0,off=[0,0]; +var c=o3_refc.toUpperCase(),p=o3_refp.toUpperCase(),W=0,H=0,pW=0,pH=0,of=[0,0]; pW=(OLbubblePI&&o3_bubble)?o3_width:OLns4?over.clip.width:over.offsetWidth; pH=(OLbubblePI&&o3_bubble)?OLbubbleHt:OLns4?over.clip.height:over.offsetHeight; -if((!OLop7)&&o.toString().indexOf('Image')!= -1){mW=o.width;mH=o.height; -}else if((!OLop7)&&o.toString().indexOf('Anchor')!= -1){mc=o3_refc='UL';}else{ -mW=(OLns4)?o.clip.width:o.offsetWidth;mH=(OLns4)?o.clip.height:o.offsetHeight;} -if(mc=='UL'){off=(mp=='UR')?[-pW,0]:(mp=='LL')?[0,-pH]:(mp=='LR')?[-pW,-pH]:[0,0]; -}else if(mc=='UR'){off=(mp=='UR')?[mW-pW,0]:(mp=='LL')?[mW,-pH]:(mp=='LR')?[mW-pW,-pH]:[mW,0]; -}else if(mc=='LL'){off=(mp=='UR')?[-pW,mH]:(mp=='LL')?[0,mH-pH]:(mp=='LR')?[-pW,mH-pH]:[0,mH]; -}else if(mc=='LR'){off=(mp=='UR')?[mW-pW,mH]:(mp=='LL')?[mW,mH-pH]:(mp=='LR')?[mW-pW,mH-pH]: -[mW,mH];} -return off; +if((!OLop7)&&o.toString().indexOf('Image')!= -1){W=o.width;H=o.height; +}else if((!OLop7)&&o.toString().indexOf('Anchor')!= -1){c=o3_refc='UL';}else{ +W=(OLns4)?o.clip.width:o.offsetWidth;H=(OLns4)?o.clip.height:o.offsetHeight;} +if((OLns4||(OLns6&&OLgek))&&o.border){W+=2*parseInt(o.border);H+=2*parseInt(o.border);} +if(c=='UL'){of=(p=='UR')?[-pW,0]:(p=='LL')?[0,-pH]:(p=='LR')?[-pW,-pH]:[0,0]; +}else if(c=='UR'){of=(p=='UR')?[W-pW,0]:(p=='LL')?[W,-pH]:(p=='LR')?[W-pW,-pH]:[W,0]; +}else if(c=='LL'){of=(p=='UR')?[-pW,H]:(p=='LL')?[0,H-pH]:(p=='LR')?[-pW,H-pH]:[0,H]; +}else if(c=='LR'){of=(p=='UR')?[W-pW,H]:(p=='LL')?[W,H-pH]:(p=='LR')?[W-pW,H-pH]: +[W,H];} +return of; } // Gets x or y location of object @@ -494,11 +483,13 @@ return l; // Moves layer function OLmouseMove(e){ var e=(e||event); +OLcC=(OLovertwoPI&&over2&&over==over2?cClick2:cClick); OLx=(e.pageX||e.clientX+OLfd().scrollLeft);OLy=(e.pageY||e.clientY+OLfd().scrollTop); -if((OLallowmove&&over)&&(o3_frame==self||over==OLgetRefById())){ +if((OLallowmove&&over)&&(o3_frame==self||over==OLgetRefById() +||(OLovertwoPI&&over2==over&&over==OLgetRefById('overDiv2')))){ OLplaceLayer();if(OLhidePI)OLhideUtil(0,1,1,0,0,0);} -if(OLhover&&over&&o3_frame==self&&OLcursorOff())if(o3_offdelay<1)cClick();else -{if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=setTimeout("cClick()",o3_offdelay);} +if(OLhover&&over&&o3_frame==self&&OLcursorOff())if(o3_offdelay<1)OLcC();else +{if(OLtimerid>0)clearTimeout(OLtimerid);OLtimerid=setTimeout("OLcC()",o3_offdelay);} } // Capture mouse and chain other scripts. @@ -515,12 +506,12 @@ OLdw.onmousemove=mh;if(OLns4)OLdw.captureEvents(Event.MOUSEMOVE); PARSING */ function OLparseTokens(pf,ar){ -var i,v,md= -1,par=(pf!='ol_'),e=eval,p=OLpar,q=OLparQuo,t=OLtoggle;OLudf=(par&&!ar.length?1:0); +var i,v,md= -1,par=(pf!='ol_'),p=OLpar,q=OLparQuo,t=OLtoggle;OLudf=(par&&!ar.length?1:0); for(i=0;i< ar.length;i++){if(md<0){if(typeof ar[i]=='number'){OLudf=(par?1:0);i--;} else{switch(pf){case 'ol_':ol_text=ar[i];break;default:o3_text=ar[i];}}md=0; }else{ -if(ar[i]==INARRAY){OLudf=0;e(pf+'text=ol_texts['+ar[++i]+']');continue;} -if(ar[i]==CAPARRAY){e(pf+'cap=ol_caps['+ar[++i]+']');continue;} +if(ar[i]==INARRAY){OLudf=0;eval(pf+'text=ol_texts['+ar[++i]+']');continue;} +if(ar[i]==CAPARRAY){eval(pf+'cap=ol_caps['+ar[++i]+']');continue;} if(ar[i]==CAPTION){q(ar[++i],pf+'cap');continue;} if(Math.abs(ar[i])==STICKY){t(ar[i],pf+'sticky');continue;} if(Math.abs(ar[i])==NOFOLLOW){t(ar[i],pf+'nofollow');continue;} @@ -545,9 +536,9 @@ if(ar[i]==BORDER){p(ar[++i],pf+'border');continue;} if(ar[i]==BASE){p(ar[++i],pf+'base');continue;} if(ar[i]==STATUS){q(ar[++i],pf+'status');continue;} if(Math.abs(ar[i])==AUTOSTATUS){v=pf+'autostatus'; -e(v+'=('+ar[i]+'<0)?('+v+'==2?2:0):('+v+'==1?0:1)');continue;} +eval(v+'=('+ar[i]+'<0)?('+v+'==2?2:0):('+v+'==1?0:1)');continue;} if(Math.abs(ar[i])==AUTOSTATUSCAP){v=pf+'autostatus'; -e(v+'=('+ar[i]+'<0)?('+v+'==1?1:0):('+v+'==2?0:2)');continue;} +eval(v+'=('+ar[i]+'<0)?('+v+'==1?1:0):('+v+'==2?0:2)');continue;} if(ar[i]==CLOSETEXT){q(ar[++i],pf+'close');continue;} if(ar[i]==SNAPX){p(ar[++i],pf+'snapx');continue;} if(ar[i]==SNAPY){p(ar[++i],pf+'snapy');continue;} @@ -594,6 +585,7 @@ if(ar[i]==CAPTIONFONTCLASS){q(ar[++i],pf+'captionfontclass');continue;} if(ar[i]==CLOSEFONTCLASS){q(ar[++i],pf+'closefontclass');continue;} if(Math.abs(ar[i])==CAPBELOW){t(ar[i],pf+'capbelow');continue;} if(ar[i]==LABEL){q(ar[++i],pf+'label');continue;} +if(Math.abs(ar[i])==DECODE){t(ar[i],pf+'decode');continue;} if(ar[i]==DONOTHING){continue;} i=OLparseCmdLine(pf,i,ar);}} if((OLfunctionPI)&&OLudf&&o3_function)o3_text=o3_function(); @@ -611,6 +603,14 @@ if(OLhasDims(o3_captionsize)){if(OLns4)o3_captionsize="2";}else if(!OLns4){i=parseInt(o3_captionsize);o3_captionsize=(i>0&&i<8)?OLpct[i]:OLpct[0];} if(OLhasDims(o3_closesize)){if(OLns4)o3_closesize="2";}else if(!OLns4){i=parseInt(o3_closesize);o3_closesize=(i>0&&i<8)?OLpct[i]:OLpct[0];} +if(OLprintPI)OLprintDims(); +} +function OLdecode(){ +var re=/%[0-9A-Fa-f]{2,}/,t=o3_text,c=o3_cap,u=unescape,d=!OLns4&&(!OLgek||OLgek>=20020826) +&&typeof decodeURIComponent?decodeURIComponent:u;if(typeof(window.TypeError)=='function'){ +if(re.test(t)){eval(new Array('try{','o3_text=d(t);','}catch(e){','o3_text=u(t);', +'}').join('\n'))};if(c&&re.test(c)){eval(new Array('try{','o3_cap=d(c);','}catch(e){', +'o3_cap=u(c);','}').join('\n'))}}else{if(re.test(t))o3_text=u(t);if(c&&re.test(c))o3_cap=u(c);} } /* @@ -625,6 +625,7 @@ if(OLns4){over.document.write(t);over.document.close(); domfrag=range.createContextualFragment(t); while(over.hasChildNodes()){over.removeChild(over.lastChild);} over.appendChild(domfrag);} +if(OLprintPI)over.print=o3_print?t:null; } // Makes object visible @@ -661,10 +662,9 @@ if(!c)o3_close=""; over.onmouseover=function(){OLhover=1;if(OLtimerid>0){clearTimeout(OLtimerid);OLtimerid=0;}} } function OLcursorOff(){ -if(OLovertwoPI&&over==over2)return false; -var o=(OLns4?over:over.style),pHt=OLns4?over.clip.height:over.offsetHeight; -var left=parseInt(o.left),top=parseInt(o.top); -var right=left+o3_width,bottom=top+((OLbubblePI&&o3_bubble)?OLbubbleHt:pHt); +var o=(OLns4?over:over.style),pHt=OLns4?over.clip.height:over.offsetHeight, +left=parseInt(o.left),top=parseInt(o.top), +right=left+o3_width,bottom=top+((OLbubblePI&&o3_bubble)?OLbubbleHt:pHt); if(OLxright||OLybottom)return true; return false; } @@ -672,38 +672,26 @@ return false; /* REGISTRATION */ -var OLcmdLine=null,OLrunTime=null; function OLsetRunTimeVar(){ -if(OLrunTime&&OLrunTime.length)for(var k=0;k-1){i=j;break;}}} return i; } -function OLisFunc(f){ -var r=1; -if(typeof f=='object'){for(var i=0;i - + <% if ( length($number) ) { %> <%= $number %> <% if ( $opt{'callable'} && $conf->config('vonage-username') ) { %> diff --git a/httemplate/search/cust_bill.html b/httemplate/search/cust_bill.html index 16128d5c8..79c05dc42 100755 --- a/httemplate/search/cust_bill.html +++ b/httemplate/search/cust_bill.html @@ -37,7 +37,7 @@ $begin = str2time($1); push @where, "cust_bill._date >= $begin"; } - if ( $cgi->param('ending') + if ( $cgi->param('ending') && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) { $end = str2time($1) + 86399; push @where, "cust_bill._date < $end"; @@ -52,6 +52,13 @@ push @where, "cust_bill._date < $end"; } + if ( $cgi->param('invnum_min') =~ /^\s*(\d+)\s*$/ ) { + push @where, "cust_bill.invnum >= $1"; + } + if ( $cgi->param('invnum_max') =~ /^\s*(\d+)\s*$/ ) { + push @where, "cust_bill.invnum <= $1"; + } + if ( $cgi->param('agentnum') =~ /^(\d+)$/ ) { $agentnum = $1; push @where, "cust_main.agentnum = $agentnum"; diff --git a/httemplate/search/svc_acct.cgi b/httemplate/search/svc_acct.cgi index ef68ba05e..0f2f3ef45 100755 --- a/httemplate/search/svc_acct.cgi +++ b/httemplate/search/svc_acct.cgi @@ -95,6 +95,7 @@ my $sql_query = { 'hashref' => {}, # \%svc_acct, 'select' => join(', ', 'svc_acct.*', + 'part_svc.svc', 'cust_main.custnum', FS::UI::Web::cust_sql_fields(), ), diff --git a/httemplate/search/svc_domain.cgi b/httemplate/search/svc_domain.cgi index 1eda3733c..ecb77792f 100755 --- a/httemplate/search/svc_domain.cgi +++ b/httemplate/search/svc_domain.cgi @@ -56,8 +56,9 @@ my $sql_query = { 'hashref' => \%svc_domain, 'select' => join(', ', 'svc_domain.*', - 'cust_main.custnum', - FS::UI::Web::cust_sql_fields(), + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), ), 'extra_sql' => "$extra_sql $orderby", 'addl_from' => $addl_from, diff --git a/httemplate/search/svc_forward.cgi b/httemplate/search/svc_forward.cgi index 4961967d7..d391a1834 100755 --- a/httemplate/search/svc_forward.cgi +++ b/httemplate/search/svc_forward.cgi @@ -39,8 +39,9 @@ my $sql_query = { 'hashref' => {}, 'select' => join(', ', 'svc_forward.*', - 'cust_main.custnum', - FS::UI::Web::cust_sql_fields(), + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields(), ), 'extra_sql' => "$extra_sql $orderby", 'addl_from' => $addl_from, diff --git a/httemplate/search/svc_www.cgi b/httemplate/search/svc_www.cgi index ae51c61fc..ef4045cf9 100755 --- a/httemplate/search/svc_www.cgi +++ b/httemplate/search/svc_www.cgi @@ -17,6 +17,7 @@ my $sql_query = { 'hashref' => {}, 'select' => join(', ', 'svc_www.*', + 'part_svc.svc', 'cust_main.custnum', FS::UI::Web::cust_sql_fields(), ), @@ -43,11 +44,13 @@ my $link_cust = sub { 'count_query' => $count_query, 'redirect' => $link, 'header' => [ '#', + 'Service', 'Zone', 'User', FS::UI::Web::cust_header(), ], 'fields' => [ 'svcnum', + 'svc', sub { $_[0]->domain_record->zone }, sub { my $svc_www = shift; @@ -59,6 +62,7 @@ my $link_cust = sub { \&FS::UI::Web::cust_fields, ], 'links' => [ $link, + $link, '', $ulink, ( map { $link_cust } diff --git a/httemplate/view/svc_external.cgi b/httemplate/view/svc_external.cgi index 2acc3f9cf..7816d88dc 100644 --- a/httemplate/view/svc_external.cgi +++ b/httemplate/view/svc_external.cgi @@ -1,4 +1,3 @@ - <% my($query) = $cgi->keywords; -- cgit v1.2.1 From e75070ffcf15e537036e4685d2aabb0415eec6d3 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 23 Jul 2006 14:07:33 +0000 Subject: more work towards adding an "inactive" status - add it to the A/R report --- FS/FS/cust_main.pm | 21 +++++---- httemplate/search/report_receivables.cgi | 80 ++++++++++++++------------------ 2 files changed, 46 insertions(+), 55 deletions(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index cbcf6cc51..2b9564442 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -3670,18 +3670,17 @@ Returns a hex triplet color string for this customer's status. =cut +use vars qw(%statuscolor); +%statuscolor = ( + 'prospect' => '7e0079', #'000000', #black? naw, purple + 'active' => '00CC00', #green + 'inactive' => '0000CC', #blue + 'suspended' => 'FF9900', #yellow + 'cancelled' => 'FF0000', #red +); sub statuscolor { my $self = shift; - - my %statuscolor = ( - 'prospect' => '7e0079', #'000000', #black? naw, purple - 'active' => '00CC00', #green - 'inactive' => '0000CC', #blue - 'suspended' => 'FF9900', #yellow - 'cancelled' => 'FF0000', #red - ); - $statuscolor{$self->status}; } @@ -3703,6 +3702,10 @@ $select_count_pkgs = "SELECT COUNT(*) FROM cust_pkg WHERE cust_pkg.custnum = cust_main.custnum"; +sub select_count_pkgs_sql { + $select_count_pkgs; +} + sub prospect_sql { " 0 = ( $select_count_pkgs ) "; } diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index c1a239fd2..f42d8af7f 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -62,26 +62,19 @@ END my $owed_cols = join(',', map owed( @$_, 'cust'=>1 ), @ranges ); - my $recurring = <select_count_pkgs_sql; - ( select count(*) from cust_pkg - where cust_main.custnum = cust_pkg.custnum - and $recurring - and ( cancel = 0 or cancel is null ) - ) as uncancelled_pkgs, - - ( select count(*) from cust_pkg - where cust_main.custnum = cust_pkg.custnum - and $recurring - and ( cancel = 0 or cancel is null ) - and ( susp = 0 or susp is null ) - ) as active_pkgs + my $active_sql = FS::cust_pkg->active_sql; + my $inactive_sql = FS::cust_pkg->inactive_sql; + my $suspended_sql = FS::cust_pkg->inactive_sql; + my $cancelled_sql = FS::cust_pkg->inactive_sql; + my $packages_cols = <1, 'noas'=>1). " > 0"; @@ -119,6 +112,27 @@ END my $clink = [ "${p}view/cust_main.cgi?", 'custnum' ]; + my $status_statuscol = sub { + #conceptual false laziness with cust_main::status... + my $row = shift; + + my $status = 'unknown'; + if ( $self->num_pkgs == 0 ) { + $status = 'prospect'; + } elsif ( $self->active_pkgs) > 0 ) { + $status = 'active'; + } elsif ( $self->inactive_pkgs > 0 ) { + $status = 'inactive'; + } elsif ( $self->suspended_pkgs > 0 ) { + $status = 'suspended'; + } elsif ( $self->cancelled_pkgs > 0 ) { + $status = 'cancelled' + } + + ( ucfirst($status), $FS::cust_main::statuscolor{$status} ); + }; + + %><%= include( 'elements/search.html', 'title' => 'Accounts Receivable Aging Summary', 'name' => 'customers', @@ -156,20 +170,7 @@ END ], 'fields' => [ \&FS::UI::Web::cust_fields, - sub { - my $row = shift; - my $status = 'Cancelled'; - my $statuscol = 'FF0000'; - if ( $row->uncancelled_pkgs ) { - $status = 'Suspended'; - $statuscol = 'FF9900'; - if ( $row->active_pkgs ) { - $status = 'Active'; - $statuscol = '00CC00'; - } - } - $status; - }, + sub { (shift->status_statuscol)[0] }, #sub { ucfirst(shift->status) }, sub { sprintf( $money_char.'%.2f', shift->get('owed_0_30') ) }, @@ -202,20 +203,7 @@ END 'b', '', '', '', '', 'b', ], 'color' => [ ( map '', FS::UI::Web::cust_header() ), - sub { - my $row = shift; - my $status = 'Cancelled'; - my $statuscol = 'FF0000'; - if ( $row->uncancelled_pkgs ) { - $status = 'Suspended'; - $statuscol = 'FF9900'; - if ( $row->active_pkgs ) { - $status = 'Active'; - $statuscol = '00CC00'; - } - } - $statuscol; - }, + sub { (shift->status_statuscol)[1] }, #sub { shift->statuscolor; }, '', '', -- cgit v1.2.1 From 8e498d1159f4e6756239839def94200c3154a494 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 23 Jul 2006 14:16:48 +0000 Subject: i should go to sleep --- httemplate/search/report_receivables.cgi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/httemplate/search/report_receivables.cgi b/httemplate/search/report_receivables.cgi index f42d8af7f..c3dc9902c 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -119,13 +119,13 @@ END my $status = 'unknown'; if ( $self->num_pkgs == 0 ) { $status = 'prospect'; - } elsif ( $self->active_pkgs) > 0 ) { + } elsif ( $row->active_pkgs > 0 ) { $status = 'active'; - } elsif ( $self->inactive_pkgs > 0 ) { + } elsif ( $row->inactive_pkgs > 0 ) { $status = 'inactive'; - } elsif ( $self->suspended_pkgs > 0 ) { + } elsif ( $row->suspended_pkgs > 0 ) { $status = 'suspended'; - } elsif ( $self->cancelled_pkgs > 0 ) { + } elsif ( $row->cancelled_pkgs > 0 ) { $status = 'cancelled' } -- cgit v1.2.1 From c1a7b9da2c7817a09e683b79b3cd49787f5e38db Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 23 Jul 2006 14:17:56 +0000 Subject: i should REALLY go to sleep --- 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 c3dc9902c..061febc10 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -117,7 +117,7 @@ END my $row = shift; my $status = 'unknown'; - if ( $self->num_pkgs == 0 ) { + if ( $row->num_pkgs == 0 ) { $status = 'prospect'; } elsif ( $row->active_pkgs > 0 ) { $status = 'active'; -- cgit v1.2.1 From 9397849cb897ebc82a8c30846ce5556382b06920 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 23 Jul 2006 14:20:10 +0000 Subject: hopefully fix the statuses here --- 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 061febc10..4c31f2afa 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -170,7 +170,7 @@ END ], 'fields' => [ \&FS::UI::Web::cust_fields, - sub { (shift->status_statuscol)[0] }, + sub { ( &{$status_statuscol}($row) )[0] }, #sub { ucfirst(shift->status) }, sub { sprintf( $money_char.'%.2f', shift->get('owed_0_30') ) }, @@ -203,7 +203,7 @@ END 'b', '', '', '', '', 'b', ], 'color' => [ ( map '', FS::UI::Web::cust_header() ), - sub { (shift->status_statuscol)[1] }, + sub { ( &{$status_statuscol}($row) )[1] }, #sub { shift->statuscolor; }, '', '', -- cgit v1.2.1 From 29e7e361910ee76e9d487b2df88710af78997c8f Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 23 Jul 2006 14:21:37 +0000 Subject: ugh, really fix the statuses here --- 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 4c31f2afa..b08fc8143 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -170,7 +170,7 @@ END ], 'fields' => [ \&FS::UI::Web::cust_fields, - sub { ( &{$status_statuscol}($row) )[0] }, + sub { ( &{$status_statuscol}(shift) )[0] }, #sub { ucfirst(shift->status) }, sub { sprintf( $money_char.'%.2f', shift->get('owed_0_30') ) }, @@ -203,7 +203,7 @@ END 'b', '', '', '', '', 'b', ], 'color' => [ ( map '', FS::UI::Web::cust_header() ), - sub { ( &{$status_statuscol}($row) )[1] }, + sub { ( &{$status_statuscol}(shift) )[1] }, #sub { shift->statuscolor; }, '', '', -- cgit v1.2.1 From 79f6314575d140a09b362129e5ee580468059e99 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 23 Jul 2006 14:23:39 +0000 Subject: sql num_pkgs conflicting with method... --- 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 b08fc8143..3052ea93c 100755 --- a/httemplate/search/report_receivables.cgi +++ b/httemplate/search/report_receivables.cgi @@ -70,7 +70,7 @@ END my $cancelled_sql = FS::cust_pkg->inactive_sql; my $packages_cols = <num_pkgs == 0 ) { + if ( $row->num_pkgs_sql == 0 ) { $status = 'prospect'; } elsif ( $row->active_pkgs > 0 ) { $status = 'active'; -- cgit v1.2.1 From c4dd2593a5cf5c4463a9180fbc5aef1f2c76f6e2 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 24 Jul 2006 22:40:37 +0000 Subject: fix up smart searching to make the quick payment entry behave better --- FS/FS/cust_main.pm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 2b9564442..f4568a8a0 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -3866,6 +3866,11 @@ sub smart_search { } elsif ( $search =~ /^\s*(\S.*\S)\s*$/ ) { #value search my $value = lc($1); + + # remove "(Last, First)" in "Company (Last, First"), otherwise the + # full strings the browser remembers won't work + $value =~ s/\([\w \,\.\-\']*\)$//; #false laziness w/Record::ut_name + my $q_value = dbh->quote($value); #exact @@ -3938,6 +3943,10 @@ sub smart_search { } + #eliminate duplicates + my %saw = (); + @cust_main = grep { !$saw{$_->custnum}++ } @cust_main; + } @cust_main; -- cgit v1.2.1 From 4f056133e2a2bd84d8d789acf24aba695a6e3688 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 25 Jul 2006 07:27:32 +0000 Subject: quick script to convert rt links from one database name to another --- bin/rt-update-links | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 bin/rt-update-links diff --git a/bin/rt-update-links b/bin/rt-update-links new file mode 100644 index 000000000..75d554f48 --- /dev/null +++ b/bin/rt-update-links @@ -0,0 +1,36 @@ +#!/usr/bin/perl + +use FS::UID qw(adminsuidsetup); + +my( $olddb, $newdb ) = ( shift, shift ); + +$FS::CurrentUser::upgrade_hack = 1; +my $dbh = adminsuidsetup; + +my $statement = "select * from links where base like 'fsck.com-rt://$olddb/%' OR target like 'fsck.com-rt://$olddb/%'"; + +my $sth = $dbh->prepare($statement) or die $dbh->errstr; +$sth->execute or die $sth->errstr; + +while ( my $row = $sth->fetchrow_hashref ) { + + ( my $base = $row->{'base'} ) + =~ s(^fsck\.com-rt://$olddb/)(fsck.com-rt://$newdb/); + + ( my $target = $row->{'target'} ) + =~ s(^fsck\.com-rt://$olddb/)(fsck.com-rt://$newdb/); + + if ( $row->{'base'} ne $base || $row->{'target'} ne $target ) { + + my $update = 'UPDATE links SET base = ?, target = ? where id = ?'; + my @param = ( $base, $target, $row->{'id'} ); + + warn "$update : ". join(', ', @param). "\n"; + $dbh->do($update, {}, @param ); + + } + +} + +$dbh->commit; + -- cgit v1.2.1 From a0732f52fdcc2bca7c399d1249ccceb191de51cd Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 25 Jul 2006 08:33:46 +0000 Subject: this should finish adding the "inactive" status, i think? --- FS/FS/agent.pm | 11 ++++ httemplate/browse/agent.cgi | 114 +++++++++++++++++++++++++++--------- httemplate/elements/table-grid.html | 6 ++ httemplate/search/cust_main.cgi | 111 ++++++++++++++++++++++------------- httemplate/search/cust_pkg.cgi | 13 +++- 5 files changed, 185 insertions(+), 70 deletions(-) diff --git a/FS/FS/agent.pm b/FS/FS/agent.pm index 4158341e4..e40ef09db 100644 --- a/FS/FS/agent.pm +++ b/FS/FS/agent.pm @@ -322,6 +322,17 @@ sub num_pkg_sql { $sth->fetchrow_arrayref->[0]; } +=item num_inactive_cust_pkg + +Returns the number of inactive customer packages (one-time packages otherwise +unsuspended/uncancelled) for this agent. + +=cut + +sub num_inactive_cust_pkg { + shift->num_pkg_sql(FS::cust_pkg->inactive_sql); +} + =item num_susp_cust_pkg Returns the number of suspended customer packages for this agent. diff --git a/httemplate/browse/agent.cgi b/httemplate/browse/agent.cgi index 17cc8bd40..f5157d9b7 100755 --- a/httemplate/browse/agent.cgi +++ b/httemplate/browse/agent.cgi @@ -29,21 +29,27 @@ full offerings (via their type).

    %> <% } %> -<%= table() %> +<%= include('/elements/table-grid.html') %> + +<% my $bgcolor1 = '#eeeeee'; + my $bgcolor2 = '#ffffff'; + my $bgcolor = ''; +%> + - param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent - Type - Customers - Customer
    packages
    - Reports - Registration codes - Prepaid cards + param('showdisabled') || !dbdef->table('agent')->column('disabled') ) ? 2 : 3 %>>Agent + Type + Customers + Customer
    packages
    + Reports + Registration codes + Prepaid cards <% if ( $conf->config('ticket_system') ) { %> - Ticketing + Ticketing <% } %> - Payment Gateway Overrides - Freq. - Prog. + Payment Gateway Overrides + Freq. + Prog. <% # Agent # @@ -58,107 +64,157 @@ foreach my $agent ( sort { 'agentnum='. $agent->agentnum; my $cust_pkg_link = $p. 'search/cust_pkg.cgi?agentnum='. $agent->agentnum; + + if ( $bgcolor eq $bgcolor1 ) { + $bgcolor = $bgcolor2; + } else { + $bgcolor = $bgcolor1; + } %> - + <%= $agent->agentnum %> <% if ( dbdef->table('agent')->column('disabled') && !$cgi->param('showdisabled') ) { %> - <%= $agent->disabled ? 'DISABLED' : '' %> + <%= $agent->disabled ? 'DISABLED' : '' %> <% } %> - + <%= $agent->agent %> - <%= $agent->agent_type->atype %> + <%= $agent->agent_type->atype %> + + + - - + + } else { %> + + + +<% } - print ""; } my($n1)=''; diff --git a/httemplate/search/cust_pkg.cgi b/httemplate/search/cust_pkg.cgi index e8b3f490d..614e9b509 100755 --- a/httemplate/search/cust_pkg.cgi +++ b/httemplate/search/cust_pkg.cgi @@ -24,6 +24,12 @@ if ( $cgi->param('magic') eq 'active' push @where, FS::cust_pkg->active_sql(); +} elsif ( $cgi->param('magic') eq 'inactive' + || $cgi->param('status') eq 'inactive' ) { + + push @where, FS::cust_pkg->inactive_sql(); + + } elsif ( $cgi->param('magic') eq 'suspended' || $cgi->param('status') eq 'suspended' ) { @@ -47,7 +53,10 @@ if ( $cgi->param('magic') eq 'active' #false lazinessish w/graph/cust_bill_pkg.cgi my $classnum = 0; my @pkg_class = (); -if ( $cgi->param('classnum') =~ /^(\d*)$/ ) { +if ( exists($cgi->Vars->{'classnum'}) + && $cgi->param('classnum') =~ /^(\d*)$/ + ) +{ $classnum = $1; if ( $classnum ) { #a specific class push @where, "classnum = $classnum"; @@ -90,7 +99,7 @@ if ( $cgi->param('magic') && $cgi->param('magic') eq 'bill' ) { } else { if ( $cgi->param('magic') && - $cgi->param('magic') =~ /^(active|suspended|cancell?ed)$/ + $cgi->param('magic') =~ /^(active|inactive|suspended|cancell?ed)$/ ) { $orderby = 'ORDER BY pkgnum'; -- cgit v1.2.1 From 2a866b4696e28f963841e493368320fcf8048953 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 25 Jul 2006 08:39:28 +0000 Subject: oops, extra else --- httemplate/search/cust_main.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index c72ab44df..f6841a099 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -480,7 +480,7 @@ END - } else { %> + <% } else { %>
    - + + + + + + + + + + + + + +
    - <%= my $num_prospect = $agent->num_prospect_cust_main %>  + + <%= my $num_prospect = $agent->num_prospect_cust_main %>  + <% if ( $num_prospect ) { %> <% } %>prospects<% if ($num_prospect ) { %><% } %>
    + + <%= my $num_inactive = $agent->num_inactive_cust_main %>  + + + <% if ( $num_inactive ) { %> + <% } %>inactive<% if ( $num_inactive ) { %><% } %> +
    <%= my $num_active = $agent->num_active_cust_main %>  <% if ( $num_active ) { %> <% } %>active<% if ( $num_active ) { %><% } %>
    <%= my $num_susp = $agent->num_susp_cust_main %>  <% if ( $num_susp ) { %> <% } %>suspended<% if ( $num_susp ) { %><% } %>
    <%= my $num_cancel = $agent->num_cancel_cust_main %>  <% if ( $num_cancel ) { %> <% } %>cancelled<% if ( $num_cancel ) { %><% } %>
    - + - - - - - + diff --git a/httemplate/elements/table-grid.html b/httemplate/elements/table-grid.html index 17eafdf1a..fd1cb9113 100644 --- a/httemplate/elements/table-grid.html +++ b/httemplate/elements/table-grid.html @@ -5,9 +5,15 @@ %>
    + + + + + + + + + + + + +
    + + <%= my $num_inactive_pkg = $agent->num_inactive_cust_pkg %>  + + + <% if ( $num_inactive_pkg ) { %> + <% } %>inactive<% if ( $num_inactive_pkg ) { %><% } %> +
    <%= my $num_active_pkg = $agent->num_active_cust_pkg %>  <% if ( $num_active_pkg ) { %> <% } %>active<% if ( $num_active_pkg ) { %><% } %>
    <%= my $num_susp_pkg = $agent->num_susp_cust_pkg %>  + <% if ( $num_susp_pkg ) { %> <% } %>suspended<% if ( $num_susp_pkg ) { %><% } %>
    <%= my $num_cancel_pkg = $agent->num_cancel_cust_pkg %>  <% if ( $num_cancel_pkg ) { %> <% } %>cancelled<% if ( $num_cancel_pkg ) { %><% } %>
    + Payments
    Credits
    A/R Aging @@ -166,14 +222,14 @@ foreach my $agent ( sort {
    + <%= my $num_reg_code = $agent->num_reg_code %> <% if ( $num_reg_code ) { %> <% } %>Unused<% if ( $num_reg_code ) { %><% } %>
    Generate codes
    + <%= my $num_prepay_credit = $agent->num_prepay_credit %> <% if ( $num_prepay_credit ) { %> <% } %>Unused<% if ( $num_prepay_credit ) { %><% } %> @@ -182,7 +238,7 @@ foreach my $agent ( sort { <% if ( $conf->config('ticket_system') ) { %> - + <% if ( $agent->ticketing_queueid ) { %> Queue: <%= $agent->ticketing_queueid %>: <%= $agent->ticketing_queue %>
    <% } %> @@ -190,7 +246,7 @@ foreach my $agent ( sort { <% } %> -
    + <% foreach my $override ( # sort { } want taxclass-full stuff first? and default cards (empty cardtype) @@ -214,8 +270,10 @@ foreach my $agent ( sort {
    <%= $agent->freq %><%= $agent->prog %>
    CELLPADDING=<%= $opt{cellpadding} %> BORDER=1 BORDERCOLOR="#000000" STYLE="border: solid 1px black; empty-cells: show"> diff --git a/httemplate/search/cust_main.cgi b/httemplate/search/cust_main.cgi index 7d5941a16..c72ab44df 100755 --- a/httemplate/search/cust_main.cgi +++ b/httemplate/search/cust_main.cgi @@ -109,6 +109,7 @@ if ( $cgi->param('browse') push @qual, FS::cust_main->cancel_sql if $cgi->param('cancelled'); push @qual, FS::cust_main->prospect_sql if $cgi->param('prospect'); push @qual, FS::cust_main->active_sql if $cgi->param('active'); + push @qual, FS::cust_main->inactive_sql if $cgi->param('inactive'); push @qual, FS::cust_main->susp_sql if $cgi->param('suspended'); #EWWWWWW @@ -415,48 +416,78 @@ END foreach my $addl_col ( @addl_cols ) { %> - '. - " ". - ""; - - } - print ''. - '
    ALIGN=right> - - <% if ( $addl_col eq 'tickets' ) { - if ( @custom_priorities ) { - print &itable('', 0); - foreach my $priority ( @custom_priorities, '' ) { - - my $num = - FS::TicketSystem->num_customer_tickets($custnum,$priority); - my $ahref = ''; - $ahref= '' - if $num; - - print '
    $ahref$num$ahref". - ( $priority || '(none)' ). - "
    '; - } - - my $ahref = ''; - $ahref = '' - if $cust_main->get($addl_col); - - print $ahref. $cust_main->get($addl_col). ''; - print "". - "${ahref}Total". - "
    " - if @custom_priorities; + <% if ( $addl_col eq 'tickets' ) { %> - } else { - print $cust_main->get($addl_col); + <% if ( @custom_priorities ) { %> + +
    ALIGN=right> + + + + <% foreach my $priority ( @custom_priorities, '' ) { %> + + <% + my $num = + FS::TicketSystem->num_customer_tickets($custnum,$priority); + my $ahref = ''; + $ahref= '' + if $num; + %> + + + + + + + <% } %> + + + + +
    + <%= $ahref.$num %> + + <%= $ahref %><%= $priority || '(none)' %> +
    + + + <% } else { %> + + ALIGN=right> + + <% } %> + + <% + my $ahref = ''; + $ahref = '' + if $cust_main->get($addl_col); + %> + + <%= $ahref %><%= $cust_main->get($addl_col) %> + + <% if ( @custom_priorities ) { %> + + + + <%= ${ahref} %>Total +
    + + <% } %> + +
    ALIGN=right> + <%= $cust_main->get($addl_col) %> + ALIGN=right> <%= $cust_main->get($addl_col) %> -- cgit v1.2.1 From d201bed3c90f0a8b80541c3216c265405117694d Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 26 Jul 2006 04:18:01 +0000 Subject: ugh, fixup bootstrapping --- FS/FS/UID.pm | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/FS/FS/UID.pm b/FS/FS/UID.pm index 0d32002b2..eb703d352 100644 --- a/FS/FS/UID.pm +++ b/FS/FS/UID.pm @@ -10,7 +10,7 @@ use subs qw( getsecrets cgisetotaker ); use Exporter; -use Carp qw(carp croak cluck); +use Carp qw(carp croak cluck confess); use DBI; use FS::Conf; use FS::CurrentUser; @@ -72,6 +72,7 @@ sub adminsuidsetup { sub forksuidsetup { $user = shift; + my $olduser = $user; if ( $FS::CurrentUser::upgrade_hack ) { $user = 'fs_bootstrap'; @@ -91,7 +92,11 @@ sub forksuidsetup { croak "Not running uid freeside!" unless checkeuid(); - $dbh = &myconnect; + if ( $FS::CurrentUser::upgrade_hack && $olduser ) { + $dbh = &myconnect($olduser); + } else { + $dbh = &myconnect(); + } use FS::Schema qw(reload_dbdef); reload_dbdef("/usr/local/etc/freeside/dbdef.$datasrc") @@ -110,10 +115,10 @@ sub forksuidsetup { } sub myconnect { - DBI->connect( getsecrets, { 'AutoCommit' => 0, - 'ChopBlanks' => 1, - 'ShowErrorStatement' => 1, - } + DBI->connect( getsecrets(@_), { 'AutoCommit' => 0, + 'ChopBlanks' => 1, + 'ShowErrorStatement' => 1, + } ) or die "DBI->connect error: $DBI::errstr\n"; } @@ -275,7 +280,7 @@ sub getsecrets { if ( $conf->exists('mapsecrets') ) { die "No user!" unless $user; my($line) = grep /^\s*($user|\*)\s/, $conf->config('mapsecrets'); - die "User $user not found in mapsecrets!" unless $line; + confess "User $user not found in mapsecrets!" unless $line; $line =~ /^\s*($user|\*)\s+(.*)$/; $secrets = $2; die "Illegal mapsecrets line for user?!" unless $secrets; -- cgit v1.2.1 From f06035669a617339ae5e424db5b8f8dda92a425c Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 27 Jul 2006 08:08:46 +0000 Subject: svc_phone.t --- FS/t/svc_phone.t | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 FS/t/svc_phone.t diff --git a/FS/t/svc_phone.t b/FS/t/svc_phone.t new file mode 100644 index 000000000..15b9ca275 --- /dev/null +++ b/FS/t/svc_phone.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::svc_phone; +$loaded=1; +print "ok 1\n"; -- cgit v1.2.1 From e39b961f3da26df319c9f116094d2386b2fa2050 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 28 Jul 2006 00:33:33 +0000 Subject: htpasswd workaround no longer necessary - closes: #1351 --- FS/FS/access_user.pm | 109 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm index e3bf2cb9f..d325dd6cc 100644 --- a/FS/FS/access_user.pm +++ b/FS/FS/access_user.pm @@ -1,13 +1,16 @@ package FS::access_user; use strict; -use vars qw( @ISA ); +use vars qw( @ISA $htpasswd_file ); use FS::Record qw( qsearch qsearchs dbh ); use FS::m2m_Common; use FS::access_usergroup; @ISA = qw( FS::m2m_Common FS::Record ); +#kludge htpasswd for now +$htpasswd_file = '/usr/local/etc/freeside/htpasswd'; + =head1 NAME FS::access_user - Object methods for access_user records @@ -70,7 +73,51 @@ otherwise returns false. =cut -# the insert method can be inherited from FS::Record +sub insert { + my $self = shift; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = + $self->SUPER::insert(@_) + || $self->htpasswd_kludge() + ; + + if ( $error ) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + return $error; + } else { + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + } + +} + +sub htpasswd_kludge { + my $self = shift; + if ( + system('htpasswd', '-b', @_, + $htpasswd_file, + $self->username, + $self->_password, + ) == 0 + ) + { + return ''; + } else { + return 'htpasswd exited unsucessfully'; + } +} + =item delete @@ -78,7 +125,34 @@ Delete this record from the database. =cut -# the delete method can be inherited from FS::Record +sub delete { + my $self = shift; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = + $self->SUPER::delete(@_) + || $self->htpasswd_kludge('-D') + ; + + if ( $error ) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + return $error; + } else { + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + } + +} =item replace OLD_RECORD @@ -87,7 +161,34 @@ returns the error, otherwise returns false. =cut -# the replace method can be inherited from FS::Record +sub replace { + my($new, $old) = ( shift, shift ); + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $error = + $new->SUPER::replace($old, @_) + || $new->htpasswd_kludge() + ; + + if ( $error ) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + return $error; + } else { + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + ''; + } + +} =item check -- cgit v1.2.1 From e55f806e5ba30a82c0c03dc1fc53f6e13fffb1bb Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 6 Aug 2006 08:10:36 +0000 Subject: add confession here to diagnose etxrn's problem better --- FS/FS/svc_acct.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index aa3e592a3..572a00853 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -1416,6 +1416,8 @@ Returns all RADIUS groups for this account (see L). sub radius_groups { my $self = shift; if ( $self->usergroup ) { + confess "specified usergroup is not an arrayref: ". $self->usergroup + unless ref($self->usergroup) eq 'ARRAY'; #when provisioning records, export callback runs in svc_Common.pm before #radius_usergroup records can be inserted... @{$self->usergroup}; -- cgit v1.2.1 From 486a0cedc8fc8010e4867516d901beb4cd631d27 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 6 Aug 2006 08:11:22 +0000 Subject: add confession here to diagnose etxrn's problem better --- 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 572a00853..65dcf915f 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -1416,7 +1416,7 @@ Returns all RADIUS groups for this account (see L). sub radius_groups { my $self = shift; if ( $self->usergroup ) { - confess "specified usergroup is not an arrayref: ". $self->usergroup + confess "explicitly specified usergroup not an arrayref: ". $self->usergroup unless ref($self->usergroup) eq 'ARRAY'; #when provisioning records, export callback runs in svc_Common.pm before #radius_usergroup records can be inserted... -- cgit v1.2.1 From 7fc8c3c193c3d673a58566569a0e82609212a087 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 6 Aug 2006 08:43:40 +0000 Subject: add debugging to track down RADIUS group problem --- FS/FS/svc_acct.pm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 65dcf915f..ad8d9a152 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -17,6 +17,7 @@ use Carp; use Fcntl qw(:flock); use Date::Format; use Crypt::PasswdMD5 1.2; +use Data::Dumper; use FS::UID qw( datasrc ); use FS::Conf; use FS::Record qw( qsearch qsearchs fields dbh dbdef ); @@ -221,7 +222,11 @@ jobnum(s) (they will not run until the specific job(s) complete(s)). sub insert { my $self = shift; my %options = @_; - my $error; + + if ( $DEBUG ) { + warn "[$me] insert called on $self: ". Dumper($self). + "\nwith options: ". Dumper(%options); + } local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -234,7 +239,7 @@ sub insert { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - $error = $self->check; + my $error = $self->check; return $error if $error; if ( $self->svcnum && qsearchs('cust_svc',{'svcnum'=>$self->svcnum}) ) { -- cgit v1.2.1 From 936038efe0b75ad037619167f62db7fe16c25255 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 6 Aug 2006 09:10:07 +0000 Subject: this should process default usergroup as well as fixed now --- FS/FS/svc_Common.pm | 15 +++++++++------ FS/FS/svc_acct.pm | 14 ++++++++++++++ httemplate/edit/svc_acct.cgi | 6 +++++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index c1c482d72..fdb4132c4 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -51,7 +51,9 @@ sub new { #$self->{'Hash'} = shift; my $newhash = shift; $self->{'Hash'} = { map { $_ => $newhash->{$_} } qw(svcnum svcpart) }; - $self->setdefault; + + $self->setdefault( $self->_fieldhandlers ); + $self->{'Hash'}{$_} = $newhash->{$_} foreach grep { defined($newhash->{$_}) && length($newhash->{$_}) } keys %$newhash; @@ -69,6 +71,9 @@ sub new { $self; } +#empty default +sub _fieldhandlers { (); } + sub virtual_fields { # This restricts the fields based on part_svc_column and the svcpart of @@ -490,11 +495,9 @@ sub setx { my $columnname = $part_svc_column->columnname; my $columnvalue = $part_svc_column->columnvalue; - if ( exists( $coderef->{$columnname} ) ) { - &{ $coderef->{$columnname} }( $self, $columnvalue); - } else { - $self->setfield( $columnname, $columnvalue ); - } + $columnvalue = &{ $coderef->{$columnname} }( $self, $columnvalue ) + if exists( $coderef->{$columnname} ); + $self->setfield( $columnname, $columnvalue ); } diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index ad8d9a152..c4dbb00c9 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -188,6 +188,20 @@ Creates a new account. To add the account to the database, see L<"insert">. sub table { 'svc_acct'; } +sub _fieldhandlers { + #false laziness with edit/svc_acct.cgi + 'usergroup' => sub { + my $usergroup = shift; + if ( ref($usergroup) eq 'ARRAY' ) { + $usergroup; + } elsif ( length($usergroup) ) { + [ split(/\s*,\s*/, $usergroup) ]; + } else { + []; + } + }, +} + =item insert [ , OPTION => VALUE ... ] Adds this account to the database. If there is an error, returns the error, diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi index 4b324a501..8d50aeda0 100755 --- a/httemplate/edit/svc_acct.cgi +++ b/httemplate/edit/svc_acct.cgi @@ -69,7 +69,11 @@ unless ( $svcnum || $cgi->param('error') ) { #adding } $svc_acct->set_default_and_fixed( { - 'usergroup' => sub { @groups = split(',', shift ); }, + #false laziness w/svc-acct::_fieldhandlers + 'usergroup' => sub { return $_[0] if ref($_[0]) eq 'ARRAY'; + @groups = split(/\s*,\s*/, shift ); + \@groups; + }, } ); } -- cgit v1.2.1 From 86102d2ff550b869f09dcfaa30bbdc98e5580d56 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 6 Aug 2006 09:14:04 +0000 Subject: this just needs to be a hashref and we should be all set with radius groups then --- FS/FS/svc_acct.pm | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index c4dbb00c9..2606c1453 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -189,17 +189,19 @@ Creates a new account. To add the account to the database, see L<"insert">. sub table { 'svc_acct'; } sub _fieldhandlers { - #false laziness with edit/svc_acct.cgi - 'usergroup' => sub { - my $usergroup = shift; - if ( ref($usergroup) eq 'ARRAY' ) { - $usergroup; - } elsif ( length($usergroup) ) { - [ split(/\s*,\s*/, $usergroup) ]; - } else { - []; - } - }, + { + #false laziness with edit/svc_acct.cgi + 'usergroup' => sub { + my $usergroup = shift; + if ( ref($usergroup) eq 'ARRAY' ) { + $usergroup; + } elsif ( length($usergroup) ) { + [ split(/\s*,\s*/, $usergroup) ]; + } else { + []; + } + }, + }; } =item insert [ , OPTION => VALUE ... ] -- cgit v1.2.1 From ad18fd616d98b80945b455ca170ba02089723873 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 6 Aug 2006 20:07:13 +0000 Subject: this should fix the barfing about default radius groups on the new customer screen... --- FS/FS/svc_acct.pm | 2 +- httemplate/edit/process/cust_main.cgi | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 2606c1453..e7d2b37b6 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -703,7 +703,7 @@ sub check { my($recref) = $self->hashref; - my $x = $self->setfixed; + my $x = $self->setfixed( $self->_fieldhandlers ); return $x unless ref($x); my $part_svc = $x; diff --git a/httemplate/edit/process/cust_main.cgi b/httemplate/edit/process/cust_main.cgi index 09a42544c..4ba30c435 100755 --- a/httemplate/edit/process/cust_main.cgi +++ b/httemplate/edit/process/cust_main.cgi @@ -103,8 +103,6 @@ if ( $new->custnum eq '' ) { 'popnum' => $cgi->param('popnum'), } ); - my $y = $svc_acct->setdefault; # arguably should be in new method - $error ||= $y unless ref($y); #and just in case you were silly $svc_acct->svcpart($svcpart); $svc_acct->username($cgi->param('username')); -- cgit v1.2.1 From 08dc9fe500cf44346e409fb8c75eda541300d64d Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 6 Aug 2006 20:21:32 +0000 Subject: yow. fix up group handling --- FS/FS/svc_Common.pm | 4 ++-- FS/FS/svc_acct.pm | 10 +++++----- httemplate/edit/svc_acct.cgi | 19 ++++++++++++++----- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index fdb4132c4..17942c7ec 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -72,7 +72,7 @@ sub new { } #empty default -sub _fieldhandlers { (); } +sub _fieldhandlers { {}; } sub virtual_fields { @@ -475,7 +475,7 @@ sub setx { my $self = shift; my $x = shift; my @x = ref($x) ? @$x : ($x); - my $coderef = scalar(@_) ? shift : {}; + my $coderef = scalar(@_) ? shift : $self->_fieldhandlers; my $error = $self->ut_numbern('svcnum') diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index e7d2b37b6..c50dfd540 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -192,11 +192,11 @@ sub _fieldhandlers { { #false laziness with edit/svc_acct.cgi 'usergroup' => sub { - my $usergroup = shift; - if ( ref($usergroup) eq 'ARRAY' ) { - $usergroup; - } elsif ( length($usergroup) ) { - [ split(/\s*,\s*/, $usergroup) ]; + my( $self, $groups ) = @_; + if ( ref($groups) eq 'ARRAY' ) { + $groups; + } elsif ( length($groups) ) { + [ split(/\s*,\s*/, $groups) ]; } else { []; } diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi index 8d50aeda0..14d6759dc 100755 --- a/httemplate/edit/svc_acct.cgi +++ b/httemplate/edit/svc_acct.cgi @@ -70,11 +70,20 @@ unless ( $svcnum || $cgi->param('error') ) { #adding $svc_acct->set_default_and_fixed( { #false laziness w/svc-acct::_fieldhandlers - 'usergroup' => sub { return $_[0] if ref($_[0]) eq 'ARRAY'; - @groups = split(/\s*,\s*/, shift ); - \@groups; - }, - } ); + 'usergroup' => sub { + my( $self, $groups ) = @_; + if ( ref($groups) eq 'ARRAY' ) { + @groups = @$groups; + $groups; + } elsif ( length($groups) ) { + @groups = split(/\s*,\s*/, $groups); + [ @groups ]; + } else { + @groups = (); + []; + } + } + ); } -- cgit v1.2.1 From 924a1bde45d6fbe134ecd502cd3ba072f5d73195 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 6 Aug 2006 20:23:04 +0000 Subject: silly closing } --- httemplate/edit/svc_acct.cgi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httemplate/edit/svc_acct.cgi b/httemplate/edit/svc_acct.cgi index 14d6759dc..71b324d99 100755 --- a/httemplate/edit/svc_acct.cgi +++ b/httemplate/edit/svc_acct.cgi @@ -82,8 +82,8 @@ unless ( $svcnum || $cgi->param('error') ) { #adding @groups = (); []; } - } - ); + } + } ); } -- cgit v1.2.1 From 81676223e719782d4197841b78434df977457865 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 6 Aug 2006 21:37:57 +0000 Subject: make sure default RADIUS groups don't override ones for existing records --- FS/FS/svc_Common.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 17942c7ec..907f35fa0 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -52,7 +52,8 @@ sub new { my $newhash = shift; $self->{'Hash'} = { map { $_ => $newhash->{$_} } qw(svcnum svcpart) }; - $self->setdefault( $self->_fieldhandlers ); + $self->setdefault( $self->_fieldhandlers ) + unless $self->svcnum; $self->{'Hash'}{$_} = $newhash->{$_} foreach grep { defined($newhash->{$_}) && length($newhash->{$_}) } -- cgit v1.2.1 From 65fc97717e093e8ff35d6f4c0efd04e3af334c8d Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 6 Aug 2006 23:39:02 +0000 Subject: slightly better bootstrapping for htpasswd kludge... hopefully that will go away in 1.7.1 --- FS/FS/access_user.pm | 1 + FS/bin/freeside-adduser | 33 ++++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm index d325dd6cc..37b3b2f37 100644 --- a/FS/FS/access_user.pm +++ b/FS/FS/access_user.pm @@ -104,6 +104,7 @@ sub insert { sub htpasswd_kludge { my $self = shift; + unshift @_, '-c' unless -e $htpasswd_file; if ( system('htpasswd', '-b', @_, $htpasswd_file, diff --git a/FS/bin/freeside-adduser b/FS/bin/freeside-adduser index 0d8b454bf..3976caece 100644 --- a/FS/bin/freeside-adduser +++ b/FS/bin/freeside-adduser @@ -1,13 +1,13 @@ #!/usr/bin/perl -w use strict; -use vars qw($opt_h $opt_b $opt_c $opt_g); +use vars qw($opt_s $opt_h $opt_b $opt_c $opt_g $opt_n); use Fcntl qw(:flock); use Getopt::Std; my $FREESIDE_CONF = "/usr/local/etc/freeside"; -getopts("bch:g:"); +getopts("s:bch:g:n"); die &usage if $opt_c && ! $opt_h; my $user = shift or die &usage; @@ -27,16 +27,21 @@ if ( $opt_h ) { push @args, '-c' if $opt_c; push @args, $opt_h, $user; push @args, shift if $opt_b; + warn join(', ', 'htpasswd', @args)."\n"; system(@args) == 0 or die "htpasswd failed: $?"; } -#my $secretfile = $opt_s || 'secrets'; -# -#open(MAPSECRETS,">>$FREESIDE_CONF/mapsecrets") -# and flock(MAPSECRETS,LOCK_EX) -# or die "can't open $FREESIDE_CONF/mapsecrets: $!"; -#print MAPSECRETS "$user $secretfile\n"; -#close MAPSECRETS or die "can't close $FREESIDE_CONF/mapsecrets: $!"; +if ( $opt_s ) { + open(MAPSECRETS,">>$FREESIDE_CONF/mapsecrets") + and flock(MAPSECRETS,LOCK_EX) + or die "can't open $FREESIDE_CONF/mapsecrets: $!"; + print MAPSECRETS "$user $opt_s\n"; + close MAPSECRETS or die "can't close $FREESIDE_CONF/mapsecrets: $!"; +} + +### + +exit if $opt_n; ### @@ -72,7 +77,7 @@ if ( $opt_g ) { ### sub usage { - die "Usage:\n\n freeside-adduser [ -h htpasswd_file [ -c ] [ -b ] ] [ -g groupnum ] username" + die "Usage:\n\n freeside-adduser [ -h htpasswd_file [ -c ] [ -b ] ] [ -g groupnum ] username [ password ]" } =head1 NAME @@ -81,7 +86,7 @@ freeside-adduser - Command line interface to add (freeside) users. =head1 SYNOPSIS - freeside-adduser [ -h htpasswd_file [ -c ] ] -g 1 username + freeside-adduser [ -n ] [ -h htpasswd_file [ -c ] [ -b ] ] [ -g groupnum ] username [ password ] =head1 DESCRIPTION @@ -96,6 +101,12 @@ sales/tech folks) to the web interface, not for adding customer accounts. -g: initial groupnum + Development/multi-DB options: + + -s: alternate secrets file + + -n: no ACL added, for bootstrapping + =head1 SEE ALSO L(1), base Freeside documentation -- cgit v1.2.1 From 786b78c646f892e1ae80006fb7870960780ea5db Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 7 Aug 2006 02:19:28 +0000 Subject: get rid of the extra border in nested tables --- httemplate/search/elements/search.html | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/httemplate/search/elements/search.html b/httemplate/search/elements/search.html index 6cf574164..3e689eba1 100644 --- a/httemplate/search/elements/search.html +++ b/httemplate/search/elements/search.html @@ -380,8 +380,7 @@ my $tableref = $_; - ''. + '
    '. join('', map { @@ -393,7 +392,7 @@ my $element = $_; - ' + <% foreach my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } - qsearch('cust_pay_batch', {} ) + qsearch('cust_pay_batch', {'batchnum'=>$batchnum} ) ) { my $cardnum = $cust_pay_batch->payinfo; #$cardnum =~ s/.{4}$/xxxx/; @@ -71,6 +87,7 @@ foreach my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } + <% } %> diff --git a/httemplate/browse/pay_batch.cgi b/httemplate/browse/pay_batch.cgi new file mode 100755 index 000000000..66c86d676 --- /dev/null +++ b/httemplate/browse/pay_batch.cgi @@ -0,0 +1,54 @@ + +<%= include("/elements/header.html","Credit card batches", menubar( 'Main Menu' => $p,)) %> + +

    + +<% + my %statusmap = ('I'=>'In Transit', 'O'=>'Open', 'R'=>'Resolved'); +%> + +
    +<%= &table() %> + + + + + + + + + +<% +foreach my $pay_batch ( sort { $b->batchnum <=> $a->batchnum } + qsearch('pay_batch', {} ) +) { + + my $statement = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . + $pay_batch->batchnum; + my $sth = dbh->prepare($statement) or die dbh->errstr. "doing $statement"; + $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; + my $total = $sth->fetchrow_arrayref->[0]; + + my $c_statement = "SELECT COUNT(*) from cust_pay_batch WHERE batchnum=" . + $pay_batch->batchnum; + my $c_sth = dbh->prepare($c_statement) + or die dbh->errstr. "doing $c_statement"; + $c_sth->execute or die "Error executing \"$c_statement\": ". $c_sth->errstr; + my $cards = $c_sth->fetchrow_arrayref->[0]; + +%> + + + + + + + + + + +<% } %> + +
    {'align'} ? ' ALIGN="'. $element->{'align'}. '"' : '' @@ -432,6 +431,8 @@ ) { + my $class = ( $field =~ /^ - + <% } %> <% } else { %> <% foreach ( @$row ) { %> -- cgit v1.2.1 From 7aae40398f1c8ed42424f1694640c9796a580d22 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 7 Aug 2006 02:44:29 +0000 Subject: add internal user disable-ing --- FS/FS/CurrentUser.pm | 1 + FS/FS/Schema.pm | 1 + FS/FS/access_user.pm | 3 ++ httemplate/browse/access_user.html | 87 ++++++++++++++++++++++++++------------ httemplate/browse/part_pkg.cgi | 2 + httemplate/edit/access_user.html | 2 + httemplate/edit/elements/edit.html | 15 ++++--- 7 files changed, 80 insertions(+), 31 deletions(-) diff --git a/FS/FS/CurrentUser.pm b/FS/FS/CurrentUser.pm index 9f2c93f4f..bcd337d2c 100644 --- a/FS/FS/CurrentUser.pm +++ b/FS/FS/CurrentUser.pm @@ -36,6 +36,7 @@ sub load_user { $CurrentUser = qsearchs('access_user', { 'username' => $user, #'_password' => + 'disabled' => '', } ); die "unknown user: $user" unless $CurrentUser; # or bad password diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 3e1d68f1b..04dcb8245 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1476,6 +1476,7 @@ sub tables_hashref { '_password', 'varchar', '', $char_d, '', '', 'last', 'varchar', '', $char_d, '', '', 'first', 'varchar', '', $char_d, '', '', + 'disabled', 'char', 'NULL', 1, '', '', ], 'primary_key' => 'usernum', 'unique' => [ [ 'username' ] ], diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm index 37b3b2f37..5d1e183e7 100644 --- a/FS/FS/access_user.pm +++ b/FS/FS/access_user.pm @@ -47,6 +47,8 @@ FS::Record. The following fields are currently supported: =item first - +=item disabled - empty or 'Y' + =back =head1 METHODS @@ -211,6 +213,7 @@ sub check { || $self->ut_text('_password') || $self->ut_text('last') || $self->ut_text('first') + || $self->ut_enum('disabled', [ '', 'Y' ] ) ; return $error if $error; diff --git a/httemplate/browse/access_user.html b/httemplate/browse/access_user.html index be11bf82a..5b787977d 100644 --- a/httemplate/browse/access_user.html +++ b/httemplate/browse/access_user.html @@ -4,6 +4,14 @@ my $html_init = "Internal users have access to the back-office interface. Typically, this is your employees and contractors, but in a VISP setup, you can also add accounts for your reseller's employees. It is highly recommended to add a separate account for each person rather than using role accounts.

    ". qq!Add an internal user

    !; +#false laziness w/part_pkg.cgi +my %search = (); +my $search = ''; +unless ( $cgi->param('showdisabled') ) { + %search = ( 'disabled' => '' ); + $search = "( disabled = '' OR disabled IS NULL )"; +} + #false laziness w/access_group.html & agent_type.cgi my $groups_sub = sub { my $access_user = shift; @@ -28,36 +36,63 @@ my $groups_sub = sub { }; +my $posttotal; +if ( $cgi->param('showdisabled') ) { + $cgi->param('showdisabled', 0); + $posttotal = '( hide disabled users )'; + $cgi->param('showdisabled', 1); +} else { + $cgi->param('showdisabled', 1); + $posttotal = '( show disabled users )'; + $cgi->param('showdisabled', 0); +} + my $count_query = 'SELECT COUNT(*) FROM access_user'; +$count_query .= " WHERE $search" + if $search; my $link = [ $p.'edit/access_user.html?', 'usernum' ]; +my @header = ( '#', 'Username' ); +my @fields = ( 'usernum', 'username' ); +my $align = 'rl'; +my @links = ( $link, $link ); +my @style = ( '', '' ); + +#false laziness w/part_pkg.cgi +#unless ( $cgi->param('showdisabled') ) { #its been reversed already +if ( $cgi->param('showdisabled') ) { #its been reversed already + push @header, 'Status'; + push @fields, sub { shift->disabled + ? 'DISABLED' + : 'Active' + }; + push @links, ''; + $align .= 'c'; + push @style, 'b'; +} + +push @header, 'Full name', 'Groups'; +push @fields, 'name', $groups_sub; +push @links, $link, ''; +$align .= 'll'; + %><%= include( 'elements/browse.html', - 'title' => 'Internal Users', - 'menubar' => [ #'Main menu' => $p, - 'Internal access groups' => $p.'browse/access_group.html', - ], - 'html_init' => $html_init, - 'name' => 'internal users', - 'query' => { 'table' => 'access_user', - 'hashref' => {}, - 'extra_sql' => 'ORDER BY last, first', - }, - 'count_query' => $count_query, - 'header' => [ '#', - 'Username', - 'Full name', - 'Groups' - ], - 'fields' => [ 'usernum', - 'username', - 'name', # sub { shift->name }, - $groups_sub, - ], - 'links' => [ $link, - $link, - $link, - '' - ], + 'title' => 'Internal Users', + 'menubar' => [ #'Main menu' => $p, + 'Internal access groups' => $p.'browse/access_group.html', + ], + 'html_init' => $html_init, + 'html_posttotal' => $posttotal, + 'name' => 'internal users', + 'query' => { 'table' => 'access_user', + 'hashref' => \%search, + 'extra_sql' => 'ORDER BY last, first', + }, + 'count_query' => $count_query, + 'header' => \@header, + 'fields' => \@fields, + 'links' => \@links, + 'style' => \@style, ) %> diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index b67a5e55f..a5e212d17 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -1,5 +1,6 @@ <% +#false laziness w/access_user.html my %search = (); my $search = ''; unless ( $cgi->param('showdisabled') ) { @@ -71,6 +72,7 @@ my $align = 'rll'; my @links = ( $link, $link, '' ); my @style = ( '', '', '' ); +#false laziness w/access_user.html #unless ( $cgi->param('showdisabled') ) { #its been reversed already if ( $cgi->param('showdisabled') ) { #its been reversed already push @header, 'Status'; diff --git a/httemplate/edit/access_user.html b/httemplate/edit/access_user.html index 2b19dbf7b..fb2a97196 100644 --- a/httemplate/edit/access_user.html +++ b/httemplate/edit/access_user.html @@ -6,6 +6,7 @@ { field=>'_password', type=>'password' }, 'last', 'first', + { field=>'disabled', type=>'checkbox', value=>'Y' }, ], 'labels' => { 'usernum' => 'User number', @@ -13,6 +14,7 @@ '_password' => 'Password', 'last' => 'Last name', 'first' => 'First name', + 'disabled' => 'Disable employee', }, 'viewall_dir' => 'browse', 'html_bottom' => diff --git a/httemplate/edit/elements/edit.html b/httemplate/edit/elements/edit.html index f79cc0b24..c40a00492 100644 --- a/httemplate/edit/elements/edit.html +++ b/httemplate/edit/elements/edit.html @@ -15,7 +15,9 @@ # 'fields' => [ # 'columname', # { 'field' => 'another_columname', - # 'type' => 'text', #text, fixed, hidden + # 'type' => 'text', #text, fixed, hidden, checkbox + # #eventually more for , etc. fields - if ( $type eq 'fixed' ) { - %> + <% if ( $type eq 'fixed' ) { %>
    + <% } elsif ( $type eq 'checkbox' ) { %> + + + <% } else { %>
    ><%= $font %><%= $a %><%= $s %><%= $field %><%= $es %><%= $a ? '' : '' %><%= $font ? '' : '' %>><%= $font %><%= $a %><%= $s %><%= $field %><%= $es %><%= $a ? '' : '' %><%= $font ? '' : '' %><%= $f->{'value'} %> + $field() eq $f->{'value'} ? ' CHECKED' : '' %>> + -- cgit v1.2.1 From 5d133672add54fb6bdd6690cdd9ca386d7a44a10 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 9 Aug 2006 03:45:00 +0000 Subject: please bleeding-edge debian perl, would you put it in /usr/local/sbin? thanks. --- fs_selfservice/FS-SelfService/Makefile.PL | 1 + 1 file changed, 1 insertion(+) diff --git a/fs_selfservice/FS-SelfService/Makefile.PL b/fs_selfservice/FS-SelfService/Makefile.PL index 0b7fc4606..85c92b4fb 100644 --- a/fs_selfservice/FS-SelfService/Makefile.PL +++ b/fs_selfservice/FS-SelfService/Makefile.PL @@ -9,6 +9,7 @@ WriteMakefile( ], 'INSTALLSCRIPT' => '/usr/local/sbin', 'INSTALLSITEBIN' => '/usr/local/sbin', + 'INSTALLSITESCRIPT' => '/usr/local/sbin', #recent deb users this... 'PERM_RWX' => '750', 'PREREQ_PM' => { 'Storable' => 2.09, -- cgit v1.2.1 From b58e61ac7df612f606c3e68371265e790e0be585 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 9 Aug 2006 06:34:26 +0000 Subject: self-service interface: move from text to html invoices --- FS/FS/ClientAPI/MyAccount.pm | 27 ++++++++++++++++++++++ fs_selfservice/FS-SelfService/SelfService.pm | 1 + .../FS-SelfService/cgi/cust_bill-logo.cgi | 18 +++++++++++++++ .../FS-SelfService/cgi/view_invoice.html | 4 +--- 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 4b67f53af..163718e13 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -414,10 +414,37 @@ sub invoice { return { 'error' => '', 'invnum' => $invnum, 'invoice_text' => join('', $cust_bill->print_text ), + 'invoice_html' => $cust_bill->print_html, }; } +sub invoice_logo { + my $p = shift; + + #sessioning for this? how do we get the session id to the backend invoice + # template so it can add it to the link, blah + + my $templatename = $p->{'templatename'}; + + #false laziness-ish w/view/cust_bill-logo.cgi + + my $conf = new FS::Conf; + if ( $templatename =~ /^([^\.\/]*)$/ && $conf->exists("logo_$1.png") ) { + $templatename = "_$1"; + } else { + $templatename = ''; + } + + my $filename = "logo$templatename.png"; + + return { 'error' => '', + 'logo' => $conf->config_binary($filename), + 'content_type' => 'image/png', #should allow gif, jpg too + }; +} + + sub list_invoices { my $p = shift; my $session = _cache->get($p->{'session_id'}) diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm index bfce1287b..16ca48ec8 100644 --- a/fs_selfservice/FS-SelfService/SelfService.pm +++ b/fs_selfservice/FS-SelfService/SelfService.pm @@ -27,6 +27,7 @@ $socket .= '.'.$tag if defined $tag && length($tag); 'customer_info' => 'MyAccount/customer_info', 'edit_info' => 'MyAccount/edit_info', #add to ss cgi! 'invoice' => 'MyAccount/invoice', + 'invoice_logo' => 'MyAccount/invoice_logo', 'list_invoices' => 'MyAccount/list_invoices', #? 'cancel' => 'MyAccount/cancel', #add to ss cgi! 'payment_info' => 'MyAccount/payment_info', diff --git a/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi b/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi new file mode 100644 index 000000000..bf82a87e8 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi @@ -0,0 +1,18 @@ +#!/usr/bin/perl -Tw + +use strict; +use CGI; +use FS::SelfService qw( invoice_logo ); + +$cgi = new CGI; + +my($query) = $cgi->keywords; +$query =~ /^([^\.\/]*)$/ or '' =~ /^()$/; +my $templatename = $1; +invoice_logo($templatename); + +print $cgi->header( '-type' => $content_type, + '-expires' => 'now', + ). + $logo; + diff --git a/fs_selfservice/FS-SelfService/cgi/view_invoice.html b/fs_selfservice/FS-SelfService/cgi/view_invoice.html index 72d061980..ad2f4f419 100644 --- a/fs_selfservice/FS-SelfService/cgi/view_invoice.html +++ b/fs_selfservice/FS-SelfService/cgi/view_invoice.html @@ -4,9 +4,7 @@ <%= include('myaccount_menu') %> -
    -<%= $invoice_text %>
    -
    +<%= $invoice_html %>

    -- cgit v1.2.1 From 6c375156081be5d2023001ed8eaac9b6db568e95 Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 9 Aug 2006 06:43:02 +0000 Subject: batch refactor --- FS/FS/Schema.pm | 8 +- FS/FS/Setup.pm | 7 ++ FS/FS/cust_bill.pm | 25 ++++- FS/FS/cust_main.pm | 30 ++++- FS/FS/cust_pay_batch.pm | 207 ++++++++++++++++++++++++++++++----- FS/FS/part_bill_event.pm | 2 +- FS/FS/pay_batch.pm | 8 +- FS/FS/payby.pm | 5 + README.1.7.0 | 2 + httemplate/browse/cust_pay_batch.cgi | 33 ++++-- httemplate/browse/pay_batch.cgi | 54 +++++++++ httemplate/docs/schema.html | 4 + httemplate/misc/download-batch.cgi | 37 ++++++- 13 files changed, 370 insertions(+), 52 deletions(-) create mode 100755 httemplate/browse/pay_batch.cgi diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 04dcb8245..5dac26600 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -542,7 +542,9 @@ sub tables_hashref { 'pay_batch' => { #batches of payments to an external processor 'columns' => [ 'batchnum', 'serial', '', '', '', '', - 'status', 'char', 'NULL', 1, '', '', + 'status', 'char', 'NULL', 1, '', '', + 'download', @date_type, 'NULL', '', + 'upload', @date_type, 'NULL', '', ], 'primary_key' => 'batchnum', 'unique' => [], @@ -565,11 +567,13 @@ sub tables_hashref { 'zip', 'varchar', 'NULL', 10, '', '', 'country', 'char', '', 2, '', '', # 'trancode', 'int', '', '', '', '' + 'payby', 'char', '', 4, '', '', # CARD/BILL/COMP, should be 'payinfo', 'varchar', '', 512, '', '', #'exp', @date_type, '', '' - 'exp', 'varchar', '', 11, '', '', + 'exp', 'varchar', 'NULL', 11, '', '', 'payname', 'varchar', 'NULL', $char_d, '', '', 'amount', @money_type, '', '', + 'status', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'paybatchnum', 'unique' => [], diff --git a/FS/FS/Setup.pm b/FS/FS/Setup.pm index 7475d3782..90f6f1011 100644 --- a/FS/FS/Setup.pm +++ b/FS/FS/Setup.pm @@ -168,6 +168,13 @@ sub initial_data { 'weight' => 50, 'plan' => 'send', }, + { 'payby' => 'DCLN', + 'event' => 'Suspend', + 'seconds' => 0, + 'eventcode' => '$cust_bill->suspend();', + 'weight' => 40, + 'plan' => 'suspend', + }, ], #you must create a service definition. An example of a service definition diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index def84f91a..d45b66faf 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -1284,6 +1284,9 @@ sub batch_card { my $self = shift; my $cust_main = $self->cust_main; + my $amount = sprintf("%.2f", $cust_main->balance - $cust_main->in_transit_payments); + return '' unless $amount > 0; + my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; @@ -1300,9 +1303,14 @@ sub batch_card { } } + my $old_cust_pay_batch = qsearchs('cust_pay_batch', { + 'batchnum' => $pay_batch->getfield('batchnum'), + 'custnum' => $cust_main->getfield('custnum'), + } ); + my $cust_pay_batch = new FS::cust_pay_batch ( { 'batchnum' => $pay_batch->getfield('batchnum'), - 'invnum' => $self->getfield('invnum'), + 'invnum' => $self->getfield('invnum'), # is there a better value? 'custnum' => $cust_main->getfield('custnum'), 'last' => $cust_main->getfield('last'), 'first' => $cust_main->getfield('first'), @@ -1312,12 +1320,23 @@ sub batch_card { 'state' => $cust_main->getfield('state'), 'zip' => $cust_main->getfield('zip'), 'country' => $cust_main->getfield('country'), + 'payby' => $cust_main->payby, 'payinfo' => $cust_main->payinfo, 'exp' => $cust_main->getfield('paydate'), 'payname' => $cust_main->getfield('payname'), - 'amount' => $self->owed, + 'amount' => $amount, # consolidating } ); - my $error = $cust_pay_batch->insert; + + $cust_pay_batch->paybatchnum($old_cust_pay_batch->paybatchnum) + if $old_cust_pay_batch; + + my $error; + if ($old_cust_pay_batch) { + $error = $cust_pay_batch->replace($old_cust_pay_batch) + } else { + $error = $cust_pay_batch->insert; + } + if ( $error ) { $dbh->rollback if $oldAutoCommit; die $error; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index f4568a8a0..f1d969cd1 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2046,6 +2046,8 @@ quiet - set true to surpress email card/ACH decline notices. freq - "1d" for the traditional, daily events (the default), or "1m" for the new monthly events +payby - allows for one time override of normal customer billing method + =cut sub collect { @@ -2116,7 +2118,10 @@ sub collect { } qsearch( { 'table' => 'part_bill_event', - 'hashref' => { 'payby' => $self->payby, + 'hashref' => { 'payby' => (exists($options{'payby'}) + ? $options{'payby'} + : $self->payby + ), 'disabled' => '', }, 'extra_sql' => $extra_sql, } ) @@ -3143,6 +3148,29 @@ sub balance_date { ); } +=item in_transit_payments + +Returns the total of requests for payments for this customer pending in +batches in transit to the bank. See L and L + +=cut + +sub in_transit_payments { + my $self = shift; + my $in_transit_payments = 0; + foreach my $pay_batch ( qsearch('pay_batch', { + 'status' => 'I', + } ) ) { + foreach my $cust_pay_batch ( qsearch('cust_pay_batch', { + 'batchnum' => $pay_batch->batchnum, + 'custnum' => $self->custnum, + } ) ) { + $in_transit_payments += $cust_pay_batch->amount; + } + } + sprintf( "%.2f", $in_transit_payments ); +} + =item paydate_monthyear Returns a two-element list consisting of the month and year of this customer's diff --git a/FS/FS/cust_pay_batch.pm b/FS/FS/cust_pay_batch.pm index 117d72561..e057334c2 100644 --- a/FS/FS/cust_pay_batch.pm +++ b/FS/FS/cust_pay_batch.pm @@ -1,12 +1,17 @@ package FS::cust_pay_batch; use strict; -use vars qw( @ISA ); -use FS::Record qw(dbh qsearchs); +use vars qw( @ISA $DEBUG ); +use FS::Record qw(dbh qsearch qsearchs); use Business::CreditCard; @ISA = qw( FS::Record ); +# 1 is mostly method/subroutine entry and options +# 2 traces progress of some operations +# 3 is even more information including possibly sensitive data +$DEBUG = 0; + =head1 NAME FS::cust_pay_batch - Object methods for batch cards @@ -37,6 +42,10 @@ following fields are currently supported: =item paybatchnum - primary key (automatically assigned) +=item batchnum - indentifies group in batch + +=item payby - CARD/CHEK/LECB/BILL/COMP + =item payinfo =item exp - card expiration @@ -65,6 +74,8 @@ following fields are currently supported: =item country +=item status + =back =head1 METHODS @@ -94,16 +105,8 @@ otherwise returns false. =item replace OLD_RECORD -#inactive -# -#Replaces the OLD_RECORD with this one in the database. If there is an error, -#returns the error, otherwise returns false. - -=cut - -sub replace { - return "Can't (yet?) replace batched transactions!"; -} +Replaces the OLD_RECORD with this one in the database. If there is an error, +returns the error, otherwise returns false. =item check @@ -119,7 +122,6 @@ sub check { my $error = $self->ut_numbern('paybatchnum') || $self->ut_numbern('trancode') #depriciated - || $self->ut_number('payinfo') || $self->ut_money('amount') || $self->ut_number('invnum') || $self->ut_number('custnum') @@ -137,6 +139,10 @@ sub check { $self->first =~ /^([\w \,\.\-\']+)$/ or return "Illegal first name"; $self->first($1); + $self->payby =~ /^(CARD|CHEK|LECB|BILL|COMP|PREP|CASH|WEST|MCRD)$/ + or return "Illegal payby"; + $self->payby($1); + # FIXME # there is no point in false laziness here # we will effectively set "check_payinfo to 0" @@ -152,7 +158,8 @@ sub check { #return "Unknown card type" if cardtype($cardnum) eq "Unknown"; if ( $self->exp eq '' ) { - return "Expiration date required"; #unless + return "Expiration date required" + unless $self->payby =~ /^(CHEK|DCHK|LECB|WEST)$/; $self->exp(''); } else { if ( $self->exp =~ /^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})$/ ) { @@ -226,7 +233,11 @@ sub import_results { my $format = $param->{'format'}; my $paybatch = $param->{'paybatch'}; + my $filetype; # CSV, Fixed80, Fixed264 my @fields; + my $formatre; # for Fixed.+ + my @values; + my $begin_condition; my $end_condition; my $end_hook; my $hook; @@ -235,6 +246,8 @@ sub import_results { if ( $format eq 'csv-td_canada_trust-merchant_pc_batch' ) { + $filetype = "CSV"; + @fields = ( 'paybatchnum', # Reference#: Invoice number of the transaction 'paid', # Amount: Amount of the transaction. Dollars and cents @@ -293,6 +306,58 @@ sub import_results { }; + }elsif ( $format eq 'PAP' ) { + + $filetype = "Fixed264"; + + @fields = ( + 'recordtype', # We are interested in the 'D' or debit records + 'batchnum', # Record#: batch number we used when sending the file + 'datacenter', # Where in the bowels of the bank the data was processed + 'paid', # Amount: Amount of the transaction. Dollars and cents + # with no decimal entered. + '_date', # Transaction Date: Date the Transaction was processed + 'bank', # Routing information + 'payinfo', # Account number for the transaction + 'paybatchnum', # Reference#: Invoice number of the transaction + ); + + $formatre = '^(.).{19}(.{4})(.{3})(.{10})(.{6})(.{9})(.{12}).{110}(.{19}).{71}$'; + + $end_condition = sub { + my $hash = shift; + $hash->{'recordtype'} eq 'W'; + }; + + $end_hook = sub { + my( $hash, $total) = @_; + $total = sprintf("%.2f", $total); + my $batch_total = $hash->{'datacenter'}.$hash->{'paid'}. + substr($hash->{'_date'},0,1); # YUCK! + $batch_total = sprintf("%.2f", $batch_total / 100 ); + return "Our total $total does not match bank total $batch_total!" + if $total != $batch_total; + ''; + }; + + $hook = sub { + my $hash = shift; + $hash->{'paid'} = sprintf("%.2f", $hash->{'paid'} / 100 ); + my $tmpdate = timelocal( 0,0,1,1,0,substr($hash->{'_date'}, 0, 3)+2000); + $tmpdate += 86400*(substr($hash->{'_date'}, 3, 3)-1) ; + $hash->{'_date'} = $tmpdate; + $hash->{'payinfo'} = $hash->{'payinfo'} . '@' . $hash->{'bank'}; + }; + + $approved_condition = sub { + 1; + }; + + $declined_condition = sub { + 0; + }; + + } else { return "Unknown format $format"; } @@ -316,10 +381,10 @@ sub import_results { return "batch $paybatch is not in transit"; }; - my %batchhash = $pay_batch->hash; - $batchhash{'status'} = 'R'; # Resolved - my $newbatch = new FS::pay_batch ( \%batchhash ); - my $error = $newbatch->replace($paybatch); + my $newbatch = new FS::pay_batch { $pay_batch->hash }; + $newbatch->status('R'); # Resolved + $newbatch->upload(time); + my $error = $newbatch->replace($pay_batch); if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error @@ -331,12 +396,23 @@ sub import_results { next if $line =~ /^\s*$/; #skip blank lines - $csv->parse($line) or do { + if ($filetype eq "CSV") { + $csv->parse($line) or do { + $dbh->rollback if $oldAutoCommit; + return "can't parse: ". $csv->error_input(); + }; + @values = $csv->fields(); + }elsif ($filetype eq "Fixed80" || $filetype eq "Fixed264"){ + @values = $line =~ /$formatre/; + unless (@values) { + $dbh->rollback if $oldAutoCommit; + return "can't parse: ". $line; + }; + }else{ $dbh->rollback if $oldAutoCommit; - return "can't parse: ". $csv->error_input(); - }; + return "Unknown file type $filetype"; + } - my @values = $csv->fields(); my %hash; foreach my $field ( @fields ) { my $value = shift @values; @@ -354,26 +430,25 @@ sub import_results { } my $cust_pay_batch = - qsearchs('cust_pay_batch', { 'paybatchnum' => $hash{'paybatchnum'} } ); + qsearchs('cust_pay_batch', { 'paybatchnum' => $hash{'paybatchnum'}+0 } ); unless ( $cust_pay_batch ) { $dbh->rollback if $oldAutoCommit; return "unknown paybatchnum $hash{'paybatchnum'}\n"; } my $custnum = $cust_pay_batch->custnum, + my $payby = $cust_pay_batch->payby, - my $error = $cust_pay_batch->delete; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error removing paybatchnum $hash{'paybatchnum'}: $error\n"; - } + my $new_cust_pay_batch = new FS::cust_pay_batch { $cust_pay_batch->hash }; &{$hook}(\%hash); if ( &{$approved_condition}(\%hash) ) { + $new_cust_pay_batch->status('Approved'); + my $cust_pay = new FS::cust_pay ( { 'custnum' => $custnum, - 'payby' => 'CARD', + 'payby' => $payby, 'paybatch' => $paybatch, map { $_ => $hash{$_} } (qw( paid _date payinfo )), } ); @@ -388,11 +463,83 @@ sub import_results { } elsif ( &{$declined_condition}(\%hash) ) { + $new_cust_pay_batch->status('Declined'); + #this should be configurable... if anybody else ever uses batches - $cust_pay_batch->cust_main->suspend; + # $cust_pay_batch->cust_main->suspend; + + foreach my $part_bill_event ( + sort { $a->seconds <=> $b->seconds + || $a->weight <=> $b->weight + || $a->eventpart <=> $b->eventpart } + grep { ! qsearch( 'cust_bill_event', { + 'invnum' => $cust_pay_batch->invnum, + 'eventpart' => $_->eventpart, + 'status' => 'done', + } ) + } + qsearch( { + 'table' => 'part_bill_event', + 'hashref' => { 'payby' => 'DCLN', + 'disabled' => '', }, + } ) + ) { + + # don't run subsequent events if balance<=0 + last if $cust_pay_batch->cust_main->balance <= 0; + + warn " calling invoice event (". $part_bill_event->eventcode. ")\n" + if $DEBUG > 1; + my $cust_main = $cust_pay_batch->cust_main; #for callback + + my $error; + { + local $SIG{__DIE__}; # don't want Mason __DIE__ handler active + $error = eval $part_bill_event->eventcode; + } + + my $status = ''; + my $statustext = ''; + if ( $@ ) { + $status = 'failed'; + $statustext = $@; + } elsif ( $error ) { + $status = 'done'; + $statustext = $error; + } else { + $status = 'done' + } + + #add cust_bill_event + my $cust_bill_event = new FS::cust_bill_event { + 'invnum' => $cust_pay_batch->invnum, + 'eventpart' => $part_bill_event->eventpart, + '_date' => time, + 'status' => $status, + 'statustext' => $statustext, + }; + $error = $cust_bill_event->insert; + if ( $error ) { + # gah, even with transactions. + $dbh->commit if $oldAutoCommit; #well. + my $e = 'WARNING: Event run but database not updated - '. + 'error inserting cust_bill_event, invnum #'. $cust_pay_batch->invnum. + ', eventpart '. $part_bill_event->eventpart. + ": $error"; + warn $e; + return $e; + } + + } } + my $error = $new_cust_pay_batch->replace($cust_pay_batch); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "error updating status of paybatchnum $hash{'paybatchnum'}: $error\n"; + } + } $dbh->commit or die $dbh->errstr if $oldAutoCommit; diff --git a/FS/FS/part_bill_event.pm b/FS/FS/part_bill_event.pm index 98e15d4a2..2aef5bcce 100644 --- a/FS/FS/part_bill_event.pm +++ b/FS/FS/part_bill_event.pm @@ -144,7 +144,7 @@ sub check { } my $error = $self->ut_numbern('eventpart') - || $self->ut_enum('payby', [qw( CARD DCRD CHEK DCHK LECB BILL COMP )] ) + || $self->ut_enum('payby', [qw( CARD DCLN DCRD CHEK DCHK LECB BILL COMP )] ) || $self->ut_text('event') || $self->ut_anything('eventcode') || $self->ut_number('seconds') diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm index 41b312c7a..7d9d9fb09 100644 --- a/FS/FS/pay_batch.pm +++ b/FS/FS/pay_batch.pm @@ -34,7 +34,11 @@ FS::Record. The following fields are currently supported: =item batchnum - primary key -=item status - +=item status - + +=item download - + +=item upload - =back @@ -109,7 +113,7 @@ sub check { =head1 BUGS -The author forgot to customize this manpage. +status is somewhat redundant now that download and upload exist =head1 SEE ALSO diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm index 9f8b68918..72a876655 100644 --- a/FS/FS/payby.pm +++ b/FS/FS/payby.pm @@ -90,6 +90,11 @@ tie %hash, 'Tie::IxHash', shortname => 'Complimentary', longname => 'Complimentary', }, + 'DCLN' => { # This is only an event. + tinyname => 'declined', + shortname => 'Declined payment', + longname => 'Declined payment', + }, ; sub payby { diff --git a/README.1.7.0 b/README.1.7.0 index 69b395a34..c82c8ada9 100644 --- a/README.1.7.0 +++ b/README.1.7.0 @@ -8,9 +8,11 @@ ALTER TABLE cust_pay_batch ALTER COLUMN batchnum SET NOT NULL; ALTER TABLE cust_pay_batch ADD COLUMN payinfo varchar(512); UPDATE cust_pay_batch SET payinfo = cardnum; ALTER TABLE cust_pay_batch DROP COLUMN cardnum; +ALTER TABLE cust_pay_batch ALTER COLUMN exp DROP NOT NULL; ALTER TABLE h_cust_pay_batch ADD COLUMN payinfo varchar(512); UPDATE h_cust_pay_batch SET payinfo = cardnum; ALTER TABLE h_cust_pay_batch DROP COLUMN cardnum; +ALTER TABLE h_cust_pay_batch ALTER COLUMN exp DROP NOT NULL; make install-perl-modules run "freeside-upgrade username" to uprade your database schema diff --git a/httemplate/browse/cust_pay_batch.cgi b/httemplate/browse/cust_pay_batch.cgi index c7f0afe76..98ea2f5a2 100755 --- a/httemplate/browse/cust_pay_batch.cgi +++ b/httemplate/browse/cust_pay_batch.cgi @@ -1,12 +1,24 @@ -<%= include("/elements/header.html","Pending credit card batch", menubar( 'Main Menu' => $p,)) %> +<%= include("/elements/header.html","Credit card batch details", menubar( 'Main Menu' => $p,)) %> + +<% + +die "No batch specified (bad URL)!" unless $cgi->keywords; +my($query) = $cgi->keywords; +$query =~ /^(\d+)$/; +my $batchnum = $1; +my $pay_batch = qsearchs('pay_batch',{'batchnum'=>$batchnum}); +die "Batch not found!" unless $pay_batch; + +%>
    Download batch in format
    + + +

    @@ -15,25 +27,28 @@ Filename
    Format

    <% - my $statement = "SELECT SUM(amount) from cust_pay_batch"; + my $statement = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=". + $batchnum; my $sth = dbh->prepare($statement) or die dbh->errstr. "doing $statement"; $sth->execute or die "Error executing \"$statement\": ". $sth->errstr; my $total = $sth->fetchrow_arrayref->[0]; - my $c_statement = "SELECT COUNT(*) from cust_pay_batch"; + my $c_statement = "SELECT COUNT(*) from cust_pay_batch WHERE batchnum=". + $batchnum; my $c_sth = dbh->prepare($c_statement) or die dbh->errstr. "doing $c_statement"; $c_sth->execute or die "Error executing \"$c_statement\": ". $c_sth->errstr; my $cards = $c_sth->fetchrow_arrayref->[0]; %> <%= $cards %> credit card payments batched
    -$<%= sprintf("%.2f", $total) %> total in pending batch
    +$<%= sprintf("%.2f", $total) %> total in batch

    <%= &table() %> @@ -45,11 +60,12 @@ $<%= sprintf("%.2f", $total) %> total in pending batch
    Card Exp AmountStatus
    <%= $cardnum %> <%= $exp %> $<%= $cust_pay_batch->amount %><%= $cust_pay_batch->status %>
    BatchFirst DownloadLast UploadItem CountAmountStatus
    <%= $pay_batch->batchnum %><%= $pay_batch->download ? time2str("%a %b %e %T %Y", $pay_batch->download) : '' %><%= $pay_batch->upload ? time2str("%a %b %e %T %Y", $pay_batch->upload) : '' %><%= $cards %><%= $total %><%= $statusmap{$pay_batch->status} %>
    + + diff --git a/httemplate/docs/schema.html b/httemplate/docs/schema.html index d9e35efc7..cd4914a6c 100644 --- a/httemplate/docs/schema.html +++ b/httemplate/docs/schema.html @@ -203,11 +203,14 @@
    • batchnum
    • status +
    • download +
    • upload
  • cust_pay_batch - Pending batch members
    • paybatchnum
    • batchnum +
    • payby - CARD, CHEK, LECB, BILL, or COMP
    • payinfo - account number
    • exp - card expiration
    • amount @@ -222,6 +225,7 @@
    • state
    • zip
    • country +
    • status
  • cust_pkg - Customer billing items
      diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi index 6172b1335..2c6481493 100644 --- a/httemplate/misc/download-batch.cgi +++ b/httemplate/misc/download-batch.cgi @@ -5,6 +5,13 @@ my $conf=new FS::Conf; #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes http_header('Content-Type' => 'text/plain' ); +my $batchnum; +if ( $cgi->param('batchnum') =~ /^(\d+)$/ ) { + $batchnum = $1; +} else { + die "No batch number (bad URL) \n"; +} + my $format; if ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) { $format = $1; @@ -16,11 +23,12 @@ my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; -my $pay_batch = qsearchs('pay_batch', {'status'=>'O'} ); +my $pay_batch = qsearchs('pay_batch', {'batchnum'=>$batchnum, 'status'=>'O'} ); die "No pending batch. \n" unless $pay_batch; my %batchhash = $pay_batch->hash; $batchhash{'status'} = 'I'; +$batchhash{'download'} = time unless $batchhash{'download'}; my $new = new FS::pay_batch \%batchhash; my $error = $new->replace($pay_batch); die "error updating batch status: $error\n" if $error; @@ -28,8 +36,10 @@ die "error updating batch status: $error\n" if $error; my $batchtotal=0; my $batchcount=0; -my (@date)=localtime(); -my $jdate = sprintf("%03d", $date[5] % 100).sprintf("%03d", $date[7]); +my (@date)=localtime($new->download); +my $jdate = sprintf("%03d", $date[5] % 100).sprintf("%03d", $date[7] + 1); +my $cdate = sprintf("%02d", $date[3]).sprintf("%02d", $date[4] + 1). + sprintf("%02d", $date[5] % 100); if ($format eq "BoM") { @@ -39,6 +49,14 @@ if ($format eq "BoM") { sprintf( "XD%03u%06u%-15s%-30s%09u%-12s \n",$typecode,$jdate,$shortname,$longname,$mybank,$myacct ) %><% +}elsif ($format eq "PAP"){ + + my($origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) = + $conf->config("batchconfig-$format"); + %><%= sprintf( "H%10sD%3s%06u%-15s%09u%-12s%04u%19s\n",$origid,$typecode,$cdate,$shortname,$mybank,$myacct,$pay_batch->batchnum,"") + + %><% + }elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){ # 1; }else{ @@ -61,7 +79,12 @@ for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } if ($format eq "BoM") { my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo ); - %><%= sprintf( "D%010u%09u%-12s%-29s%-19s\n",$cust_pay_batch->amount*100,$aba,$account,$cust_pay_batch->payname,$cust_pay_batch->invnum %><% + %><%= sprintf( "D%010.0f%09u%-12s%-29s%-19s\n",$cust_pay_batch->amount*100,$aba,$account,$cust_pay_batch->payname,$cust_pay_batch->paybatchnum) %><% + + } elsif ($format eq "PAP"){ + + my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo ); + %><%= sprintf( "D%-23s%06u%-19s%09u%-12s%010.0f\n",$cust_pay_batch->payname,$cdate,$cust_pay_batch->paybatchnum,$aba,$account,$cust_pay_batch->amount*100) %><% } elsif ($format eq "csv-td_canada_trust-merchant_pc_batch") { @@ -75,9 +98,13 @@ for my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum } if ($format eq "BoM") { - %><%= sprintf( "YD%08u%014u%56s\n",$batchcount,$batchtotal*100,"" ). + %><%= sprintf( "YD%08u%014.0f%56s\n",$batchcount,$batchtotal*100,"" ). sprintf( "Z%014u%05u%014u%05u%41s\n",$batchtotal*100,$batchcount,"0","0","" ) %><% +} elsif ($format eq "PAP"){ + + %><%= sprintf( "T%08u%014.0f%57s\n",$batchcount,$batchtotal*100,"" ) %><% + } elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){ #1; } else { -- cgit v1.2.1 From cea76425e837641044923ac3622a2d0a35e4cfb2 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 9 Aug 2006 07:03:43 +0000 Subject: this is not my beautiful magic template! (water flowing underground) --- fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi b/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi index bf82a87e8..e353b4489 100644 --- a/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi +++ b/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi @@ -4,15 +4,15 @@ use strict; use CGI; use FS::SelfService qw( invoice_logo ); -$cgi = new CGI; +my $cgi = new CGI; my($query) = $cgi->keywords; $query =~ /^([^\.\/]*)$/ or '' =~ /^()$/; my $templatename = $1; -invoice_logo($templatename); +my $hashref = invoice_logo($templatename); -print $cgi->header( '-type' => $content_type, +print $cgi->header( '-type' => $hashref->{'content_type'}, '-expires' => 'now', ). - $logo; + $hashref->{'logo'}; -- cgit v1.2.1 From 95f4195da730f6d40faee94aa7a3108b82823d8d Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 9 Aug 2006 07:46:09 +0000 Subject: and the days go by... --- fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi b/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi index e353b4489..cade712d1 100644 --- a/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi +++ b/fs_selfservice/FS-SelfService/cgi/cust_bill-logo.cgi @@ -9,7 +9,7 @@ my $cgi = new CGI; my($query) = $cgi->keywords; $query =~ /^([^\.\/]*)$/ or '' =~ /^()$/; my $templatename = $1; -my $hashref = invoice_logo($templatename); +my $hashref = invoice_logo('templatename' => $templatename); print $cgi->header( '-type' => $hashref->{'content_type'}, '-expires' => 'now', -- cgit v1.2.1 From 97316d268e5751a1d08a0a37e5a0456f2ce4815c Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 9 Aug 2006 10:47:18 +0000 Subject: self-service interface: add proper password changer and prevent "Setup my services" provisioner from showing broken links for services not handled yet --- FS/FS/ClientAPI/MyAccount.pm | 84 ++++++++++++++++++++++ FS/FS/svc_acct.pm | 5 ++ fs_selfservice/FS-SelfService/SelfService.pm | 5 +- .../FS-SelfService/cgi/change_password.html | 53 ++++++++++++++ .../FS-SelfService/cgi/myaccount_menu.html | 10 +-- .../cgi/process_change_password.html | 13 ++++ .../FS-SelfService/cgi/provision_list.html | 5 +- fs_selfservice/FS-SelfService/cgi/selfservice.cgi | 38 +++++++++- 8 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 fs_selfservice/FS-SelfService/cgi/change_password.html create mode 100644 fs_selfservice/FS-SelfService/cgi/process_change_password.html diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 163718e13..54b8a9979 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -526,6 +526,51 @@ sub list_pkgs { } +sub list_svcs { + my $p = shift; + + use Data::Dumper; + + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + my $search = { 'custnum' => $custnum }; + $search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; + my $cust_main = qsearchs('cust_main', $search ) + or return { 'error' => "unknown custnum $custnum" }; + + my @cust_svc = (); + #foreach my $cust_pkg ( $cust_main->ncancelled_pkgs ) { + foreach my $cust_pkg ( $cust_main->unsuspended_pkgs ) { + push @cust_svc, @{[ $cust_pkg->cust_svc ]}; #@{[ ]} to force array context + } + @cust_svc = grep { $_->part_svc->svcdb eq $p->{'svcdb'} } @cust_svc + if $p->{'svcdb'}; + + #@svc_x = sort { $a->domain cmp $b->domain || $a->username cmp $b->username } + # @svc_x; + + { + #no#'svcnum' => $session->{'svcnum'}, + 'custnum' => $custnum, + 'svcs' => [ map { + my $svc_x = $_->svc_x; + my($label, $value) = $_->label; + + { 'svcnum' => $_->svcnum, + 'label' => $label, + 'value' => $value, + 'username' => $svc_x->username, + 'email' => $svc_x->email, + # more... + }; + } + @cust_svc + ], + }; + +} + sub order_pkg { my $p = shift; @@ -799,6 +844,45 @@ sub unprovision_svc { } +sub myaccount_passwd { + my $p = shift; + my($context, $session, $custnum) = _custoragent_session_custnum($p); + return { 'error' => $session } if $context eq 'error'; + + return { 'error' => "New passwords don't match." } + if $p->{'new_password'} ne $p->{'new_password2'}; + + return { 'error' => 'Enter new password' } + unless length($p->{'new_password'}); + + #my $search = { 'custnum' => $custnum }; + #$search->{'agentnum'} = $session->{'agentnum'} if $context eq 'agent'; + $custnum =~ /^(\d+)$/ or die "illegal custnum"; + my $search = " AND custnum = $1"; + $search .= " AND agentnum = ". $session->{'agentnum'} if $context eq 'agent'; + + my $svc_acct = qsearchs( { + 'table' => 'svc_acct', + 'addl_from' => 'LEFT JOIN cust_svc USING ( svcnum ) '. + 'LEFT JOIN cust_pkg USING ( pkgnum ) '. + 'LEFT JOIN cust_main USING ( custnum ) ', + 'hashref' => { 'svcnum' => $p->{'svcnum'}, }, + 'extra_sql' => $search, #important + } ) + or return { 'error' => "Service not found" }; + + $svc_acct->_password($p->{'new_password'}); + my $error = $svc_acct->replace(); + + my($label, $value) = $svc_acct->cust_svc->label; + + return { 'error' => $error, + 'label' => $label, + 'value' => $value, + }; + +} + #-- sub _custoragent_session_custnum { diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index c50dfd540..b201f2353 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -484,6 +484,11 @@ sub replace { my $error; warn "$me replacing $old with $new\n" if $DEBUG; + # We absolutely have to have an old vs. new record to make this work. + if (!defined($old)) { + $old = qsearchs( 'svc_acct', { 'svcnum' => $new->svcnum } ); + } + return "can't modify system account" if $old->_check_system; { diff --git a/fs_selfservice/FS-SelfService/SelfService.pm b/fs_selfservice/FS-SelfService/SelfService.pm index 16ca48ec8..c3026fa13 100644 --- a/fs_selfservice/FS-SelfService/SelfService.pm +++ b/fs_selfservice/FS-SelfService/SelfService.pm @@ -33,7 +33,8 @@ $socket .= '.'.$tag if defined $tag && length($tag); 'payment_info' => 'MyAccount/payment_info', 'process_payment' => 'MyAccount/process_payment', 'process_prepay' => 'MyAccount/process_prepay', - 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss cgi! + 'list_pkgs' => 'MyAccount/list_pkgs', #add to ss cgi (added?) + 'list_svcs' => 'MyAccount/list_svcs', #add to ss cgi (added?) 'order_pkg' => 'MyAccount/order_pkg', #add to ss cgi! 'cancel_pkg' => 'MyAccount/cancel_pkg', #add to ss cgi! 'charge' => 'MyAccount/charge', #? @@ -41,6 +42,7 @@ $socket .= '.'.$tag if defined $tag && length($tag); 'provision_acct' => 'MyAccount/provision_acct', 'provision_external' => 'MyAccount/provision_external', 'unprovision_svc' => 'MyAccount/unprovision_svc', + 'myaccount_passwd' => 'MyAccount/myaccount_passwd', 'signup_info' => 'Signup/signup_info', 'new_customer' => 'Signup/new_customer', 'agent_login' => 'Agent/agent_login', @@ -73,6 +75,7 @@ foreach my $autoload ( keys %autoload ) { if ( ref($_[0]) ) { $param = shift; } else { + #warn scalar(@_). ": ". join(" / ", @_); $param = { @_ }; } diff --git a/fs_selfservice/FS-SelfService/cgi/change_password.html b/fs_selfservice/FS-SelfService/cgi/change_password.html new file mode 100644 index 000000000..af7b45313 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/change_password.html @@ -0,0 +1,53 @@ +MyAccount +MyAccount

      +<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> +
  • + +Change password

    + +<%= if ( $error ) { + $OUT .= qq!$error

    !; +} ''; %> + +
    + + + + + + + + + + + + + + + + + + + + +
    Change password for account: + +
    New password:
    Re-enter new password:
    +
    + + + +
    + +
    +
    +powered by freeside + diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html index f2e5e998e..aa22e7c9b 100644 --- a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html +++ b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html @@ -35,9 +35,9 @@ push @menu, ( { title=>' ' }, { title=>'Change my information', size=>'+1', }, - { title=>'Change payment information*', url=>'change_bill', indent=>2 }, - { title=>'Change service address*', url=>'change_ship', indent=>2 }, - { title=>'Change password(s)*', url=>'hmmmFIXME', indent=>2 }, + { title=>'Change payment information*', url=>'change_bill', indent=>2 }, + { title=>'Change service address*', url=>'change_ship', indent=>2 }, + { title=>'Change password(s)', url=>'change_password', indent=>2 }, { title=>' ' }, @@ -82,8 +82,8 @@ foreach my $item ( @menu ) { %> +

    * coming soon + -(tempFIXME) Change password(s)

    -* coming soon diff --git a/fs_selfservice/FS-SelfService/cgi/process_change_password.html b/fs_selfservice/FS-SelfService/cgi/process_change_password.html new file mode 100644 index 000000000..4fdee79f3 --- /dev/null +++ b/fs_selfservice/FS-SelfService/cgi/process_change_password.html @@ -0,0 +1,13 @@ +MyAccount +MyAccount

    +<%= $url = "$selfurl?session=$session_id;action="; ''; %> +<%= include('myaccount_menu') %> + + +Password changed for <%= $value %> <%= $label %>. + + +
    +powered by freeside + + diff --git a/fs_selfservice/FS-SelfService/cgi/provision_list.html b/fs_selfservice/FS-SelfService/cgi/provision_list.html index 0f68dfe3c..cd587f072 100644 --- a/fs_selfservice/FS-SelfService/cgi/provision_list.html +++ b/fs_selfservice/FS-SelfService/cgi/provision_list.html @@ -75,7 +75,10 @@ function areyousure(href, message) { $OUT .= "$td COLSPAN=3 ALIGN=center>". qq!!. 'Setup '. $part_svc->{'svc'}. ' '. '('. $part_svc->{'num_avail'}. ' available)'. - ''; + '' + #self-service only supports these services so far + if grep { $part_svc->{'svcdb'} eq $_ } qw( svc_acct svc_external ); + $col = $col eq $col1 ? $col2 : $col1; } diff --git a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi index 034a684c6..4ab13090f 100644 --- a/fs_selfservice/FS-SelfService/cgi/selfservice.cgi +++ b/fs_selfservice/FS-SelfService/cgi/selfservice.cgi @@ -13,6 +13,7 @@ use FS::SelfService qw( login customer_info invoice list_pkgs part_svc_info provision_acct provision_external unprovision_svc + list_svcs myaccount_passwd ); $template_dir = '.'; @@ -62,7 +63,7 @@ $session_id = $cgi->param('session'); #order|pw_list XXX ??? $cgi->param('action') =~ - /^(myaccount|view_invoice|make_payment|payment_results|recharge_prepay|recharge_results|logout|change_bill|change_ship|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc)$/ + /^(myaccount|view_invoice|make_payment|payment_results|recharge_prepay|recharge_results|logout|change_bill|change_ship|provision|provision_svc|process_svc_acct|process_svc_external|delete_svc|change_password|process_change_password)$/ or die "unknown action ". $cgi->param('action'); my $action = $1; @@ -257,6 +258,41 @@ sub delete_svc { ); } +sub change_password { + list_svcs( + 'session_id' => $session_id, + 'svcdb' => 'svc_acct', + ); +}; + +sub process_change_password { + + my $result = myaccount_passwd( + 'session_id' => $session_id, + map { $_ => $cgi->param($_) } qw( svcnum new_password new_password2 ) + ); + + if ( exists $result->{'error'} && $result->{'error'} ) { + + $action = 'change_password'; + return { + $cgi->Vars, + %{ list_svcs( 'session_id' => $session_id, + 'svcdb' => 'svc_acct', + ) + }, + #'svcnum' => $cgi->param('svcnum'), + 'error' => $result->{'error'} + }; + + } else { + + return $result; + + } + +} + #-- sub do_template { -- cgit v1.2.1 From 9f4696b5f2d5e414580eda21a7d7a5acb4a97160 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 9 Aug 2006 21:46:32 +0000 Subject: take "coming soon" options off the menu, its been Soon for too long - they'll get here when they do --- fs_selfservice/FS-SelfService/cgi/myaccount_menu.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html index aa22e7c9b..6dacc3ef4 100644 --- a/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html +++ b/fs_selfservice/FS-SelfService/cgi/myaccount_menu.html @@ -12,7 +12,7 @@ my @menu = ( { title=>' ' }, { title=>'Purchase', size=>'+1', }, - { title=>'Purchase additional package*', url=>'order', 'indent'=>2 }, +# { title=>'Purchase additional package*', url=>'order', 'indent'=>2 }, ); if ( 1 ) { #XXXFIXME "enable selfservice prepay features" flag or something, eventually per-pkg or something really fancy @@ -35,8 +35,8 @@ push @menu, ( { title=>' ' }, { title=>'Change my information', size=>'+1', }, - { title=>'Change payment information*', url=>'change_bill', indent=>2 }, - { title=>'Change service address*', url=>'change_ship', indent=>2 }, +# { title=>'Change payment information*', url=>'change_bill', indent=>2 }, +# { title=>'Change service address*', url=>'change_ship', indent=>2 }, { title=>'Change password(s)', url=>'change_password', indent=>2 }, { title=>' ' }, @@ -82,7 +82,9 @@ foreach my $item ( @menu ) { %> -

    * coming soon + + +



    -- cgit v1.2.1 From 64aeaf538530faec44db4818e8ecadfd0d1ee3f1 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 10 Aug 2006 02:27:35 +0000 Subject: better debugging for missing recur_fee so its easier to check the db --- FS/FS/part_pkg.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index aa39d890b..9de27f813 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -687,7 +687,8 @@ sub option { my %plandata = map { /^(\w+)=(.*)$/; ( $1 => $2 ); } split("\n", $self->get('plandata') ); return $plandata{$opt} if exists $plandata{$opt}; - cluck "Package definition option $opt not found in options or plandata!\n" + cluck "WARNING: (pkgpart ". $self->pkgpart. ") Package def option $opt". + "not found in options or plandata!\n" unless $ornull; ''; } -- cgit v1.2.1 From 6495ab5fb29dcdb8233f004b5d60d3d97db058b9 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 10 Aug 2006 03:10:00 +0000 Subject: don't set the default to NULL the string! besides, that's already the default value of any nullable column, which @date_type is... --- FS/FS/Schema.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 5dac26600..e7505465e 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -543,8 +543,8 @@ sub tables_hashref { 'columns' => [ 'batchnum', 'serial', '', '', '', '', 'status', 'char', 'NULL', 1, '', '', - 'download', @date_type, 'NULL', '', - 'upload', @date_type, 'NULL', '', + 'download', @date_type, '', '', + 'upload', @date_type, '', '', ], 'primary_key' => 'batchnum', 'unique' => [], -- cgit v1.2.1 From 264ad081e68057c7bc30a8b2ad741b783e1342ff Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 10 Aug 2006 11:55:50 +0000 Subject: agent-virtualize advertising sources --- FS/FS/AccessRight.pm | 5 +- FS/FS/Record.pm | 31 +++++++ FS/FS/Schema.pm | 7 +- FS/FS/access_user.pm | 22 +++++ FS/FS/part_referral.pm | 24 ++++-- httemplate/browse/part_referral.cgi | 97 --------------------- httemplate/browse/part_referral.html | 132 +++++++++++++++++++++++++++++ httemplate/edit/part_referral.cgi | 44 ---------- httemplate/edit/part_referral.html | 9 ++ httemplate/edit/process/part_referral.cgi | 28 ------ httemplate/edit/process/part_referral.html | 5 ++ httemplate/elements/menu.html | 52 +++++++----- 12 files changed, 257 insertions(+), 199 deletions(-) delete mode 100755 httemplate/browse/part_referral.cgi create mode 100755 httemplate/browse/part_referral.html delete mode 100755 httemplate/edit/part_referral.cgi create mode 100755 httemplate/edit/part_referral.html delete mode 100755 httemplate/edit/process/part_referral.cgi create mode 100755 httemplate/edit/process/part_referral.html diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 797a12a4d..888a6865b 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -136,8 +136,11 @@ assigned to users and/or groups. 'Import', # 'Export', # - 'Configuration', #none of the configuraiton is agent-virtualized either + 'Edit advertising sources', + 'Edit global advertising sources', + 'Configuration', #most of the rest of the configuraiton is not + # agent-virtualized ); sub rights { diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 41e0eba51..a551bb8cc 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -10,6 +10,7 @@ use Locale::Country; use DBI qw(:sql_types); use DBIx::DBSchema 0.25; use FS::UID qw(dbh getotaker datasrc driver_name); +use FS::CurrentUser; use FS::Schema qw(dbdef); use FS::SearchCache; use FS::Msgcat qw(gettext); @@ -1620,6 +1621,36 @@ sub ut_foreign_keyn { : ''; } +=item ut_agentnum_acl + +Checks this column as an agentnum, taking into account the current users's +ACLs. + +=cut + +sub ut_agentnum_acl { + my( $self, $field, $null_acl ) = @_; + + my $error = $self->ut_foreign_keyn($field, 'agent', 'agentnum'); + return "Illegal agentnum: $error" if $error; + + my $curuser = $FS::CurrentUser::CurrentUser; + + if ( $self->$field() ) { + + return "Access deined" + unless $curuser->agentnum($self->$field()); + + } else { + + return "Access denied" + unless $curuser->access_right($null_acl); + + } + + ''; + +} =item virtual_fields [ TABLE ] diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index e7505465e..e23ae737d 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -692,9 +692,10 @@ sub tables_hashref { 'part_referral' => { 'columns' => [ - 'refnum', 'serial', '', '', '', '', - 'referral', 'varchar', '', $char_d, '', '', - 'disabled', 'char', 'NULL', 1, '', '', + 'refnum', 'serial', '', '', '', '', + 'referral', 'varchar', '', $char_d, '', '', + 'disabled', 'char', 'NULL', 1, '', '', + 'agentnum', 'int', 'NULL', '', '', '', ], 'primary_key' => 'refnum', 'unique' => [], diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm index 5d1e183e7..f32fa3a23 100644 --- a/FS/FS/access_user.pm +++ b/FS/FS/access_user.pm @@ -106,6 +106,10 @@ sub insert { sub htpasswd_kludge { my $self = shift; + + #awful kludge to skip setting htpasswd for fs_* users + return '' if $self->username =~ /^fs_/; + unshift @_, '-c' unless -e $htpasswd_file; if ( system('htpasswd', '-b', @_, @@ -297,6 +301,24 @@ sub agentnums_sql { ' )'; } +=item agentnum + +Returns true if the user can view the specified agent. + +=cut + +sub agentnum { + my( $self, $agentnum ) = @_; + my $sth = dbh->prepare( + "SELECT COUNT(*) FROM access_usergroup + JOIN access_groupagent USING ( groupnum ) + WHERE usernum = ? AND agentnum = ?" + ) or die dbh->errstr; + $sth->execute($self->usernum, $agentnum) or die $sth->errstr; + $sth->fetchrow_arrayref->[0]; +} + + =item access_right Given a right name, returns true if this user has this right (currently via diff --git a/FS/FS/part_referral.pm b/FS/FS/part_referral.pm index c0858c0ed..b12cd628e 100644 --- a/FS/FS/part_referral.pm +++ b/FS/FS/part_referral.pm @@ -2,7 +2,8 @@ package FS::part_referral; use strict; use vars qw( @ISA ); -use FS::Record; +use FS::Record qw(qsearchs); +use FS::agent; @ISA = qw( FS::Record ); @@ -40,6 +41,8 @@ The following fields are currently supported: =item disabled - Disabled flag, empty or 'Y' +=item agentnum - Optional agentnum (see L) + =back =head1 NOTE @@ -95,17 +98,26 @@ sub check { my $error = $self->ut_numbern('refnum') || $self->ut_text('referral') + || $self->ut_enum('disabled', [ '', 'Y' ] ) + #|| $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum') + || $self->ut_agentnum_acl('agentnum', 'Edit global advertising sources') ; return $error if $error; - if ( $self->dbdef_table->column('disabled') ) { - $error = $self->ut_enum('disabled', [ '', 'Y' ] ); - return $error if $error; - } - $self->SUPER::check; } +=item agent + +Returns the associated agent for this referral, if any, as an FS::agent object. + +=cut + +sub agent { + my $self = shift; + qsearchs('agent', { 'agentnum' => $self->agentnum } ); +} + =back =head1 BUGS diff --git a/httemplate/browse/part_referral.cgi b/httemplate/browse/part_referral.cgi deleted file mode 100755 index 238ddace7..000000000 --- a/httemplate/browse/part_referral.cgi +++ /dev/null @@ -1,97 +0,0 @@ - -<%= include("/elements/header.html","Advertising source Listing", menubar( - 'Main Menu' => $p, -# 'Add new referral' => "../edit/part_referral.cgi", -)) %> -Where a customer heard about your service. Tracked for informational purposes. -

    -Add a new advertising source -

    - -<% - my $today = timelocal(0, 0, 0, (localtime(time))[3..5] ); - my %after; - tie %after, 'Tie::IxHash', - 'Today' => 0, - 'Yesterday' => 86400, # 60sec * 60min * 24hrs - 'Past week' => 518400, # 60sec * 60min * 24hrs * 6days - 'Past 30 days' => 2505600, # 60sec * 60min * 24hrs * 29days - 'Past 60 days' => 5097600, # 60sec * 60min * 24hrs * 59days - 'Past 90 days' => 7689600, # 60sec * 60min * 24hrs * 89days - 'Past 6 months' => 15724800, # 60sec * 60min * 24hrs * 182days - 'Past year' => 31486000, # 60sec * 60min * 24hrs * 364days - 'Total' => $today, - ; - my %before = ( - 'Today' => 86400, # 60sec * 60min * 24hrs - 'Yesterday' => 0, - 'Past week' => 86400, # 60sec * 60min * 24hrs - 'Past 30 days' => 86400, # 60sec * 60min * 24hrs - 'Past 60 days' => 86400, # 60sec * 60min * 24hrs - 'Past 90 days' => 86400, # 60sec * 60min * 24hrs - 'Past 6 months' => 86400, # 60sec * 60min * 24hrs - 'Past year' => 86400, # 60sec * 60min * 24hrs - 'Total' => 86400, # 60sec * 60min * 24hrs - ); - - my $statement = "SELECT COUNT(*) FROM h_cust_main - WHERE history_action = 'insert' - AND refnum = ? - AND history_date >= ? - AND history_date < ? - "; - my $sth = dbh->prepare($statement) - or die dbh->errstr; -%> - -<%= table() %> - - Advertising source - >Customers - -<% for my $period ( keys %after ) { %> - <%= $period %> -<% } %> - - -<% -foreach my $part_referral ( sort { - $a->getfield('refnum') <=> $b->getfield('refnum') -} qsearch('part_referral',{}) ) { -%> - - - <%= $part_referral->refnum %> - - <%= $part_referral->referral %> - <% for my $period ( keys %after ) { - $sth->execute( $part_referral->refnum, - $today-$after{$period}, - $today+$before{$period}, - ) or die $sth->errstr; - my $number = $sth->fetchrow_arrayref->[0]; - %> - <%= $number %> - <% } %> - -<% } %> - -<% - $statement =~ s/AND refnum = \?//; - $sth = dbh->prepare($statement) - or die dbh->errstr; -%> - - Total - <% for my $period ( keys %after ) { - $sth->execute( $today-$after{$period}, - $today+$before{$period}, - ) or die $sth->errstr; - my $number = $sth->fetchrow_arrayref->[0]; - %> - <%= $number %> - <% } %> - - - - diff --git a/httemplate/browse/part_referral.html b/httemplate/browse/part_referral.html new file mode 100755 index 000000000..fff433ae0 --- /dev/null +++ b/httemplate/browse/part_referral.html @@ -0,0 +1,132 @@ +<%= include("/elements/header.html","Advertising source Listing" ) %> + +Where a customer heard about your service. Tracked for informational purposes. +

    + +Add a new advertising source +

    + +<% + my $today = timelocal(0, 0, 0, (localtime(time))[3..5] ); + my %after; + tie %after, 'Tie::IxHash', + 'Today' => 0, + 'Yesterday' => 86400, # 60sec * 60min * 24hrs + 'Past week' => 518400, # 60sec * 60min * 24hrs * 6days + 'Past 30 days' => 2505600, # 60sec * 60min * 24hrs * 29days + 'Past 60 days' => 5097600, # 60sec * 60min * 24hrs * 59days + 'Past 90 days' => 7689600, # 60sec * 60min * 24hrs * 89days + 'Past 6 months' => 15724800, # 60sec * 60min * 24hrs * 182days + 'Past year' => 31486000, # 60sec * 60min * 24hrs * 364days + 'Total' => $today, + ; + my %before = ( + 'Today' => 86400, # 60sec * 60min * 24hrs + 'Yesterday' => 0, + 'Past week' => 86400, # 60sec * 60min * 24hrs + 'Past 30 days' => 86400, # 60sec * 60min * 24hrs + 'Past 60 days' => 86400, # 60sec * 60min * 24hrs + 'Past 90 days' => 86400, # 60sec * 60min * 24hrs + 'Past 6 months' => 86400, # 60sec * 60min * 24hrs + 'Past year' => 86400, # 60sec * 60min * 24hrs + 'Total' => 86400, # 60sec * 60min * 24hrs + ); + + my $curuser = $FS::CurrentUser::CurrentUser; + my $extra_sql = $curuser->agentnums_sql; + $extra_sql .= ' OR agentnum IS NULL ' + if $curuser->access_right('Edit global advertising sources'); + + $extra_sql = " WHERE $extra_sql"; + + my $statement = "SELECT COUNT(*) FROM h_cust_main + WHERE history_action = 'insert' + AND refnum = ? + AND history_date >= ? + AND history_date < ? + AND ". $curuser->agentnums_sql; + my $sth = dbh->prepare($statement) + or die dbh->errstr; + + my $show_agentnums = scalar($curuser->agentnums); + +%> + +<%= include('/elements/table-grid.html') %> + +<% my $bgcolor1 = '#eeeeee'; + my $bgcolor2 = '#ffffff'; + my $bgcolor = ''; +%> + + + Advertising source + <% if ( $show_agentnums ) { %> + Agent + <% } %> + >Customers + +<% for my $period ( keys %after ) { %> + <%= $period %> +<% } %> + + +<% +foreach my $part_referral ( + + qsearch({ + 'table' => 'part_referral', + 'extra_sql' => "$extra_sql ORDER BY refnum", + }) + +) { + + if ( $bgcolor eq $bgcolor1 ) { + $bgcolor = $bgcolor2; + } else { + $bgcolor = $bgcolor1; + } + +%> + + + + <%= $part_referral->refnum %> + + <%= $part_referral->referral %> + + <% if ( $show_agentnums ) { %> + <%= $part_referral->agentnum ? $part_referral->agent->agent : '(global)' %> + <% } %> + + <% for my $period ( keys %after ) { + $sth->execute( $part_referral->refnum, + $today-$after{$period}, + $today+$before{$period}, + ) or die $sth->errstr; + my $number = $sth->fetchrow_arrayref->[0]; + %> + <%= $number %> + <% } %> + +<% } %> + +<% + $statement =~ s/AND refnum = \?//; + $sth = dbh->prepare($statement) + or die dbh->errstr; +%> + + Total + <% for my $period ( keys %after ) { + $sth->execute( $today-$after{$period}, + $today+$before{$period}, + ) or die $sth->errstr; + my $number = $sth->fetchrow_arrayref->[0]; + %> + <%= $number %> + <% } %> + + + + diff --git a/httemplate/edit/part_referral.cgi b/httemplate/edit/part_referral.cgi deleted file mode 100755 index dce1e6394..000000000 --- a/httemplate/edit/part_referral.cgi +++ /dev/null @@ -1,44 +0,0 @@ -<% - -my $part_referral; -if ( $cgi->param('error') ) { - $part_referral = new FS::part_referral ( { - map { $_, scalar($cgi->param($_)) } fields('part_referral') - } ); -} elsif ( $cgi->keywords ) { - my($query) = $cgi->keywords; - $query =~ /^(\d+)$/; - $part_referral = qsearchs( 'part_referral', { 'refnum' => $1 } ); -} else { #adding - $part_referral = new FS::part_referral {}; -} -my $action = $part_referral->refnum ? 'Edit' : 'Add'; -my $hashref = $part_referral->hashref; - -my $p1 = popurl(1); - -%><%= include('/elements/header.html', "$action Advertising source", menubar( - 'Main Menu' => popurl(2), - 'View all advertising sources' => popurl(2). "browse/part_referral.cgi", -)) %> - -<% if ( $cgi->param('error') ) { %> - Error: <%= $cgi->param('error') %> -<% } %> - -
    - - - -<% -#print "Referral #", $hashref->{refnum} ? $hashref->{refnum} : "(NEW)"; -%> - -Advertising source - -
    -"> - -
    - -<%= include('/elements/footer.html') %> diff --git a/httemplate/edit/part_referral.html b/httemplate/edit/part_referral.html new file mode 100755 index 000000000..ec0f32f58 --- /dev/null +++ b/httemplate/edit/part_referral.html @@ -0,0 +1,9 @@ +<%= include( 'elements/edit.html', + 'name' => 'Advertising source', + 'table' => 'part_referral', + 'fields' => [ 'referral' ], + 'labels' => { 'referral' => 'Advertising source' }, + 'viewall_dir' => 'browse', + 'html_table_bottom' => include('/elements/tr-select-agent.html'), + ) +%> diff --git a/httemplate/edit/process/part_referral.cgi b/httemplate/edit/process/part_referral.cgi deleted file mode 100755 index fd2c01506..000000000 --- a/httemplate/edit/process/part_referral.cgi +++ /dev/null @@ -1,28 +0,0 @@ -<% - -my $refnum = $cgi->param('refnum'); - -my $new = new FS::part_referral ( { - map { - $_, scalar($cgi->param($_)); - } fields('part_referral') -} ); - -my $error; -if ( $refnum ) { - my $old = qsearchs( 'part_referral', { 'refnum' =>$ refnum } ); - die "(Old) Record not found!" unless $old; - $error = $new->replace($old); -} else { - $error = $new->insert; -} -$refnum=$new->refnum; - -if ( $error ) { - $cgi->param('error', $error); - print $cgi->redirect(popurl(2). "part_referral.cgi?". $cgi->query_string ); -} else { - print $cgi->redirect(popurl(3). "browse/part_referral.cgi"); -} - -%> diff --git a/httemplate/edit/process/part_referral.html b/httemplate/edit/process/part_referral.html new file mode 100755 index 000000000..0b5d959a0 --- /dev/null +++ b/httemplate/edit/process/part_referral.html @@ -0,0 +1,5 @@ +<%= include( 'elements/process.html', + 'table' => 'part_referral', + 'viewall_dir' => 'browse', + ) +%> diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html index 8c62d9778..a5b41aefd 100644 --- a/httemplate/elements/menu.html +++ b/httemplate/elements/menu.html @@ -189,26 +189,36 @@ 'View/Edit address blocks' => [ $fsurl.'browse/addr_block.cgi', 'Manage address blocks and block assignments to broadband routers' ], ; - tie my %config_misc, 'Tie::IxHash', - 'View/Edit advertising sources' => [ $fsurl.'browse/part_referral.cgi', 'Where a customer heard about your service. Tracked for informational purposes' ], - 'View/Edit virtual fields' => [ $fsurl.'browse/part_virtual_field.cgi', 'Locally defined fields', ], - 'View/Edit message catalog' => [ $fsurl.'browse/msgcat.cgi', 'Change error messages and other customizable labels' ], - 'View/Edit inventory classes and inventory' => [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ], - ; + tie my %config_misc, 'Tie::IxHash'; + $config_misc{'View/Edit advertising sources'} = [ $fsurl.'browse/part_referral.html', 'Where a customer heard about your service. Tracked for informational purposes' ] + if $curuser->access_right('Configuration') + || $curuser->access_right('Edit advertising sources') + || $curuser->access_right('Edit global advertising sources'); + if ( $curuser->access_right('Configuration') ) { + $config_misc{'View/Edit virtual fields'} = [ $fsurl.'browse/part_virtual_field.cgi', 'Locally defined fields', ]; + $config_misc{'View/Edit message catalog'} = [ $fsurl.'browse/msgcat.cgi', 'Change error messages and other customizable labels' ]; + $config_misc{'View/Edit inventory classes and inventory'} = [ $fsurl.'browse/inventory_class.html', 'Setup inventory classes and stock inventory' ]; + } - tie my %config_menu, 'Tie::IxHash', - 'Settings' => [ $fsurl.'config/config-view.cgi', '' ], - 'separator' => '', #its a separator! - 'Employees' => [ \%config_employees, '' ], - 'Provisioning, services and packages' - => [ \%config_export_svc_pkg, '' ], - 'Resellers' => [ \%config_agent, '' ], - 'Billing' => [ \%config_billing, '' ], - 'Dialup' => [ \%config_dialup, '' ], - 'Fixed (username-less) broadband' - => [ \%config_broadband, '' ], - 'Miscellaneous' => [ \%config_misc, '' ], - ; + tie my %config_menu, 'Tie::IxHash'; + if ( $curuser->access_right('Configuration' ) ) { + %config_menu = ( + 'Settings' => [ $fsurl.'config/config-view.cgi', '' ], + 'separator' => '', #its a separator! + 'Employees' => [ \%config_employees, '' ], + 'Provisioning, services and packages' + => [ \%config_export_svc_pkg, '' ], + 'Resellers' => [ \%config_agent, '' ], + 'Billing' => [ \%config_billing, '' ], + 'Dialup' => [ \%config_dialup, '' ], + 'Fixed (username-less) broadband' + => [ \%config_broadband, '' ], + ); + } + $config_menu{'Miscellaneous'} = [ \%config_misc, '' ] + if $curuser->access_right('Configuration') + || $curuser->access_right('Edit advertising sources') + || $curuser->access_right('Edit global advertising sources'); tie my %menu, 'Tie::IxHash', 'Billing Main' => [ $fsurl, 'Billing start page', ], @@ -225,7 +235,9 @@ $menu{'Tools'} = [ \%tools_menu, 'Tools' ] if keys %tools_menu; $menu{'Configuration'} = [ \%config_menu, 'Configuraiton and setup' ] - if $curuser->access_right('Configuration'); + if $curuser->access_right('Configuration') + || $curuser->access_right('Edit advertising sources') + || $curuser->access_right('Edit global advertising sources'); use vars qw($gmenunum); $gmenunum = 0; -- cgit v1.2.1 From 9b38584b79d03f6baa9a55bc623760261305e96c Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 10 Aug 2006 12:01:07 +0000 Subject: bold the total footer --- httemplate/browse/part_referral.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httemplate/browse/part_referral.html b/httemplate/browse/part_referral.html index fff433ae0..6a9f7003b 100755 --- a/httemplate/browse/part_referral.html +++ b/httemplate/browse/part_referral.html @@ -117,14 +117,14 @@ foreach my $part_referral ( or die dbh->errstr; %> - Total + Total <% for my $period ( keys %after ) { $sth->execute( $today-$after{$period}, $today+$before{$period}, ) or die $sth->errstr; my $number = $sth->fetchrow_arrayref->[0]; %> - <%= $number %> + <%= $number %> <% } %> -- cgit v1.2.1 From 0a7d9371d3514312edd8661c621876cece4d5221 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 10 Aug 2006 13:50:45 +0000 Subject: add cust_main.agent_custid (at least to schema and customer view, no manual editing yet) --- FS/FS/Schema.pm | 3 +- FS/FS/cust_main.pm | 1 + FS/FS/part_referral.pm | 61 +++++++++++++++++++++++++++++++++++- README.1.7.0 | 3 ++ httemplate/browse/part_referral.html | 7 +---- httemplate/view/cust_main/misc.html | 15 +++++++-- 6 files changed, 80 insertions(+), 10 deletions(-) diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index e23ae737d..ce2d79067 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -394,6 +394,7 @@ sub tables_hashref { 'columns' => [ 'custnum', 'serial', '', '', '', '', 'agentnum', 'int', '', '', '', '', + 'agent_custid', 'varchar', 'NULL', $char_d, '', '', # 'titlenum', 'int', 'NULL', '', '', '', 'last', 'varchar', '', $char_d, '', '', # 'middle', 'varchar', 'NULL', $char_d, '', '', @@ -443,7 +444,7 @@ sub tables_hashref { 'spool_cdr','char', 'NULL', 1, '', '', ], 'primary_key' => 'custnum', - 'unique' => [], + 'unique' => [ [ 'agentnum', 'agent_custid' ] ], #'index' => [ ['last'], ['company'] ], 'index' => [ ['last'], [ 'company' ], [ 'referral_custnum' ], [ 'daytime' ], [ 'night' ], [ 'fax' ], [ 'refnum' ], diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index f1d969cd1..3f67c62f0 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1060,6 +1060,7 @@ sub check { my $error = $self->ut_numbern('custnum') || $self->ut_number('agentnum') + || $self->ut_textn('agent_custid') || $self->ut_number('refnum') || $self->ut_name('last') || $self->ut_name('first') diff --git a/FS/FS/part_referral.pm b/FS/FS/part_referral.pm index b12cd628e..e1688650d 100644 --- a/FS/FS/part_referral.pm +++ b/FS/FS/part_referral.pm @@ -2,7 +2,7 @@ package FS::part_referral; use strict; use vars qw( @ISA ); -use FS::Record qw(qsearchs); +use FS::Record qw( qsearch qsearchs dbh ); use FS::agent; @ISA = qw( FS::Record ); @@ -120,6 +120,65 @@ sub agent { =back +=head1 CLASS METHODS + +=over 4 + +=item acl_agentnum_sql + +Returns an SQL fragment for searching for part_referral records allowed by the +current users's agent ACLs (and "Edit global advertising sources" right). + +=cut + +sub acl_agentnum_sql { + #my $class = shift; + + my $curuser = $FS::CurrentUser::CurrentUser; + my $sql = $curuser->agentnums_sql; + $sql = " ( $sql OR agentnum IS NULL ) " + if $curuser->access_right('Edit global advertising sources'); + + $sql; + +} + +=item all_part_referral + +Returns all part_referral records allowed by the current users's agent ACLs +(and "Edit global advertising sources" right). + +=cut + +sub all_part_referral { + my $self = shift; + + qsearch({ + 'table' => 'part_referral', + 'extra_sql' => ' WHERE '. $self->acl_agentnum_sql. ' ORDER BY refnum ', + }); + +} + +=item num_part_referral + +Returns the number of part_referral records allowed by the current users's +agent ACLs (and "Edit global advertising sources" right). + +=cut + +sub num_part_referral { + my $self = shift; + + my $sth = dbh->prepare( + 'SELECT COUNT(*) FROM part_referral WHERE '. $self->acl_agentnum_sql + ) or die dbh->errstr; + $sth->execute() or die $sth->errstr; + $sth->fetchrow_arrayref->[0]; +} + +=back + =head1 BUGS The delete method is unimplemented. diff --git a/README.1.7.0 b/README.1.7.0 index c82c8ada9..24b89c90d 100644 --- a/README.1.7.0 +++ b/README.1.7.0 @@ -34,6 +34,9 @@ Optional for better zip code report performance: CREATE INDEX cust_main16 on cust_main ( zip ); CREATE INDEX cust_main17 on cust_main ( ship_zip ); +Optional if you're using the new agent cust ref#s: +CREATE UNIQUE INDEX cust_main18 ON cust_main ( agentnum, agent_custid ); + Optional to eliminate harmless but noisy warnings: UPDATE cust_main_county SET exempt_amount = 0 WHERE exempt_amount IS NULL; diff --git a/httemplate/browse/part_referral.html b/httemplate/browse/part_referral.html index 6a9f7003b..468593160 100755 --- a/httemplate/browse/part_referral.html +++ b/httemplate/browse/part_referral.html @@ -32,12 +32,7 @@ Where a customer heard about your service. Tracked for informational purposes. 'Total' => 86400, # 60sec * 60min * 24hrs ); - my $curuser = $FS::CurrentUser::CurrentUser; - my $extra_sql = $curuser->agentnums_sql; - $extra_sql .= ' OR agentnum IS NULL ' - if $curuser->access_right('Edit global advertising sources'); - - $extra_sql = " WHERE $extra_sql"; + $extra_sql = " WHERE ". FS::part_referral->acl_agentnum_sql; my $statement = "SELECT COUNT(*) FROM h_cust_main WHERE history_action = 'insert' diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html index 69e120573..742b35958 100644 --- a/httemplate/view/cust_main/misc.html +++ b/httemplate/view/cust_main/misc.html @@ -1,5 +1,6 @@ <% my( $cust_main ) = @_; + my $conf = new FS::Conf; %> <%= ntable("#cccccc") %><%= &ntable("#cccccc",2) %> @@ -25,8 +26,18 @@ $agent = $agents[0]; } - my @referrals = qsearch( 'part_referral', {} ); - unless ( scalar(@referrals) == 1 ) { + if ( $cust_main->agent_custid ) { +%> + + + Agent customer ref# + <%= $cust_main->agent_custid %> + + +<% + } + + unless ( FS::part_referral->num_part_referral == 1 ) { my $referral = qsearchs('part_referral', { 'refnum' => $cust_main->refnum } ); -- cgit v1.2.1 From dbc120189697e8306f62349c310f5410f8382491 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 10 Aug 2006 22:18:45 +0000 Subject: bugfix for selects that don't have select_enum --- httemplate/config/config.cgi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httemplate/config/config.cgi b/httemplate/config/config.cgi index 6008c0e42..cf228dba5 100644 --- a/httemplate/config/config.cgi +++ b/httemplate/config/config.cgi @@ -102,7 +102,7 @@ function SafeOnsubmit() { <% my $curvalue = $conf->config($i->key); if ( $conf->exists($i->key) && $curvalue - && ! grep { $curvalue eq $_ } @{$i->select_enum} + && ! $hash{$curvalue} ) { %> -- cgit v1.2.1 From e47e9758f480c664bfc3917d798cd69c7d354999 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 11 Aug 2006 08:02:26 +0000 Subject: virtualize referrals on customer addition --- FS/FS/access_user.pm | 16 +++++ FS/FS/part_referral.pm | 21 ++++--- httemplate/browse/part_referral.html | 29 +++++---- httemplate/edit/cust_main.cgi | 80 +++++++++--------------- httemplate/elements/select-agent.html | 8 +-- httemplate/elements/select-part_referral.html | 17 +++++ httemplate/elements/tr-select-agent.html | 10 ++- httemplate/elements/tr-select-part_referral.html | 30 +++++++++ 8 files changed, 132 insertions(+), 79 deletions(-) create mode 100644 httemplate/elements/select-part_referral.html create mode 100644 httemplate/elements/tr-select-part_referral.html diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm index f32fa3a23..830d7f826 100644 --- a/FS/FS/access_user.pm +++ b/FS/FS/access_user.pm @@ -5,6 +5,7 @@ use vars qw( @ISA $htpasswd_file ); use FS::Record qw( qsearch qsearchs dbh ); use FS::m2m_Common; use FS::access_usergroup; +use FS::agent; @ISA = qw( FS::m2m_Common FS::Record ); @@ -318,6 +319,21 @@ sub agentnum { $sth->fetchrow_arrayref->[0]; } +=item agents + +Returns the list of agents this user can view (via group membership), as +FS::agent objects. + +=cut + +sub agents { + my $self = shift; + qsearch({ + 'table' => 'agent', + 'hashref' => { disabled=>'' }, + 'extra_sql' => ' AND '. $self->agentnums_sql, + }); +} =item access_right diff --git a/FS/FS/part_referral.pm b/FS/FS/part_referral.pm index e1688650d..87bc87cba 100644 --- a/FS/FS/part_referral.pm +++ b/FS/FS/part_referral.pm @@ -124,30 +124,37 @@ sub agent { =over 4 -=item acl_agentnum_sql +=item acl_agentnum_sql [ INCLUDE_GLOBAL_BOOL ] Returns an SQL fragment for searching for part_referral records allowed by the current users's agent ACLs (and "Edit global advertising sources" right). +Pass a true value to include global advertising sources (for example, when +simply using rather than editing advertising sources). + =cut sub acl_agentnum_sql { - #my $class = shift; + my $self = shift; my $curuser = $FS::CurrentUser::CurrentUser; my $sql = $curuser->agentnums_sql; $sql = " ( $sql OR agentnum IS NULL ) " - if $curuser->access_right('Edit global advertising sources'); + if $curuser->access_right('Edit global advertising sources') + or defined($_[0]) && $_[0]; $sql; } -=item all_part_referral +=item all_part_referral [ INCLUDE_GLOBAL_BOOL ] Returns all part_referral records allowed by the current users's agent ACLs (and "Edit global advertising sources" right). +Pass a true value to include global advertising sources (for example, when +simply using rather than editing advertising sources). + =cut sub all_part_referral { @@ -155,12 +162,12 @@ sub all_part_referral { qsearch({ 'table' => 'part_referral', - 'extra_sql' => ' WHERE '. $self->acl_agentnum_sql. ' ORDER BY refnum ', + 'extra_sql' => ' WHERE '. $self->acl_agentnum_sql(@_). ' ORDER BY refnum ', }); } -=item num_part_referral +=item num_part_referral [ INCLUDE_GLOBAL_BOOL ] Returns the number of part_referral records allowed by the current users's agent ACLs (and "Edit global advertising sources" right). @@ -171,7 +178,7 @@ sub num_part_referral { my $self = shift; my $sth = dbh->prepare( - 'SELECT COUNT(*) FROM part_referral WHERE '. $self->acl_agentnum_sql + 'SELECT COUNT(*) FROM part_referral WHERE '. $self->acl_agentnum_sql(@_) ) or die dbh->errstr; $sth->execute() or die $sth->errstr; $sth->fetchrow_arrayref->[0]; diff --git a/httemplate/browse/part_referral.html b/httemplate/browse/part_referral.html index 468593160..c50a406ed 100755 --- a/httemplate/browse/part_referral.html +++ b/httemplate/browse/part_referral.html @@ -32,7 +32,7 @@ Where a customer heard about your service. Tracked for informational purposes. 'Total' => 86400, # 60sec * 60min * 24hrs ); - $extra_sql = " WHERE ". FS::part_referral->acl_agentnum_sql; + my $curuser = $FS::CurrentUser::CurrentUser; my $statement = "SELECT COUNT(*) FROM h_cust_main WHERE history_action = 'insert' @@ -67,14 +67,7 @@ Where a customer heard about your service. Tracked for informational purposes. <% -foreach my $part_referral ( - - qsearch({ - 'table' => 'part_referral', - 'extra_sql' => "$extra_sql ORDER BY refnum", - }) - -) { +foreach my $part_referral ( FS::part_referral->all_part_referral(1) ) { if ( $bgcolor eq $bgcolor1 ) { $bgcolor = $bgcolor2; @@ -82,13 +75,23 @@ foreach my $part_referral ( $bgcolor = $bgcolor1; } + $a = 0; + %> - - <%= $part_referral->refnum %> - - <%= $part_referral->referral %> + + <% if ( $part_referral->agentnum || $curuser->access_right('Edit global advertising sources') ) { + $a++; + %> + + <% } %> + <%= $part_referral->refnum %><%= $a ? '' : '' %> + + <% if ( $a ) { %> + + <% } %> + <%= $part_referral->referral %><%= $a ? '' : '' %> <% if ( $show_agentnums ) { %> <%= $part_referral->agentnum ? $part_referral->agent->agent : '(global)' %> diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index bb2a8618e..45cb69fc2 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -66,6 +66,8 @@ if ( $cgi->param('error') ) { $cgi->delete_all(); my $action = $custnum ? 'Edit' : 'Add'; +my $r = qq!* !; + %> @@ -77,38 +79,29 @@ my $action = $custnum ? 'Edit' : 'Add'; ) %> <% if ( $error ) { %> -Error: <%= $error %> +Error: <%= $error %>

    <% } %>
    -Customer # <%= $custnum ? "$custnum" : " (NEW)" %> - - - -<% +<% if ( $custnum ) { %> + Customer #<%= $custnum %> - + + <%= ucfirst($cust_main->status) %> + +

    +<% } %> -my $r = qq!* !; +<%= &ntable("#cccccc") %> -my %agent_search = dbdef->table('agent')->column('disabled') - ? ( 'disabled' => '' ) : (); -my @agents = qsearch( 'agent', \%agent_search ); -#die "No agents created!" unless @agents; -eidiot "You have not created any agents (or all agents are disabled). You must create at least one agent before adding a customer. Go to ". popurl(2). "browse/agent.cgi and create one or more agents." unless @agents; -my $agentnum = $cust_main->agentnum || $agents[0]->agentnum; #default to first + +<%= include('/elements/tr-select-agent.html', $cust_main->agentnum, + 'label' => "${r}Agent", + 'empty_label' => 'Select agent', + ) %> -<% if ( scalar(@agents) == 1 ) { %> - -<% } else { %> -

    <%=$r%>Agent -<% } %> - <% @@ -118,28 +111,9 @@ if ( $custnum && ! $conf->exists('editreferrals') ) { -<% - } else { - - my(@referrals) = qsearch('part_referral',{}); - if ( scalar(@referrals) == 0 ) { - eidiot "You have not created any advertising sources. You must create at least one advertising source before adding a customer. Go to ". popurl(2). "browse/part_referral.cgi and create one or more advertising sources."; - } elsif ( scalar(@referrals) == 1 ) { - $refnum ||= $referrals[0]->refnum; -%> - - - <% } else { %> -

    <%=$r%>Advertising source - -<% } %> + <%= include('/elements/tr-select-part_referral.html') %> <% } %> @@ -153,14 +127,20 @@ if ( $cust_main->referral_custnum ) { %> -

    Referring Customer: - <%= $cust_main->referral_custnum %>: <%= $referring_cust_main->name %> + + Referring customer + + <%= $cust_main->referral_custnum %>: <%= $referring_cust_main->name %> + + <% } elsif ( ! $conf->exists('disable_customer_referrals') ) { %> -

    Referring customer number: - + + Referring customer + + <% } else { %> @@ -168,6 +148,8 @@ if ( $cust_main->referral_custnum <% } %> + +

    @@ -377,10 +359,10 @@ unless ( $custnum ) { #false laziness, copied from FS::cust_pkg::order my $pkgpart; + my @agents = $FS::CurrentUser::CurrentUser->agents; if ( scalar(@agents) == 1 ) { # $pkgpart->{PKGPART} is true iff $custnum may purchase PKGPART - my($agent)=qsearchs('agent',{'agentnum'=> $agentnum }); - $pkgpart = $agent->pkgpart_hashref; + $pkgpart = $agents[0]->pkgpart_hashref; } else { #can't know (agent not chosen), so, allow all my %typenum; diff --git a/httemplate/elements/select-agent.html b/httemplate/elements/select-agent.html index 78ec25f82..009cc6e06 100644 --- a/httemplate/elements/select-agent.html +++ b/httemplate/elements/select-agent.html @@ -1,8 +1,7 @@ <% my( $agentnum, %opt ) = @_; - my %select_opt = (); - $select_opt{'records'} = $opt{'agents'} + $opt{'records'} = delete $opt{'agents'} if $opt{'agents'}; %><%= include( '/elements/select-table.html', @@ -12,7 +11,8 @@ 'empty_label' => 'all', 'hashref' => { 'disabled' => '' }, 'extra_sql' => ' AND '. - $FS::CurrentUser::CurrentUser->agentnums_sql, - %select_opt, + $FS::CurrentUser::CurrentUser->agentnums_sql. + ' ORDER BY agent', + %opt, ) %> diff --git a/httemplate/elements/select-part_referral.html b/httemplate/elements/select-part_referral.html new file mode 100644 index 000000000..deb87bd3b --- /dev/null +++ b/httemplate/elements/select-part_referral.html @@ -0,0 +1,17 @@ +<% + my( $refnum, %opt ) = @_; + + $opt{'records'} = delete $opt{'part_referrals'} + if $opt{'part_referrals'}; + +%><%= include( '/elements/select-table.html', + 'table' => 'part_referral', + 'name_col' => 'referral', + 'value' => $refnum, + 'empty_label' => 'Select advertising source', + 'hashref' => { 'disabled' => '' }, + 'extra_sql' => ' AND '. + FS::part_referral->all_part_referral(1), + %opt, + ) +%> diff --git a/httemplate/elements/tr-select-agent.html b/httemplate/elements/tr-select-agent.html index 83c8d1758..6158f6f47 100644 --- a/httemplate/elements/tr-select-agent.html +++ b/httemplate/elements/tr-select-agent.html @@ -7,12 +7,9 @@ #here is the agent virtualization my $agentnums_href = $FS::CurrentUser::CurrentUser->agentnums_href; @agents = grep $agentnums_href->{$_->agentnum}, @{ $opt{'agents'} }; + delete $opt{'agents'}; } else { - @agents = qsearch( { - 'table' => 'agent', - 'hashref' => { disabled=>'' }, - 'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql, - }); + @agents = $FS::CurrentUser::CurrentUser->agents; } %> @@ -24,10 +21,11 @@ <% } else { %> - <%= $opt{'label'} || 'Agent: ' %> + <%= $opt{'label'} || 'Agent' %> <%= include( '/elements/select-agent.html', $agentnum, 'agents' => \@agents, + %opt, ) %> diff --git a/httemplate/elements/tr-select-part_referral.html b/httemplate/elements/tr-select-part_referral.html new file mode 100644 index 000000000..0108388bc --- /dev/null +++ b/httemplate/elements/tr-select-part_referral.html @@ -0,0 +1,30 @@ +<% + my( $refnum, %opt ) = @_; + + $opt{'part_referrals'} ||= + [ FS::part_referral->all_part_referral( 1 ) ]; #1: include global + + my $r = qq!* !; + +%> + +<% if ( scalar( @{$opt{'part_referrals'}} ) == 0 ) { + eidiot "You have not created any advertising sources. You must create at least one advertising source before adding a customer. Go to ". popurl(2). "browse/part_referral.html and create one or more advertising sources."; + } elsif ( scalar( @{$opt{'part_referrals'}} ) == 1 ) { +%> + + + +<% } else { %> + + + <%=$r%>Advertising source + + <%= include( '/elements/select-part_referral.html', $refnum, + 'part_referrals' => $opt{'part_referrals'}, + ) + %> + + + +<% } %> -- cgit v1.2.1 From eddbfe83c7b701ef02ce346b169fc44eae4f6e97 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 12 Aug 2006 06:45:14 +0000 Subject: don't adjust next bill date on unsuspension! causes undesirable effects with prorate/subscription packages and undesirably rewards customers for non-payment, closes: Bug#1325 --- FS/FS/cust_pkg.pm | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 45128e966..4976a2d50 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -556,18 +556,27 @@ sub suspend { ''; #no errors } -=item unsuspend +=item unsuspend [ OPTION => VALUE ... ] Unsuspends all services (see L and L) in this package, then unsuspends the package itself (clears the susp field). +Available options are: I. + +I can be set true to adjust the next bill date forward by +the amount of time the account was inactive. This was set true by default +since 1.4.2 and 1.5.0pre6; however, starting with 1.7.0 this needs to be +explicitly requested. Price plans for which this makes sense (anniversary-date +based than prorate or subscription) could have an option to enable this +behaviour? + If there is an error, returns the error, otherwise returns false. =cut sub unsuspend { - my $self = shift; - my($error); + my( $self, %opt ) = @_; + my $error; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -606,9 +615,12 @@ sub unsuspend { unless ( ! $self->getfield('susp') ) { my %hash = $self->hash; my $inactive = time - $hash{'susp'}; - $hash{'susp'} = ''; + $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive - if $inactive > 0 && ( $hash{'bill'} || $hash{'setup'} ); + if $opt{'adjust_next_bill'} + && $inactive > 0 && ( $hash{'bill'} || $hash{'setup'} ); + + $hash{'susp'} = ''; my $new = new FS::cust_pkg ( \%hash ); $error = $new->replace($self); if ( $error ) { -- cgit v1.2.1 From b8ec621c59747a1c2fcd7fbd603d0c6de41e7df5 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 12 Aug 2006 10:47:51 +0000 Subject: fix acl rewrite causing problems: void now shows up properly, deprecate all the redundant config values --- FS/FS/AccessRight.pm | 1 + FS/FS/Conf.pm | 28 ++++----- httemplate/view/cust_main/payment_history.html | 86 +++++++++++++++++--------- 3 files changed, 71 insertions(+), 44 deletions(-) diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 888a6865b..d03b79acd 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -120,6 +120,7 @@ assigned to users and/or groups. 'Credit card void', #aka. cc-void #Enable local-only voiding of echeck payments in addition to refunds against the payment gateway 'Echeck void', #aka. echeck-void #Enable local-only voiding of echeck payments in addition to refunds against the payment gateway + 'Regular void', 'Unvoid', #aka. unvoid #Enable unvoiding of voided payments 'List customers', diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 8b3cd1aa6..5b77234a6 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -435,29 +435,29 @@ httemplate/docs/config.html { 'key' => 'deletepayments', - 'section' => 'UI', - 'description' => 'Enable deletion of unclosed payments. Be very careful! Only delete payments that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a payment is deleted.', + 'section' => 'billing', + 'description' => 'Enable deletion of unclosed payments. Really, with voids this is pretty much not recommended in any situation anymore. Be very careful! Only delete payments that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a payment is deleted.', 'type' => [qw( checkbox text )], }, { 'key' => 'deletecredits', - 'section' => 'UI', - 'description' => 'Enable deletion of unclosed credits. Be very careful! Only delete credits that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a credit is deleted.', + 'section' => 'deprecated', + 'description' => 'DEPRECATED, now controlled by ACLs. Used to enable deletion of unclosed credits. Be very careful! Only delete credits that were data-entry errors, not adjustments. Optionally specify one or more comma-separated email addresses to be notified when a credit is deleted.', 'type' => [qw( checkbox text )], }, { 'key' => 'unapplypayments', - 'section' => 'UI', - 'description' => 'Enable "unapplication" of unclosed payments.', + 'section' => 'deprecated', + 'description' => 'DEPRECATED, now controlled by ACLs. Used to enable "unapplication" of unclosed payments.', 'type' => 'checkbox', }, { 'key' => 'unapplycredits', - 'section' => 'UI', - 'description' => 'Enable "unapplication" of unclosed credits.', + 'section' => 'deprecated', + 'description' => 'DEPRECATED, now controlled by ACLs. Used to nable "unapplication" of unclosed credits.', 'type' => 'checkbox', }, @@ -1557,22 +1557,22 @@ httemplate/docs/config.html { 'key' => 'echeck-void', - 'section' => 'billing', - 'description' => 'Enable local-only voiding of echeck payments in addition to refunds against the payment gateway', + 'section' => 'deprecated', + 'description' => 'DEPRECATED, now controlled by ACLs. Used to enable local-only voiding of echeck payments in addition to refunds against the payment gateway', 'type' => 'checkbox', }, { 'key' => 'cc-void', - 'section' => 'billing', - 'description' => 'Enable local-only voiding of credit card payments in addition to refunds against the payment gateway', + 'section' => 'deprecated', + 'description' => 'DEPRECATED, now controlled by ACLs. Used to enable local-only voiding of credit card payments in addition to refunds against the payment gateway', 'type' => 'checkbox', }, { 'key' => 'unvoid', - 'section' => 'billing', - 'description' => 'Enable unvoiding of voided payments', + 'section' => 'deprecated', + 'description' => 'DEPRECATED, now controlled by ACLs. Used to enable unvoiding of voided payments', 'type' => 'checkbox', }, diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html index 5c2aa24c9..482136fa1 100644 --- a/httemplate/view/cust_main/payment_history.html +++ b/httemplate/view/cust_main/payment_history.html @@ -135,7 +135,7 @@ foreach my $cust_pay ($cust_main->cust_pay) { $post = '
    '; $apply = qq! (apply)!; + qq!', 392, 336, 'cust_bill_pay_popup' ), CAPTION, 'Apply payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply)!; } elsif ( scalar(@cust_bill_pay) == 1 && scalar(@cust_pay_refund) == 0 @@ -161,7 +161,7 @@ foreach my $cust_pay ($cust_main->cust_pay) { } elsif ( $app->isa('FS::cust_pay_refund') ) { $desc .= '  '. '$'. $app->amount. - ' refunded on'. time2str("%D", $app->_date). + ' refunded on '. time2str("%D", $app->_date). '
    '; } else { die "$app is not a FS::cust_bill_pay or FS::cust_pay_refund"; @@ -173,7 +173,7 @@ foreach my $cust_pay ($cust_main->cust_pay) { $cust_pay->unapplied. ' unapplied
    '. qq! (apply)!. + qq!', 392, 336, 'cust_bill_pay_popup' ), CAPTION, 'Apply payment', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply)!. '
    '; } } @@ -195,13 +195,14 @@ foreach my $cust_pay ($cust_main->cust_pay) { my $void = ''; if ( $cust_pay->closed !~ /^Y/i && ( ( $cust_pay->payby eq 'CARD' - && $conf->exists('cc-void') - && $curuser->acccess_right('Credit card void') + && $curuser->access_right('Credit card void') ) || ( $cust_pay->payby eq 'CHEK' - && $conf->exists('echeck-void') - && $curuser->acccess_right('Echeck void') - ) + && $curuser->access_right('Echeck void') + ) + || ( $cust_pay->payby !~ /^(CARD|CHEK)$/ + && $curuser->access_right('Regular void') + ) ) ) { @@ -231,7 +232,6 @@ foreach my $cust_pay ($cust_main->cust_pay) { my $unapply = ''; if ( $cust_pay->closed !~ /^Y/i - && $conf->exists('unapplypayments') && scalar(@cust_bill_pay) && $curuser->access_right('Unapply payment') ) @@ -268,7 +268,6 @@ foreach my $cust_pay_void ($cust_main->cust_pay_void) { my $unvoid = ''; if ( $cust_pay_void->closed !~ /^Y/i - && $conf->exists('unvoid') && $curuser->access_right('Unvoid') ) { @@ -307,7 +306,7 @@ foreach my $cust_credit ($cust_main->cust_credit) { $post = ''; $apply = qq! (apply)!; + qq!', 392, 336, 'cust_credit_bill_popup' ), CAPTION, 'Apply credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply)!; } elsif ( scalar(@cust_credit_bill) == 1 && scalar(@cust_credit_refund) == 0 && $cust_credit->credited == 0 ) { @@ -332,7 +331,7 @@ foreach my $cust_credit ($cust_main->cust_credit) { } elsif ( $app->isa('FS::cust_credit_refund') ) { $desc .= '  '. '$'. $app->amount. - ' refunded on'. time2str("%D", $app->_date). + ' refunded on '. time2str("%D", $app->_date). '
    '; } else { die "$app is not a FS::cust_credit_bill or a FS::cust_credit_refund"; @@ -343,14 +342,18 @@ foreach my $cust_credit ($cust_main->cust_credit) { $cust_credit->credited. ' unapplied'. qq! (apply)!. + qq!', 392, 336, 'cust_credit_bill_popup' ), CAPTION, 'Apply credit', STICKY, AUTOSTATUSCAP, MIDX, 0, MIDY, 0, DRAGGABLE, CLOSECLICK ); return false;">apply)!. '
    '; } } # my $delete = ''; if ( $cust_credit->closed !~ /^Y/i - && $conf->exists('deletecredits') + + #s'pose deleting a credit isn't bad like deleting a payment + # and this needs to be generally available until we have credit voiding.. + #&& $conf->exists('deletecredits') + && $curuser->access_right('Delete credit') ) { @@ -362,7 +365,6 @@ foreach my $cust_credit ($cust_main->cust_credit) { my $unapply = ''; if ( $cust_credit->closed !~ /^Y/i - && $conf->exists('unapplycredits') && scalar(@cust_credit_bill) && $curuser->access_right('Unapply credit') ) @@ -408,15 +410,21 @@ foreach my $cust_refund ($cust_main->cust_refund) { %> -<%= include("/elements/table.html") %> +<%= include("/elements/table-grid.html") %> + +<% my $bgcolor1 = '#eeeeee'; + my $bgcolor2 = '#ffffff'; + my $bgcolor = ''; +%> + - Date - Description - Charge - Payment - In-house
    Credit
    - Refund - Balance + Date + Description + Charge + Payment + In-house
    Credit
    + Refund + Balance <% @@ -426,6 +434,12 @@ my %target; my $balance = 0; foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) { + if ( $bgcolor eq $bgcolor1 ) { + $bgcolor = $bgcolor2; + } else { + $bgcolor = $bgcolor1; + } + my $charge = exists($item->{'charge'}) ? sprintf('$%.2f', $item->{'charge'}) : ''; @@ -454,7 +468,7 @@ foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) { %> - + <% unless ( !$target || $target{$target}++ ) { %> <% } %> @@ -464,12 +478,24 @@ foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) { <% } %> - <%= $item->{'desc'} %> - <%= $charge %> - <%= $payment %> - <%= $credit %> - <%= $refund %> - <%= $showbalance %> + + <%= $item->{'desc'} %> + + + <%= $charge %> + + + <%= $payment %> + + + <%= $credit %> + + + <%= $refund %> + + + <%= $showbalance %> + <% } %> -- cgit v1.2.1 From a2bddcaabfdaf11d4b726b21af7f6ffc13c458d3 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 12 Aug 2006 11:01:27 +0000 Subject: s/Post/Enter/; --- httemplate/edit/cust_credit.cgi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httemplate/edit/cust_credit.cgi b/httemplate/edit/cust_credit.cgi index d19f0e4b3..8de627d20 100755 --- a/httemplate/edit/cust_credit.cgi +++ b/httemplate/edit/cust_credit.cgi @@ -24,7 +24,7 @@ my $otaker = getotaker; my $p1 = popurl(1); -%><%= include('/elements/header-popup.html', 'Post Credit') %> +%><%= include('/elements/header-popup.html', 'Enter Credit') %> <% if ( $cgi->param('error') ) { %> Error: <%= $cgi->param('error') %> @@ -72,7 +72,7 @@ Credit
    -
    +
    -- cgit v1.2.1 From 8e3dfb380406e145494a5fffa7a0e4aab7b38253 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 13 Aug 2006 10:25:58 +0000 Subject: customer view work: DONE 1. add status and balance to top DONE 2. add some sort of oldest date thing so the history doesn't get too big (# years and a link to "show older") 3. make the rest of the action links into js popups? maybe later, weird IENess when closing em DONE (finished) - so revert out or finish/commit the Enter check payment one - Process page can wait until another day.. it should be more of an *action* DONE 4. Ticket list config knobs for wtxs (grid it too) DONE 5. grid the package list --- FS/FS/Conf.pm | 22 ++++++ FS/FS/TicketSystem/RT_External.pm | 46 ++++++++---- httemplate/edit/cust_pay.cgi | 20 +++-- httemplate/edit/process/cust_pay.cgi | 30 +++++--- httemplate/view/cust_main.cgi | 19 +---- httemplate/view/cust_main/billing.html | 14 +++- httemplate/view/cust_main/misc.html | 15 +++- httemplate/view/cust_main/packages.html | 100 +++++++++++++------------ httemplate/view/cust_main/payment_history.html | 92 +++++++++++++++++++---- httemplate/view/cust_main/tickets.html | 46 +++++++++--- 10 files changed, 278 insertions(+), 126 deletions(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 5b77234a6..68ca49d0b 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1716,6 +1716,28 @@ httemplate/docs/config.html 'type' => 'textarea', }, + { + 'key' => 'payment_history-years', + 'section' => 'UI', + 'description' => 'Number of years of payment history to show by default. Currently defaults to 2.', + 'type' => 'text', + }, + + { + 'key' => 'cust_main-ticket_statuses', + 'section' => 'UI', + 'description' => 'Show tickets with these statuses on the customer view page.', + 'type' => 'selectmultiple', + 'select_enum' => [qw( new open stalled resolved rejected deleted )], + }, + + { + 'key' => 'cust_main-max_tickets', + 'section' => 'UI', + 'description' => 'Maximum number of tickets to show on the customer view page.', + 'type' => 'text', + }, + ); 1; diff --git a/FS/FS/TicketSystem/RT_External.pm b/FS/FS/TicketSystem/RT_External.pm index ea3448039..7dde86228 100644 --- a/FS/FS/TicketSystem/RT_External.pm +++ b/FS/FS/TicketSystem/RT_External.pm @@ -10,7 +10,7 @@ use FS::Record qw(qsearchs); use FS::cust_main; FS::UID->install_callback( sub { - my $conf = new FS::Conf; + $conf = new FS::Conf; $default_queueid = $conf->config('ticket_system-default_queueid'); $priority_field = $conf->config('ticket_system-custom_priority_field'); @@ -57,7 +57,7 @@ sub customer_tickets { my( $from_sql, @param) = $self->_from_customer( $custnum, $priority ); my $sql = "SELECT tickets.*, queues.name". ( length($priority) ? ", objectcustomfieldvalues.content" : '' ). - " $from_sql ORDER BY priority DESC LIMIT $limit"; + " $from_sql ORDER BY priority, id DESC LIMIT $limit"; my $sth = $dbh->prepare($sql) or die $dbh->errstr. "preparing $sql"; $sth->execute(@param) or die $sth->errstr. "executing $sql"; @@ -131,7 +131,7 @@ sub _from_customer { JOIN queues ON ( tickets.queue = queues.id ) JOIN links ON ( tickets.id = links.localbase ) $join - WHERE ( status = 'new' OR status = 'open' OR status = 'stalled' ) + WHERE ( ". join(' OR ', map "status = '$_'", $self->statuses ). " ) AND target = 'freeside://freeside/cust_main/$custnum' $where "; @@ -140,31 +140,44 @@ sub _from_customer { } +sub statuses { + #my $self = shift; + my @statuses = grep { ! /^\s*$/ } $conf->config('cust_main-ticket_statuses'); + @statuses = (qw( new open stalled )) unless scalar(@statuses); + @statuses; +} + sub href_customer_tickets { my( $self, $custnum, $priority ) = @_; - my $href = $self->baseurl; + #my $href = $self->baseurl; - #i snarfed this from an RT bookmarked search, it could be unescaped in the - #source for readability and run through uri_escape - $href .= - 'Search/Results.html?Order=ASC&Query=%20MemberOf%20%3D%20%27freeside%3A%2F%2Ffreeside%2Fcust_main%2F'. - $custnum. - '%27%20%20AND%20%28%20Status%20%3D%20%27open%27%20%20OR%20Status%20%3D%20%27new%27%20%20OR%20Status%20%3D%20%27stalled%27%20%29%20' + #i snarfed this from an RT bookmarked search, then unescaped (some of) it with + #perl -npe 's/%([0-9A-F]{2})/pack('C', hex($1))/eg;' + + my $href .= + "Search/Results.html?Order=ASC&". + "Query= MemberOf = 'freeside://freeside/cust_main/$custnum' ". + #" AND ( Status = 'open' OR Status = 'new' OR Status = 'stalled' )" + " AND ( ". join(' OR ', map "Status = '$_'", $self->statuses ). " ) " ; if ( defined($priority) && $field && $priority_field_queue ) { - $href .= 'AND%20Queue%20%3D%20%27'. $priority_field_queue. '%27%20'; + $href .= " AND Queue = '$priority_field_queue' "; } if ( defined($priority) && $field ) { - $href .= '%20AND%20%27CF.'. $field. '%27%20'; + $href .= " AND 'CF.$field' "; if ( $priority ) { - $href .= '%3D%20%27'. $priority. '%27%20'; + $href .= "= '$priority' "; } else { - $href .= 'IS%20%27NULL%27%20'; + $href .= "IS 'NULL' "; #this is "RTQL", not SQL } } + #$href = + uri_escape($href); + #eventually should unescape all of it... + $href .= '&Rows=100'. '&OrderBy=id&Page=1'. '&Format=%27%20%20%20%3Cb%3E%3Ca%20href%3D%22'. @@ -185,7 +198,10 @@ sub href_customer_tickets { $href .= '%20%0A%27%3Csmall%3E__ToldRelative__%3C%2Fsmall%3E%27%2C%20%0A%27%3Csmall%3E__LastUpdatedRelative__%3C%2Fsmall%3E%27%2C%20%0A%27%3Csmall%3E__TimeLeft__%3C%2Fsmall%3E%27'; - $href; + #$href = + #uri_escape($href); + + $self->baseurl. $href; } diff --git a/httemplate/edit/cust_pay.cgi b/httemplate/edit/cust_pay.cgi index a03a245eb..e7734c1fc 100755 --- a/httemplate/edit/cust_pay.cgi +++ b/httemplate/edit/cust_pay.cgi @@ -9,22 +9,20 @@ my %payby = ( 'MCRD' => 'Manual credit card', ); -my($link, $linknum, $paid, $payby, $payinfo, $quickpay, $_date); +my($link, $linknum, $paid, $payby, $payinfo, $_date); if ( $cgi->param('error') ) { $link = $cgi->param('link'); $linknum = $cgi->param('linknum'); $paid = $cgi->param('paid'); $payby = $cgi->param('payby'); $payinfo = $cgi->param('payinfo'); - $quickpay = $cgi->param('quickpay'); $_date = $cgi->param('_date') ? str2time($cgi->param('_date')) : time; } elsif ( $cgi->param('custnum') =~ /^(\d+)$/ ) { - $link = 'custnum'; + $link = $cgi->param('popup') ? 'popup' : 'custnum'; $linknum = $1; $paid = ''; $payby = $cgi->param('payby') || 'BILL'; $payinfo = ''; - $quickpay = $cgi->param('quickpay'); $_date = time; } elsif ( $cgi->param('invnum') =~ /^(\d+)$/ ) { $link = 'invnum'; @@ -32,7 +30,6 @@ if ( $cgi->param('error') ) { $paid = ''; $payby = $cgi->param('payby') || 'BILL'; $payinfo = ""; - $quickpay = ''; $_date = time; } else { die "illegal query ". $cgi->keywords; @@ -43,9 +40,15 @@ my $paybatch = "webui-$_date-$$-". rand() * 2**32; my $title = 'Post '. $payby{$payby}. ' payment'; $title .= " against Invoice #$linknum" if $link eq 'invnum'; -%> +if ( $link eq 'popup' ) { + +%><%= include('/elements/header-popup.html', $title ) %> + +<% } else { %> -<%= include("/elements/header.html",$title, '') %> +<%= include("/elements/header.html", $title, '') %> + +<% } %> <% if ( $cgi->param('error') ) { %> Error: <%= $cgi->param('error') %> @@ -60,7 +63,6 @@ $title .= " against Invoice #$linknum" if $link eq 'invnum';
    - <% my $money_char = $conf->config('money_char') || '$'; @@ -74,7 +76,9 @@ if ( $link eq 'invnum' ) { } %> +<% unless ( $link eq 'popup' ) { %> <%= small_custview($custnum, $conf->config('countrydefault')) %> +<% } %> diff --git a/httemplate/edit/process/cust_pay.cgi b/httemplate/edit/process/cust_pay.cgi index 87d6011e7..cecccb59e 100755 --- a/httemplate/edit/process/cust_pay.cgi +++ b/httemplate/edit/process/cust_pay.cgi @@ -4,15 +4,16 @@ $cgi->param('linknum') =~ /^(\d+)$/ or die "Illegal linknum: ". $cgi->param('linknum'); my $linknum = $1; -$cgi->param('link') =~ /^(custnum|invnum)$/ +$cgi->param('link') =~ /^(custnum|invnum|popup)$/ or die "Illegal link: ". $cgi->param('link'); -my $link = $1; +my $field = my $link = $1; +$field = 'custnum' if $field eq 'popup'; my $_date = str2time($cgi->param('_date')); my $new = new FS::cust_pay ( { - $link => $linknum, - _date => $_date, + $field => $linknum, + _date => $_date, map { $_, scalar($cgi->param($_)); } qw(paid payby payinfo paybatch) @@ -24,19 +25,30 @@ my $error = $new->insert; if ($error) { $cgi->param('error', $error); print $cgi->redirect(popurl(2). 'cust_pay.cgi?'. $cgi->query_string ); -} elsif ( $link eq 'invnum' ) { +} elsif ( $field eq 'invnum' ) { print $cgi->redirect(popurl(3). "view/cust_bill.cgi?$linknum"); -} elsif ( $link eq 'custnum' ) { +} elsif ( $field eq 'custnum' ) { if ( $cgi->param('apply') eq 'yes' ) { my $cust_main = qsearchs('cust_main', { 'custnum' => $linknum }) or die "unknown custnum $linknum"; $cust_main->apply_payments; } - if ( $cgi->param('quickpay') eq 'yes' ) { - print $cgi->redirect(popurl(3). "search/cust_main-quickpay.html"); - } else { + if ( $link eq 'popup' ) { + + %><%= header('Payment entered') %> + + + + <% + + } elsif ( $link eq 'custnum' ) { print $cgi->redirect(popurl(3). "view/cust_main.cgi?$linknum"); + } else { + die "unknown link $link"; } + } %> diff --git a/httemplate/view/cust_main.cgi b/httemplate/view/cust_main.cgi index 89ddc38f2..8267fea51 100755 --- a/httemplate/view/cust_main.cgi +++ b/httemplate/view/cust_main.cgi @@ -2,24 +2,6 @@ my $conf = new FS::Conf; -my %uiview = (); -my %uiadd = (); -foreach my $part_svc ( qsearch('part_svc',{}) ) { - $uiview{$part_svc->svcpart} = $p. "view/". $part_svc->svcdb . ".cgi"; - $uiadd{$part_svc->svcpart}= $p. "edit/". $part_svc->svcdb . ".cgi"; -} - -%> - - -<%= include("/elements/header.html","Customer View", - include("/elements/menubar.html", - 'Main Menu' => $p, -)) %> - - -<% - my $curuser = $FS::CurrentUser::CurrentUser; die "No customer specified (bad URL)!" unless $cgi->keywords; @@ -31,6 +13,7 @@ die "Customer not found!" unless $cust_main; %> +<%= include("/elements/header.html","Customer View: ". $cust_main->name ) %> <% if ( $curuser->access_right('Edit customer') ) { %> Edit this customer | diff --git a/httemplate/view/cust_main/billing.html b/httemplate/view/cust_main/billing.html index 895814cc2..191d3092f 100644 --- a/httemplate/view/cust_main/billing.html +++ b/httemplate/view/cust_main/billing.html @@ -1,12 +1,24 @@ <% my( $cust_main ) = @_; my @invoicing_list = $cust_main->invoicing_list; + my $conf = new FS::Conf; + my $money_char = $conf->config('money_char') || '$'; %> Billing information (Bill now) <%= ntable("#cccccc") %><%= ntable("#cccccc",2) %> +<% +( my $balance = $cust_main->balance ) + =~ s/^(\-?)(.*)$/$1<\/FONT>$money_char$2/; +%> + + + Balance due + <%= $balance %> + + Billing type @@ -159,7 +171,7 @@ if ( $date =~ /^(\d{4})-(\d{1,2})-\d{1,2}$/ ) { #PostgreSQL date format <%= join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ) || 'no' %> -<% my $conf = new FS::Conf; if ( $conf->exists('voip-cust_cdr_spools') ) { %> +<% if ( $conf->exists('voip-cust_cdr_spools') ) { %> Spool CDRs <%= $cust_main->spool_cdr ? 'yes' : 'no' %> diff --git a/httemplate/view/cust_main/misc.html b/httemplate/view/cust_main/misc.html index 742b35958..f06a4fbd2 100644 --- a/httemplate/view/cust_main/misc.html +++ b/httemplate/view/cust_main/misc.html @@ -4,11 +4,17 @@ %> <%= ntable("#cccccc") %><%= &ntable("#cccccc",2) %> + Customer number <%= $cust_main->custnum %> + + Status + <%= ucfirst($cust_main->status) %> + + <% my @agents = qsearch( 'agent', {} ); my $agent; @@ -50,10 +56,6 @@ <% } %> - - Order taker - <%= $cust_main->otaker %> - Referring Customer @@ -82,5 +84,10 @@ + + Order taker + <%= $cust_main->otaker %> + + diff --git a/httemplate/view/cust_main/packages.html b/httemplate/view/cust_main/packages.html index beb67f325..9cd1e284f 100755 --- a/httemplate/view/cust_main/packages.html +++ b/httemplate/view/cust_main/packages.html @@ -7,10 +7,6 @@ my $packages = get_packages($cust_main, $conf); %> - - Packages <% if ( $curuser->access_right('Order customer package') ) { %> @@ -54,30 +50,33 @@ Current packages <% if ( @$packages ) { %> - +<%= include('/elements/table-grid.html') %> + +<% my $bgcolor1 = '#eeeeee'; + my $bgcolor2 = '#ffffff'; + my $bgcolor = ''; +%> + - - - + + + <% foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { - my $rowspan = 0; - if ($pkg->{cancel}) { - $rowspan = 0; + if ( $bgcolor eq $bgcolor1 ) { + $bgcolor = $bgcolor2; } else { - foreach my $svcpart (@{$pkg->{svcparts}}) { - $rowspan += $svcpart->{count}; - $rowspan++ if ($svcpart->{count} < $svcpart->{quantity}); - } - } + $bgcolor = $bgcolor1; + } + %> - - + + + + + + + + + + + + + <% + } + + } + if ( $bgcolor eq $bgcolor1 ) { $bgcolor = $bgcolor2; } else { @@ -441,18 +484,24 @@ foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) { } my $charge = exists($item->{'charge'}) - ? sprintf('$%.2f', $item->{'charge'}) + ? sprintf("$money_char\%.2f", $item->{'charge'}) : ''; + my $payment = exists($item->{'payment'}) - ? sprintf('- $%.2f', $item->{'payment'}) + ? sprintf("- $money_char\%.2f", $item->{'payment'}) : ''; - $payment ||= sprintf('- $%.2f', $item->{'void_payment'}) + + $payment ||= sprintf( "- $money_char\%.2f", + $item->{'void_payment'} + ) if exists($item->{'void_payment'}); + my $credit = exists($item->{'credit'}) - ? sprintf('- $%.2f', $item->{'credit'}) + ? sprintf("- $money_char\%.2f", $item->{'credit'}) : ''; + my $refund = exists($item->{'refund'}) - ? sprintf('$%.2f', $item->{'refund'}) + ? sprintf("$money_char\%.2f", $item->{'refund'}) : ''; my $target = exists($item->{'target'}) ? $item->{'target'} : ''; @@ -463,11 +512,11 @@ foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) { $balance += $item->{'refund'} if exists $item->{'refund'}; $balance = sprintf("%.2f", $balance); $balance =~ s/^\-0\.00$/0.00/; #yay ieee fp - ( my $showbalance = '$'. $balance ) =~ s/^\$\-/- \$/; + ( my $showbalance = $money_char. $balance ) =~ s/^\$\-/- \$/; %> - + >
    PackageStatusServicesPackageStatusServices
    > + <%=$pkg->{pkgnum}%>: <%=$pkg->{pkg}%> - <%=$pkg->{comment}%>
    @@ -94,8 +93,8 @@ foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) { <% } %>
    > - + -<% - if ($rowspan == 0) { print qq!\n!; next; } + + +<% } #end display packages %>
    + <% sub myfreq { @@ -315,42 +314,51 @@ foreach my $pkg (sort pkgsort_pkgnum_cancel @$packages) {
    + - my $cnt = 0; + +<% foreach my $svcpart (sort {$a->{svcpart} <=> $b->{svcpart}} @{$pkg->{svcparts}}) { foreach my $service (@{$svcpart->{services}}) { - print '' if ($cnt > 0); %> - - + + + + + <% if ( $curuser->access_right('Unprovision customer service') ) { %> + + + + <% } %> + <% } %> - - -<% - $cnt++; - } - if ( $svcpart->{count} < $svcpart->{quantity} ) { - print '' if ($cnt > 0); - if ( $curuser->access_right('Provision customer service') ) { - print ''; - } else { - #print ''; - print ''; - } - } - } -} -#end display packages + <% if ( $curuser->access_right('Provision customer service') + && $svcpart->{count} < $svcpart->{quantity} + ) + { + %> + + + + + + <% } %> + +<% } %> + +
    <%=svc_link($svcpart,$service)%><%=svc_label_link($svcpart,$service)%> - <% if ( $curuser->access_right('Unprovision customer service') ) { %> -
    ( <%=svc_unprovision_link($service)%> ) +
    <%=svc_link($svcpart,$service)%><%=svc_label_link($svcpart,$service)%>
    ( <%=svc_unprovision_link($service)%> )
    '. - svc_provision_link($pkg, $svcpart, $conf, $curuser). - '
     
    + <%= svc_provision_link($pkg, $svcpart, $conf, $curuser) %> +
    +
    + <% } else { %>
    <% } %> diff --git a/httemplate/view/cust_main/payment_history.html b/httemplate/view/cust_main/payment_history.html index 482136fa1..aef5fb8f8 100644 --- a/httemplate/view/cust_main/payment_history.html +++ b/httemplate/view/cust_main/payment_history.html @@ -21,21 +21,21 @@ <% if ( $payby{'BILL'} && $curuser->access_right('Post payment') ) { %> <%= $s++ ? ' | ' : '' %> - Post check payment + Enter check payment <% } %> <% if ( $payby{'CASH'} && $curuser->access_right('Post payment') ) { %> <%= $s++ ? ' | ' : '' %> - Post cash payment + Enter cash payment <% } %> <% if ( $payby{'WEST'} && $curuser->access_right('Post payment') ) { %> <%= $s++ ? ' | ' : '' %> - Post Western Union payment + Enter Western Union payment <% } %> @@ -62,7 +62,7 @@ <% if ( $payby{'MCRD'} && $curuser->access_right('Post payment') ) { %> <%= $s++ ? ' | ' : '' %> - Post manual credit card payment + Post manual (offline) credit card payment <% } %> @@ -70,7 +70,7 @@ <% if ( $curuser->access_right('Post credit') ) { %> - Post credit + Enter credit
    @@ -430,10 +430,53 @@ foreach my $cust_refund ($cust_main->cust_refund) { <% #display payment history -my %target; my $balance = 0; +my %target = (); +my $money_char = $conf->config('money_char') || '$'; + +my $years = $conf->config('payment_history-years') || 2; +my $older_than = time - $years * 31556736; #60*60*24*365.24 +my $hidden = 0; +my $seen = 0; +my $old_history = 0; + foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) { + my $display; + if ( $item->{'date'} < $older_than ) { + $display = ' STYLE="display:none" '; + $hidden = 1; + } else { + + $display = ''; + + if ( $hidden && ! $seen++ ) { + ( my $balance_forward = $money_char. $balance ) =~ s/^\$\-/- \$/; + %> + +
    + <%= time2str("%D",$item->{'date'}) %> + + Starting balance on <%= time2str("%D",$item->{'date'}) %> + (show prior history) + <%= $balance_forward %>
    <% unless ( !$target || $target{$target}++ ) { %> @@ -502,3 +551,20 @@ foreach my $item ( sort { $a->{'date'} <=> $b->{'date'} } @history ) {
    + + diff --git a/httemplate/view/cust_main/tickets.html b/httemplate/view/cust_main/tickets.html index 5c1d94ee1..93cb51f25 100644 --- a/httemplate/view/cust_main/tickets.html +++ b/httemplate/view/cust_main/tickets.html @@ -2,7 +2,7 @@ my( $cust_main ) = @_; my $conf = new FS::Conf; - my $num = 10; + my $num = $conf->config('cust_main-max_tickets') || 10; my @tickets = (); unless ( $conf->config('ticket_system-custom_priority_field') ) { @@ -31,24 +31,46 @@ Highest priority tickets (View all tickets for this customer) (New ticket for this customer) -<%= include("/elements/table.html") %> + +<%= include("/elements/table-grid.html") %> + +<% my $bgcolor1 = '#eeeeee'; + my $bgcolor2 = '#ffffff'; + my $bgcolor = ''; +%> + - # - Subject - Priority - Queue - Status + # + Subject + Priority + Queue + Status + <% foreach my $ticket ( @tickets ) { my $href = FS::TicketSystem->href_ticket($ticket->{id}); + if ( $bgcolor eq $bgcolor1 ) { + $bgcolor = $bgcolor2; + } else { + $bgcolor = $bgcolor1; + } %> + - ><%= $ticket->{id} %> - ><%= $ticket->{subject} %> - <%= $ticket->{content} || $ticket->{priority} %> - <%= $ticket->{name} %> - <%= $ticket->{status} %> + + ><%= $ticket->{id} %> + + ><%= $ticket->{subject} %> + + <%= $ticket->{content} || $ticket->{priority} %> + + <%= $ticket->{name} %> + + <%= $ticket->{status} %> + + <% } %> + -- cgit v1.2.1 From 97168edae6af4a4d98c4f790b0c064b73efbb9fd Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 14 Aug 2006 08:38:08 +0000 Subject: bugfix for agentless access users, triggered by part_referral (advertising source) agent virtualization --- FS/FS/access_user.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm index 830d7f826..874da6687 100644 --- a/FS/FS/access_user.pm +++ b/FS/FS/access_user.pm @@ -297,8 +297,12 @@ Returns an sql fragement to select only agentnums this user can view. sub agentnums_sql { my $self = shift; + + my @agentnums = $self->agentnums; + return ' 1 = 0 ' unless scalar(@agentnums); + '( '. - join( ' OR ', map "agentnum = $_", $self->agentnums ). + join( ' OR ', map "agentnum = $_", @agentnums ). ' )'; } -- cgit v1.2.1 From b19897e1db4c110d7d7e8b52800cda5ab58ce9e0 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 14 Aug 2006 12:13:40 +0000 Subject: sprinkle some magic ajax fairy dust on referring customer SELEKTAH. rewind! make smart search smarter, re-layout the top search bars and add an invoice one --- FS/FS/cust_main.pm | 329 +++++++++++++++++++----------- httemplate/edit/cust_main.cgi | 10 +- httemplate/elements/header.html | 94 +++++++-- httemplate/elements/search-cust_main.html | 163 +++++++++++++++ rt/html/Elements/FreesideInvoiceSearch | 20 ++ rt/html/Elements/FreesideNewCust | 2 +- rt/html/Elements/FreesideSearch | 7 +- rt/html/Elements/FreesideSvcSearch | 7 +- rt/html/Elements/PageLayout | 2 +- rt/html/Elements/SimpleSearch | 7 +- rt/html/Elements/Tabs | 6 +- rt/html/NoAuth/webrt.css | 39 +++- 12 files changed, 531 insertions(+), 155 deletions(-) create mode 100644 httemplate/elements/search-cust_main.html create mode 100644 rt/html/Elements/FreesideInvoiceSearch diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 3f67c62f0..7962f6dfa 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -3824,8 +3824,8 @@ sub uncancel_sql { " =item fuzzy_search FUZZY_HASHREF [ HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ ] Performs a fuzzy (approximate) search and returns the matching FS::cust_main -records. Currently, only I or I may be specified (the -appropriate ship_ field is also searched if applicable). +records. Currently, I, I and/or I may be specified (the +appropriate ship_ field is also searched). Additional options are the same as FS::Record::qsearch @@ -3839,19 +3839,25 @@ sub fuzzy_search { check_and_rebuild_fuzzyfiles(); foreach my $field ( keys %$fuzzy ) { - my $sub = \&{"all_$field"}; my %match = (); - $match{$_}=1 foreach ( amatch($fuzzy->{$field}, ['i'], @{ &$sub() } ) ); + $match{$_}=1 foreach ( amatch( $fuzzy->{$field}, + ['i'], + @{ $self->all_X($field) } + ) + ); + my @fcust = (); foreach ( keys %match ) { - push @cust_main, qsearch('cust_main', { %$hash, $field=>$_}, @opt); - push @cust_main, qsearch('cust_main', { %$hash, "ship_$field"=>$_}, @opt) - if defined dbdef->table('cust_main')->column('ship_last'); + push @fcust, qsearch('cust_main', { %$hash, $field=>$_}, @opt); + push @fcust, qsearch('cust_main', { %$hash, "ship_$field"=>$_}, @opt); } + my %fsaw = (); + push @cust_main, grep { ! $fsaw{$_->custnum}++ } @fcust; } + # we want the components of $fuzzy ANDed, not ORed, but still don't want dupes my %saw = (); - @cust_main = grep { !$saw{$_->custnum}++ } @cust_main; + @cust_main = grep { ++$saw{$_->custnum} == scalar(keys %$fuzzy) } @cust_main; @cust_main; @@ -3866,8 +3872,9 @@ sub fuzzy_search { =item smart_search OPTION => VALUE ... Accepts the following options: I, the string to search for. The string -will be searched for as a customer number, last name or company name, first -searching for an exact match then fuzzy and substring matches. +will be searched for as a customer number, phone number, name or company name, +first searching for an exact match then fuzzy and substring matches (in some +cases - see the source code for the exact heuristics used). Any additional options treated as an additional qualifier on the search (i.e. I). @@ -3878,13 +3885,53 @@ Returns a (possibly empty) array of FS::cust_main objects. sub smart_search { my %options = @_; - my $search = delete $options{'search'}; #here is the agent virtualization my $agentnums_sql = $FS::CurrentUser::CurrentUser->agentnums_sql; my @cust_main = (); - if ( $search =~ /^\s*(\d+)\s*$/ ) { # customer # search + + my $search = delete $options{'search'}; + ( my $alphanum_search = $search ) =~ s/\W//g; + + if ( $alphanum_search =~ /^1?(\d{3})(\d{3})(\d{4})(\d*)$/ ) { #phone# search + + #false laziness w/Record::ut_phone + my $phonen = "$1-$2-$3"; + $phonen .= " x$4" if $4; + + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { %options }, + 'extra_sql' => ( scalar(keys %options) ? ' AND ' : ' WHERE ' ). + ' ( '. + join(' OR ', map "$_ = '$phonen'", + qw( daytime night fax + ship_daytime ship_night ship_fax ) + ). + ' ) '. + " AND $agentnums_sql", #agent virtualization + } ); + + unless ( @cust_main || $phonen =~ /x\d+$/ ) { #no exact match + #try looking for matches with extensions unless one was specified + + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { %options }, + 'extra_sql' => ( scalar(keys %options) ? ' AND ' : ' WHERE ' ). + ' ( '. + join(' OR ', map "$_ LIKE '$phonen\%'", + qw( daytime night + ship_daytime ship_night ) + ). + ' ) '. + " AND $agentnums_sql", #agent virtualization + } ); + + } + + } elsif ( $search =~ /^\s*(\d+)\s*$/ ) { # customer # search push @cust_main, qsearch( { 'table' => 'cust_main', @@ -3892,22 +3939,86 @@ sub smart_search { 'extra_sql' => " AND $agentnums_sql", #agent virtualization } ); - } elsif ( $search =~ /^\s*(\S.*\S)\s*$/ ) { #value search + } elsif ( $search =~ /^\s*(\S.*\S)\s+\((.+), ([^,]+)\)\s*$/ ) { + + my($company, $last, $first) = ( $1, $2, $3 ); + + # "Company (Last, First)" + #this is probably something a browser remembered, + #so just do an exact search + + foreach my $prefix ( '', 'ship_' ) { + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { $prefix.'first' => $first, + $prefix.'last' => $last, + $prefix.'company' => $company, + %options, + }, + 'extra_sql' => " AND $agentnums_sql", + } ); + } + + } elsif ( $search =~ /^\s*(\S.*\S)\s*$/ ) { # value search + # try (ship_){last,company} my $value = lc($1); - # remove "(Last, First)" in "Company (Last, First"), otherwise the - # full strings the browser remembers won't work - $value =~ s/\([\w \,\.\-\']*\)$//; #false laziness w/Record::ut_name + # # remove "(Last, First)" in "Company (Last, First)", otherwise the + # # full strings the browser remembers won't work + # $value =~ s/\([\w \,\.\-\']*\)$//; #false laziness w/Record::ut_name + + use Lingua::EN::NameParse; + my $NameParse = new Lingua::EN::NameParse( + auto_clean => 1, + allow_reversed => 1, + ); + + my($last, $first) = ( '', '' ); + #maybe disable this too and just rely on NameParse? + if ( $value =~ /^(.+),\s*([^,]+)$/ ) { # Last, First + ($last, $first) = ( $1, $2 ); + + #} elsif ( $value =~ /^(.+)\s+(.+)$/ ) { + } elsif ( ! $NameParse->parse($value) ) { + + my %name = $NameParse->components; + $first = $name{'given_name_1'}; + $last = $name{'surname_1'}; + + } + + if ( $first && $last ) { + + my($q_last, $q_first) = ( dbh->quote($last), dbh->quote($first) ); + + #exact + my $sql = scalar(keys %options) ? ' AND ' : ' WHERE '; + $sql .= " + ( ( LOWER(last) = $q_last AND LOWER(first) = $q_first ) + OR ( LOWER(ship_last) = $q_last AND LOWER(ship_first) = $q_first ) + )"; + + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => \%options, + 'extra_sql' => "$sql AND $agentnums_sql", #agent virtualization + } ); + + # or it just be something that was typed in... (try that in a sec) + + } + my $q_value = dbh->quote($value); #exact my $sql = scalar(keys %options) ? ' AND ' : ' WHERE '; - $sql .= " ( LOWER(last) = $q_value OR LOWER(company) = $q_value"; - $sql .= " OR LOWER(ship_last) = $q_value OR LOWER(ship_company) = $q_value" - if defined dbdef->table('cust_main')->column('ship_last'); - $sql .= ' )'; + $sql .= " ( LOWER(last) = $q_value + OR LOWER(company) = $q_value + OR LOWER(ship_last) = $q_value + OR LOWER(ship_company) = $q_value + )"; push @cust_main, qsearch( { 'table' => 'cust_main', @@ -3920,56 +4031,62 @@ sub smart_search { #still some false laziness w/ search/cust_main.cgi #substring - push @cust_main, qsearch( { - 'table' => 'cust_main', - 'hashref' => { 'last' => { 'op' => 'ILIKE', - 'value' => "%$value%" }, - %options, - }, - 'extra_sql' => " AND $agentnums_sql", #agent virtualizaiton - } ); - push @cust_main, qsearch( { - 'table' => 'cust_main', - 'hashref' => { 'ship_last' => { 'op' => 'ILIKE', - 'value' => "%$value%" }, - %options, - }, - 'extra_sql' => " AND $agentnums_sql", #agent virtualization - } ) - if defined dbdef->table('cust_main')->column('ship_last'); - push @cust_main, qsearch( { - 'table' => 'cust_main', - 'hashref' => { 'company' => { 'op' => 'ILIKE', - 'value' => "%$value%" }, - %options, - }, - 'extra_sql' => " AND $agentnums_sql", #agent virtualization - } ); - push @cust_main, qsearch( { - 'table' => 'cust_main', - 'hashref' => { 'ship_company' => { 'op' => 'ILIKE', - 'value' => "%$value%" }, - %options, - }, - 'extra_sql' => " AND $agentnums_sql", #agent virtualization - } ) - if defined dbdef->table('cust_main')->column('ship_last'); + my @hashrefs = ( + { 'company' => { op=>'ILIKE', value=>"%$value%" }, }, + { 'ship_company' => { op=>'ILIKE', value=>"%$value%" }, }, + ); + + if ( $first && $last ) { + + push @hashrefs, + { 'first' => { op=>'ILIKE', value=>"%$first%" }, + 'last' => { op=>'ILIKE', value=>"%$last%" }, + }, + { 'ship_first' => { op=>'ILIKE', value=>"%$first%" }, + 'ship_last' => { op=>'ILIKE', value=>"%$last%" }, + }, + ; + + } else { + + push @hashrefs, + { 'last' => { op=>'ILIKE', value=>"%$value%" }, }, + { 'ship_last' => { op=>'ILIKE', value=>"%$value%" }, }, + ; + } + + foreach my $hashref ( @hashrefs ) { + + push @cust_main, qsearch( { + 'table' => 'cust_main', + 'hashref' => { %$hashref, + %options, + }, + 'extra_sql' => " AND $agentnums_sql", #agent virtualizaiton + } ); + + } #fuzzy - push @cust_main, FS::cust_main->fuzzy_search( - { 'last' => $value }, #fuzzy hashref - \%options, #hashref - '', #select - " AND $agentnums_sql", #extra_sql #agent virtualization - ); - push @cust_main, FS::cust_main->fuzzy_search( - { 'company' => $value }, #fuzzy hashref + my @fuzopts = ( \%options, #hashref '', #select " AND $agentnums_sql", #extra_sql #agent virtualization ); + if ( $first && $last ) { + push @cust_main, FS::cust_main->fuzzy_search( + { 'last' => $last, #fuzzy hashref + 'first' => $first }, # + @fuzopts + ); + } + foreach my $field ( 'last', 'company' ) { + push @cust_main, + FS::cust_main->fuzzy_search( { $field => $value }, @fuzopts ); + } + } #eliminate duplicates @@ -3986,10 +4103,12 @@ sub smart_search { =cut +use vars qw(@fuzzyfields); +@fuzzyfields = ( 'last', 'first', 'company' ); + sub check_and_rebuild_fuzzyfiles { my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; - -e "$dir/cust_main.last" && -e "$dir/cust_main.company" - or &rebuild_fuzzyfiles; + rebuild_fuzzyfiles() if grep { ! -e "$dir/cust_main.$_" } @fuzzyfields } =item rebuild_fuzzyfiles @@ -4003,71 +4122,39 @@ sub rebuild_fuzzyfiles { my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; mkdir $dir, 0700 unless -d $dir; - #last - - open(LASTLOCK,">>$dir/cust_main.last") - or die "can't open $dir/cust_main.last: $!"; - flock(LASTLOCK,LOCK_EX) - or die "can't lock $dir/cust_main.last: $!"; - - my @all_last = map $_->getfield('last'), qsearch('cust_main', {}); - push @all_last, - grep $_, map $_->getfield('ship_last'), qsearch('cust_main',{}) - if defined dbdef->table('cust_main')->column('ship_last'); - - open (LASTCACHE,">$dir/cust_main.last.tmp") - or die "can't open $dir/cust_main.last.tmp: $!"; - print LASTCACHE join("\n", @all_last), "\n"; - close LASTCACHE or die "can't close $dir/cust_main.last.tmp: $!"; - - rename "$dir/cust_main.last.tmp", "$dir/cust_main.last"; - close LASTLOCK; - - #company - - open(COMPANYLOCK,">>$dir/cust_main.company") - or die "can't open $dir/cust_main.company: $!"; - flock(COMPANYLOCK,LOCK_EX) - or die "can't lock $dir/cust_main.company: $!"; - - my @all_company = grep $_ ne '', map $_->company, qsearch('cust_main',{}); - push @all_company, - grep $_ ne '', map $_->ship_company, qsearch('cust_main', {}) - if defined dbdef->table('cust_main')->column('ship_last'); + foreach my $fuzzy ( @fuzzyfields ) { - open (COMPANYCACHE,">$dir/cust_main.company.tmp") - or die "can't open $dir/cust_main.company.tmp: $!"; - print COMPANYCACHE join("\n", @all_company), "\n"; - close COMPANYCACHE or die "can't close $dir/cust_main.company.tmp: $!"; - - rename "$dir/cust_main.company.tmp", "$dir/cust_main.company"; - close COMPANYLOCK; - -} - -=item all_last - -=cut + open(LOCK,">>$dir/cust_main.$fuzzy") + or die "can't open $dir/cust_main.$fuzzy: $!"; + flock(LOCK,LOCK_EX) + or die "can't lock $dir/cust_main.$fuzzy: $!"; + + my @all = map $_->getfield($fuzzy), qsearch('cust_main', {}); + push @all, + grep $_, map $_->getfield("ship_$fuzzy"), qsearch('cust_main',{}); + + open (CACHE,">$dir/cust_main.$fuzzy.tmp") + or die "can't open $dir/cust_main.$fuzzy.tmp: $!"; + print CACHE join("\n", @all), "\n"; + close CACHE or die "can't close $dir/cust_main.$fuzzy.tmp: $!"; + + rename "$dir/cust_main.$fuzzy.tmp", "$dir/cust_main.$fuzzy"; + close LOCK; + } -sub all_last { - my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; - open(LASTCACHE,"<$dir/cust_main.last") - or die "can't open $dir/cust_main.last: $!"; - my @array = map { chomp; $_; } ; - close LASTCACHE; - \@array; } -=item all_company +=item all_X =cut -sub all_company { +sub all_X { + my( $self, $field ) = @_; my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; - open(COMPANYCACHE,"<$dir/cust_main.company") - or die "can't open $dir/cust_main.last: $!"; - my @array = map { chomp; $_; } ; - close COMPANYCACHE; + open(CACHE,"<$dir/cust_main.$field") + or die "can't open $dir/cust_main.$field: $!"; + my @array = map { chomp; $_; } ; + close CACHE; \@array; } diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi index 45cb69fc2..c3d1804bc 100755 --- a/httemplate/edit/cust_main.cgi +++ b/httemplate/edit/cust_main.cgi @@ -64,7 +64,9 @@ if ( $cgi->param('error') ) { @invoicing_list = (); } $cgi->delete_all(); + my $action = $custnum ? 'Edit' : 'Add'; +$action .= ": ". $cust_main->name if $custnum; my $r = qq!* !; @@ -139,7 +141,13 @@ if ( $cust_main->referral_custnum Referring customer - + + + <%= include('/elements/search-cust_main.html', + 'field_name' => 'referral_custnum', + ) + %> + <% } else { %> diff --git a/httemplate/elements/header.html b/httemplate/elements/header.html index 3aa81be3b..ea8c418c3 100644 --- a/httemplate/elements/header.html +++ b/httemplate/elements/header.html @@ -3,6 +3,7 @@ my $etc = @_ ? shift : ''; #$etc is for things like onLoad= etc. my $head = @_ ? shift : ''; #$head is for things that go in the section my $conf = new FS::Conf; + %> @@ -18,17 +19,22 @@ @@ -39,13 +45,11 @@ STYLE="margin-top:0; margin-bottom:0; margin-left:0; margin-right:0"> - + - @@ -77,34 +81,88 @@
    - freeside - freeside <%= $conf->config('company_name') || 'ExampleCo' %> Logged in as <%= getotaker %> 
    Preferences 

    +
    Logged in as <%= getotaker %> 
    Preferences 
    - + + +
    - + + - + + + + - +
    + + - +
    - - +
    + Advanced +
    + <% if ( $FS::CurrentUser::CurrentUser->access_right('View invoices') ) { %> +
    + + <% if ( $FS::CurrentUser::CurrentUser->access_right('List invoices') ) { %> + Advanced + <% } %> +
    + +
    + <% } %> +
    - - +
    + Advanced +
    + +
    - - +
    + Advanced +
    diff --git a/httemplate/elements/search-cust_main.html b/httemplate/elements/search-cust_main.html new file mode 100644 index 000000000..ca91b4027 --- /dev/null +++ b/httemplate/elements/search-cust_main.html @@ -0,0 +1,163 @@ +<% + my( %opt ) = @_; + $opt{'field_name'} ||= 'custnum'; + + my $cust_main = ''; + if ( $opt{'value'} ) { + $cust_main = qsearchs( + 'table' => 'cust_main', + 'hashref' => { 'custnum' => $opt{'value'} }, + 'extra_sql' => " AND ". $FS::CurrentUser::CurrentUser->agentnums_sql, + ); + } +%> + + + + + + + + + +<%= include('/elements/xmlhttp.html', + 'url' => $p. 'misc/xmlhttp-cust_main-search.cgi', + 'subs' => [ 'smart_search' ], + ) +%> + + + diff --git a/rt/html/Elements/FreesideInvoiceSearch b/rt/html/Elements/FreesideInvoiceSearch new file mode 100644 index 000000000..3842b2ff9 --- /dev/null +++ b/rt/html/Elements/FreesideInvoiceSearch @@ -0,0 +1,20 @@ +% if ( $FS::CurrentUser::CurrentUser->access_right('View invoices') ) { + + + + + +% if ( $FS::CurrentUser::CurrentUser->access_right('List invoices') ) { + Advanced +% } +
    + + + + +% } diff --git a/rt/html/Elements/FreesideNewCust b/rt/html/Elements/FreesideNewCust index af8f9f1a7..c752437da 100644 --- a/rt/html/Elements/FreesideNewCust +++ b/rt/html/Elements/FreesideNewCust @@ -1,3 +1,3 @@ -  +  diff --git a/rt/html/Elements/FreesideSearch b/rt/html/Elements/FreesideSearch index f0efb60d4..99b8da072 100644 --- a/rt/html/Elements/FreesideSearch +++ b/rt/html/Elements/FreesideSearch @@ -1,10 +1,11 @@ - -  +
    +Advanced + diff --git a/rt/html/Elements/FreesideSvcSearch b/rt/html/Elements/FreesideSvcSearch index 47c430f1a..e9ad56426 100644 --- a/rt/html/Elements/FreesideSvcSearch +++ b/rt/html/Elements/FreesideSvcSearch @@ -1,10 +1,11 @@ - -  +
    + Advanced + diff --git a/rt/html/Elements/PageLayout b/rt/html/Elements/PageLayout index 52353fa46..f13ee0dda 100644 --- a/rt/html/Elements/PageLayout +++ b/rt/html/Elements/PageLayout @@ -52,7 +52,7 @@ %# % my $notfirst = 0; foreach my $action (sort keys %{$topactions}) { - % } diff --git a/rt/html/Elements/SimpleSearch b/rt/html/Elements/SimpleSearch index 55d65fc89..e9fc5c6ed 100644 --- a/rt/html/Elements/SimpleSearch +++ b/rt/html/Elements/SimpleSearch @@ -46,10 +46,11 @@ - -  +
    +Advanced + diff --git a/rt/html/Elements/Tabs b/rt/html/Elements/Tabs index dcb652e12..721f920d5 100644 --- a/rt/html/Elements/Tabs +++ b/rt/html/Elements/Tabs @@ -63,9 +63,11 @@ my $basetopactions = { }, B => { html => $m->scomp('/Elements/FreesideSearch') }, - C => { html => $m->scomp('/Elements/FreesideSvcSearch') + C => { html => $m->scomp('/Elements/FreesideInvoiceSearch') }, - D => { html => $m->scomp('/Elements/SimpleSearch') + D => { html => $m->scomp('/Elements/FreesideSvcSearch') + }, + E => { html => $m->scomp('/Elements/SimpleSearch') } }; my $basetabs = { diff --git a/rt/html/NoAuth/webrt.css b/rt/html/NoAuth/webrt.css index 04c959a05..5c241f93f 100644 --- a/rt/html/NoAuth/webrt.css +++ b/rt/html/NoAuth/webrt.css @@ -373,9 +373,44 @@ li.currenttopnav-5-major { background-color: #000000; color: #ffffff; background-position: left top; - vertical-align: top; + vertical-align: center; text-align: right; + font-size:16px; + padding-right:4px } + +input.fsblackbutton { + background-color:#333333; + color: #ffffff; + border:1px solid; + border-top-color:#cccccc; + border-left-color:#cccccc; + border-right-color:#aaaaaa; + border-bottom-color:#aaaaaa; + font-family: Arial, Verdana, Helvetica, sans-serif; + font-weight:bold; + padding-left:12px; + padding-right:12px; + overflow:visible; + filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff333333',EndColorStr='#ff666666') +} + +input.fsblackbuttonselected { + background-color:#7e0079; + color: #ffffff; + border:1px solid; + border-top-color:#cccccc; + border-left-color:#cccccc; + border-right-color:#aaaaaa; + border-bottom-color:#aaaaaa; + font-family: Arial, Verdana, Helvetica, sans-serif; + font-weight:bold; + padding-left:12px; + padding-right:12px; + overflow:visible; + filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0,StartColorStr='#ff330033',EndColorStr='#ff7e0079') +} + .mediumgray { background-color: #cccccc; background-position: left top; @@ -442,7 +477,7 @@ div.downloadattachment { td { font-family: Arial, Verdana, Helvetica, sans-serif; - font-size: 11px; + font-size: 12px; background-position: left top; } .black { background-color: #000000; -- cgit v1.2.1 From 9cf3ec9dbbcb4ecc7c00b5300beb065184b448d4 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 14 Aug 2006 12:24:08 +0000 Subject: there's more, but this will have to do --- Changes.1.7.0 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Changes.1.7.0 b/Changes.1.7.0 index d5dcf3da7..d98273efc 100644 --- a/Changes.1.7.0 +++ b/Changes.1.7.0 @@ -14,6 +14,21 @@ and (now they're finally here)... - ACLs - Agent virtualization + - customers etc. + - most reports + - just advertising sources in the config so far +- percentage late fees in addition to flat +- voip work +- "referring customer #" ajax-ified +- top search box updated some more +- view/cust_main: + - customer status and balance displayed up top + - all the tables (tickets, packages, payment history) are grid-ified + - payment history only shows N years of history with link to show more + - simple payment actions are js popups + - configurable statuses and # of tickets for ticket list +- edit/cust_main: + - magic ajax search box for referring customer -------- some of the above, nicely: -- cgit v1.2.1 From 16a001f9e22bdb68e85d6c23697bfb5f7b8fa8f4 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 14 Aug 2006 13:28:56 +0000 Subject: pass email, phone and ip adderss to B:OP when doing refunds, hopefully this will fix OpenECHO refunds --- FS/FS/cust_main.pm | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 7962f6dfa..95097da71 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2850,6 +2850,22 @@ sub realtime_refund_bop { $payname = "$payfirst $paylast"; } + my @invoicing_list = grep { $_ ne 'POST' } $self->invoicing_list; + if ( $conf->exists('emailinvoiceauto') + || ( $conf->exists('emailinvoiceonly') && ! @invoicing_list ) ) { + push @invoicing_list, $self->all_emails; + } + + my $email = ($conf->exists('business-onlinepayment-email-override')) + ? $conf->config('business-onlinepayment-email-override') + : $invoicing_list[0]; + + my $payip = exists($options{'payip'}) + ? $options{'payip'} + : $self->payip; + $content{customer_ip} = $payip + if length($payip); + my $payinfo = ''; if ( $method eq 'CC' ) { @@ -2888,6 +2904,8 @@ sub realtime_refund_bop { 'state' => $self->state, 'zip' => $self->zip, 'country' => $self->country, + 'email' => $email, + 'phone' => $self->daytime || $self->night, %content, #after ); warn join('', map { " $_ => $sub_content{$_}\n" } keys %sub_content ) -- cgit v1.2.1 From 04cbf1d986eabf7fdcc22ff95b95da17e5f4bf63 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 15 Aug 2006 14:20:51 +0000 Subject: add a new, extended CSV import format --- FS/FS/cust_main.pm | 112 ++++++++++++++++++++++----- httemplate/misc/cust_main-import.cgi | 102 +++++++++++++----------- httemplate/misc/process/cust_main-import.cgi | 5 +- 3 files changed, 153 insertions(+), 66 deletions(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 95097da71..c40d54aac 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -4230,9 +4230,33 @@ sub batch_import { #warn join('-',keys %$param); my $fh = $param->{filehandle}; my $agentnum = $param->{agentnum}; + my $refnum = $param->{refnum}; my $pkgpart = $param->{pkgpart}; - my @fields = @{$param->{fields}}; + + #my @fields = @{$param->{fields}}; + my $format = $param->{'format'}; + my @fields; + my $payby; + if ( $format eq 'simple' ) { + @fields = qw( cust_pkg.setup dayphone first last + address1 address2 city state zip comments ); + $payby = 'BILL'; + } elsif ( $format eq 'extended' ) { + @fields = qw( agent_custid refnum + last first address1 address2 city state zip country + daytime night + ship_last ship_first ship_address1 ship_address2 + ship_city ship_state ship_zip ship_country + payinfo paycvv paydate + invoicing_list + cust_pkg.pkgpart + svc_acct.username svc_acct._password + ); + $payby = 'CARD'; + } else { + die "unknown format $format"; + } eval "use Text::CSV_XS;"; die $@ if $@; @@ -4271,51 +4295,99 @@ sub batch_import { agentnum => $agentnum, refnum => $refnum, country => $conf->config('countrydefault') || 'US', - payby => 'BILL', #default + payby => $payby, #default paydate => '12/2037', #default ); my $billtime = time; my %cust_pkg = ( pkgpart => $pkgpart ); + my %svc_acct = (); foreach my $field ( @fields ) { - if ( $field =~ /^cust_pkg\.(setup|bill|susp|expire|cancel)$/ ) { + + if ( $field =~ /^cust_pkg\.(pkgpart|setup|bill|susp|expire|cancel)$/ ) { + #$cust_pkg{$1} = str2time( shift @$columns ); - if ( $1 eq 'setup' ) { + if ( $1 eq 'pkgpart' ) { + $cust_pkg{$1} = shift @columns; + } elsif ( $1 eq 'setup' ) { $billtime = str2time(shift @columns); } else { $cust_pkg{$1} = str2time( shift @columns ); - } + } + + } elsif ( $field =~ /^svc_acct\.(username|_password)$/ ) { + + $svc_acct{$1} = shift @columns; + } else { + + #refnum interception + if ( $field eq 'refnum' && $columns[0] !~ /^\s*(\d+)\s*$/ ) { + + my $referral = $columns[0]; + my $part_referral = new FS::part_referral { + 'referral' => $referral, + 'agentnum' => $agentnum, + }; + + my $error = $part_referral->insert; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't auto-insert advertising source: $referral: $error"; + } + $columns[0] = $part_referral->refnum; + } + #$cust_main{$field} = shift @$columns; $cust_main{$field} = shift @columns; } } - my $cust_pkg = new FS::cust_pkg ( \%cust_pkg ) if $pkgpart; + my $invoicing_list = $cust_main{'invoicing_list'} + ? [ delete $cust_main{'invoicing_list'} ] + : []; + my $cust_main = new FS::cust_main ( \%cust_main ); + use Tie::RefHash; tie my %hash, 'Tie::RefHash'; #this part is important - $hash{$cust_pkg} = [] if $pkgpart; - my $error = $cust_main->insert( \%hash ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "can't insert customer for $line: $error"; + if ( $cust_pkg{'pkgpart'} ) { + my $cust_pkg = new FS::cust_pkg ( \%cust_pkg ); + + my @svc_acct = (); + if ( $svc_acct{'username'} ) { + $svc_acct{svcpart} = $cust_pkg->part_pkg->svcpart( 'svc_acct' ); + push @svc_acct, new FS::svc_acct ( \%svc_acct ) + } + + $hash{$cust_pkg} = \@svc_acct; } - #false laziness w/bill.cgi - $error = $cust_main->bill( 'time' => $billtime ); + my $error = $cust_main->insert( \%hash, $invoicing_list ); + if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "can't bill customer for $line: $error"; + return "can't insert customer for $line: $error"; } - $cust_main->apply_payments; - $cust_main->apply_credits; + if ( $format eq 'simple' ) { + + #false laziness w/bill.cgi + $error = $cust_main->bill( 'time' => $billtime ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't bill customer for $line: $error"; + } + + $cust_main->apply_payments; + $cust_main->apply_credits; + + $error = $cust_main->collect(); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't collect customer for $line: $error"; + } - $error = $cust_main->collect(); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "can't collect customer for $line: $error"; } $imported++; diff --git a/httemplate/misc/cust_main-import.cgi b/httemplate/misc/cust_main-import.cgi index 484855005..2ad4d95b4 100644 --- a/httemplate/misc/cust_main-import.cgi +++ b/httemplate/misc/cust_main-import.cgi @@ -1,51 +1,65 @@ - <%= include("/elements/header.html",'Batch Customer Import') %> +
    -Import a CSV file containing customer records.

    -Default file format is CSV, with the following field order: cust_pkg.setup, dayphone, first, last, address1, address2, city, state, zip, comments

    -<% - #false laziness with edit/cust_main.cgi - my @agents = qsearch( 'agent', {} ); - die "No agents created!" unless @agents; - my $agentnum = $agents[0]->agentnum; #default to first +Import a CSV file containing customer records. +

    + + + +Extended file format is CSV, with the following field order: agent_custid, refnum[1], last, first, address1, address2, city, state, zip, country, daytime, night, ship_last, ship_first, ship_address1, ship_address2, ship_city, ship_state, ship_zip, ship_country, payinfo, paycvv, paydate, invoicing_list, pkgpart, username, _password +

    - if ( scalar(@agents) == 1 ) { +[1] This field has special treatment upon import: If a string is passed instead +of an integer, the string is searched for and if necessary auto-created in the +target table. +

    + +<%= &ntable("#cccccc") %> + +<%= include('/elements/tr-select-agent.html', '', #$agentnum, + 'label' => "Agent", + 'empty_label' => 'Select agent', + ) %> - -<% } else { %> -

    Agent

    -<% } %> - -<% - my @referrals = qsearch('part_referral',{}); - die "No advertising sources created!" unless @referrals; - my $refnum = $referrals[0]->refnum; #default to first - - if ( scalar(@referrals) == 1 ) { + +
    + + + + + + + + + +<% #include('/elements/tr-select-part_referral.html') %> - -<% } else { %> -

    Advertising source

    -<% } %> - - First package:

    - - CSV Filename:

    - - - - + + + +
    > + <%$topactions->{"$action"}->{'html'} |n %>
    Format + +
    CSV filename
    +

    + + + + +<%= include('/elements/footer.html') %> diff --git a/httemplate/misc/process/cust_main-import.cgi b/httemplate/misc/process/cust_main-import.cgi index 371929a5e..aff6b39ef 100644 --- a/httemplate/misc/process/cust_main-import.cgi +++ b/httemplate/misc/process/cust_main-import.cgi @@ -10,8 +10,9 @@ agentnum => scalar($cgi->param('agentnum')), refnum => scalar($cgi->param('refnum')), pkgpart => scalar($cgi->param('pkgpart')), - 'fields' => [qw( cust_pkg.setup dayphone first last address1 address2 - city state zip comments )], + #'fields' => [qw( cust_pkg.setup dayphone first last address1 address2 + # city state zip comments )], + 'format' => scalar($cgi->param('format')), } ) : 'No file'; -- cgit v1.2.1 From 020d74d3e1ce242990486a3fc7b61be5d666c2de Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 16 Aug 2006 08:19:47 +0000 Subject: get rid of too-verbose debugging --- FS/bin/freeside-adduser | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FS/bin/freeside-adduser b/FS/bin/freeside-adduser index 3976caece..b955902ba 100644 --- a/FS/bin/freeside-adduser +++ b/FS/bin/freeside-adduser @@ -27,7 +27,7 @@ if ( $opt_h ) { push @args, '-c' if $opt_c; push @args, $opt_h, $user; push @args, shift if $opt_b; - warn join(', ', 'htpasswd', @args)."\n"; + #warn join(', ', @args)."\n"; system(@args) == 0 or die "htpasswd failed: $?"; } -- cgit v1.2.1 From 8d1ba54d93d8b4b12395fcc2a45632ffa59546c5 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 08:33:47 +0000 Subject: first try at skeleton feature for mg --- FS/FS/Conf.pm | 14 +++++ FS/FS/cust_main.pm | 175 +++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 157 insertions(+), 32 deletions(-) diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 68ca49d0b..f6495119e 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1738,6 +1738,20 @@ httemplate/docs/config.html 'type' => 'text', }, + { + 'key' => 'cust_main-skeleton_tables', + 'section' => '', + 'description' => 'Tables which will have skeleton records inserted into them for each customer. Syntax for specifying tables is unfortunately a tricky perl data structure for now.', + 'type' => 'textarea', + }, + + { + 'key' => 'cust_main-skeleton_custnum', + 'section' => '', + 'description' => 'Customer number specifying the source data to copy into skeleton tables for new customers.', + 'type' => 'text', + }, + ); 1; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index c40d54aac..f9d7be1c8 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -416,6 +416,20 @@ sub insert { $self->invoicing_list( $invoicing_list ); } + if ( $conf->config('cust_main-skeleton_tables') + && $conf->config('cust_main-skeleton_custnum') ) { + + warn " inserting skeleton records\n" + if $DEBUG > 1; + + my $error = $self->start_copy_skel; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + + } + warn " ordering packages\n" if $DEBUG > 1; @@ -458,6 +472,102 @@ sub insert { } +sub start_copy_skel { + my $self = shift; + + #'mg_user_preference' => {}, + #'mg_user_indicator_profile' => { 'mg_profile_indicator' => { 'mg_profile_details' }, }, + #'mg_watchlist_header' => { 'mg_watchlist_details' }, + #'mg_user_grid_header' => { 'mg_user_grid_details' }, + #'mg_portfolio_header' => { 'mg_portfolio_trades' => { 'mg_portfolio_trades_positions' } }, + my @tables = eval($conf->config('cust_main-skeleton_tables')); + die $@ if $@; + + _copy_skel( 'cust_main', #tablename + $conf->config('cust_main-skeleton_custnum'), #sourceid + $self->custnum, #destid + @tables, #child tables + ); +} + +#recursive subroutine, not a method +sub _copy_skel { + my( $table, $sourceid, $destid, %child_tables ) = @_; + + my $dbdef_table = dbdef->table($table); + my $primary_key = $dbdef_table->primary_key + or return "$table has no primary key". + " (or do you need to run dbdef-create?)"; + + foreach my $child_table ( keys %child_tables ) { + + my $child_pkey = dbdef->table($child_table)->primary_key; + # or return "$table has no primary key". + # " (or do you need to run dbdef-create?)\n"; + my $sequence = ''; + if ( keys %{ $child_tables{$child_table} } ) { + + return "$child_table has no primary key\n" unless $child_pkey; + + #false laziness w/Record::insert and only works on Pg + #refactor the proper last-inserted-id stuff out of Record::insert if this + # ever gets use for anything besides a quick kludge for one customer + my $default = dbdef->table($child_table)->column($child_pkey)->default; + $default =~ /^nextval\(\(?'"?([\w\.]+)"?'/i + or return "can't parse $child_table.$child_pkey default value ". + " for sequence name: $default"; + $sequence = $1; + + } + + my @sel_columns = grep { $_ ne $primary_key } dbdef->table($table)->columns; + my $sel_columns = ' ( '. join(', ', @sel_columns ). ' ) '; + + my @ins_columns = grep { $_ ne $child_pkey } @sel_columns; + my $ins_columns = ' ( ', join(', ', $primary_key, @ins_columns ). ' ) ', + my $placeholders = ' ( ?, '. join(', ', map '?', @ins_columns ). ' ) '; + + my $sel_sth = dbh->prepare( "SELECT $sel_columns FROM $child_table". + " WHERE $primary_key = $sourceid") + or return dbh->errstr; + + $sel_sth->execute or return $sel_sth->errstr; + + while ( my $row = $sel_sth->fetchrow_hashref ) { + + my $ins_sth = + dbh->prepare("INSERT INTO $child_table $ins_columns". + " VALUES $placeholders") + or return dbh->errstr; + $ins_sth->execute( $destid, map $row->{$_}, @ins_columns ) + or return $ins_sth->errstr; + + #next unless keys %{ $child_tables{$child_table} }; + next unless $sequence; + + #another section of that laziness + my $seq_sql = "SELECT currval('$sequence')"; + my $seq_sth = dbh->prepare($seq_sql) or return dbh->errstr; + $seq_sth->execute or return $seq_sth->errstr; + my $insertid = $seq_sth->fetchrow_arrayref->[0]; + + # don't drink soap! recurse! recurse! okay! + my $error = + _copy_skel( $child_table, + $row->{$child_pkey}, #sourceid + $insertid, #destid + %{ $child_tables{$child_table} }, + ); + return $error if $error; + + } + + } + + return ''; + +} + =item order_pkgs HASHREF, [ SECONDSREF, [ , OPTION => VALUE ... ] ] Like the insert method on an existing record, this method orders a package @@ -1023,15 +1133,19 @@ sub queue_fuzzyfiles_update { my $dbh = dbh; my $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - my $error = $queue->insert($self->getfield('last'), $self->company); + my $error = $queue->insert( map $self->getfield($_), + qw(first last company) + ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "queueing job (transaction rolled back): $error"; } - if ( defined $self->dbdef_table->column('ship_last') && $self->ship_last ) { + if ( $self->ship_last ) { $queue = new FS::queue { 'job' => 'FS::cust_main::append_fuzzyfiles' }; - $error = $queue->insert($self->getfield('ship_last'), $self->ship_company); + $error = $queue->insert( map $self->getfield("ship_$_"), + qw(first last company) + ); if ( $error ) { $dbh->rollback if $oldAutoCommit; return "queueing job (transaction rolled back): $error"; @@ -4146,14 +4260,21 @@ sub rebuild_fuzzyfiles { or die "can't open $dir/cust_main.$fuzzy: $!"; flock(LOCK,LOCK_EX) or die "can't lock $dir/cust_main.$fuzzy: $!"; - - my @all = map $_->getfield($fuzzy), qsearch('cust_main', {}); - push @all, - grep $_, map $_->getfield("ship_$fuzzy"), qsearch('cust_main',{}); - + open (CACHE,">$dir/cust_main.$fuzzy.tmp") or die "can't open $dir/cust_main.$fuzzy.tmp: $!"; - print CACHE join("\n", @all), "\n"; + + foreach my $field ( $fuzzy, "ship_$fuzzy" ) { + my $sth = dbh->prepare("SELECT $field FROM cust_main". + " WHERE $field != '' AND $field IS NOT NULL"); + $sth->execute or die $sth->errstr; + + while ( my $row = $sth->fetchrow_arrayref ) { + print CACHE $row->[0]. "\n"; + } + + } + close CACHE or die "can't close $dir/cust_main.$fuzzy.tmp: $!"; rename "$dir/cust_main.$fuzzy.tmp", "$dir/cust_main.$fuzzy"; @@ -4181,7 +4302,7 @@ sub all_X { =cut sub append_fuzzyfiles { - my( $last, $company ) = @_; + #my( $first, $last, $company ) = @_; &check_and_rebuild_fuzzyfiles; @@ -4189,33 +4310,23 @@ sub append_fuzzyfiles { my $dir = $FS::UID::conf_dir. "cache.". $FS::UID::datasrc; - if ( $last ) { - - open(LAST,">>$dir/cust_main.last") - or die "can't open $dir/cust_main.last: $!"; - flock(LAST,LOCK_EX) - or die "can't lock $dir/cust_main.last: $!"; - - print LAST "$last\n"; + foreach my $field (qw( first last company )) { + my $value = shift; - flock(LAST,LOCK_UN) - or die "can't unlock $dir/cust_main.last: $!"; - close LAST; - } - - if ( $company ) { + if ( $value ) { - open(COMPANY,">>$dir/cust_main.company") - or die "can't open $dir/cust_main.company: $!"; - flock(COMPANY,LOCK_EX) - or die "can't lock $dir/cust_main.company: $!"; + open(CACHE,">>$dir/cust_main.$field") + or die "can't open $dir/cust_main.$field: $!"; + flock(CACHE,LOCK_EX) + or die "can't lock $dir/cust_main.$field: $!"; - print COMPANY "$company\n"; + print CACHE "$value\n"; - flock(COMPANY,LOCK_UN) - or die "can't unlock $dir/cust_main.company: $!"; + flock(CACHE,LOCK_UN) + or die "can't unlock $dir/cust_main.$field: $!"; + close CACHE; + } - close COMPANY; } 1; -- cgit v1.2.1 From 44c2490342f14b64acbd9fce3bf7df8df4e64aff Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 09:50:03 +0000 Subject: add debugging to _copy_skel to get some idea what's going on --- FS/FS/cust_main.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index f9d7be1c8..2617d10b0 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -499,6 +499,10 @@ sub _copy_skel { or return "$table has no primary key". " (or do you need to run dbdef-create?)"; + warn " _copy_skel: $table.$primary_key $sourceid to $destid for ". + join (', ', keys %child_tables). "\n" + if $DEBUG > 2; + foreach my $child_table ( keys %child_tables ) { my $child_pkey = dbdef->table($child_table)->primary_key; -- cgit v1.2.1 From 7bac56be80a6323073566c6e3c0d87c90801036b Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 09:58:11 +0000 Subject: oops, want CHILD table for skeleton inserts, not parent --- FS/FS/cust_main.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 2617d10b0..270c544ef 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -524,7 +524,8 @@ sub _copy_skel { } - my @sel_columns = grep { $_ ne $primary_key } dbdef->table($table)->columns; + my @sel_columns = grep { $_ ne $primary_key } + dbdef->table($child_table)->columns; my $sel_columns = ' ( '. join(', ', @sel_columns ). ' ) '; my @ins_columns = grep { $_ ne $child_pkey } @sel_columns; -- cgit v1.2.1 From 99c0907183fd9fbfb62a9dbd27d72013a95ca64a Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 10:00:20 +0000 Subject: skeleton typo --- 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 270c544ef..a52145b19 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -529,7 +529,7 @@ sub _copy_skel { my $sel_columns = ' ( '. join(', ', @sel_columns ). ' ) '; my @ins_columns = grep { $_ ne $child_pkey } @sel_columns; - my $ins_columns = ' ( ', join(', ', $primary_key, @ins_columns ). ' ) ', + my $ins_columns = ' ( '. join(', ', $primary_key, @ins_columns ). ' ) '; my $placeholders = ' ( ?, '. join(', ', map '?', @ins_columns ). ' ) '; my $sel_sth = dbh->prepare( "SELECT $sel_columns FROM $child_table". -- cgit v1.2.1 From 3500f6c6f1c42b39139cdedc12dc96ee5f6b8e57 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 10:10:33 +0000 Subject: what's going on with the parameters for skeleton inserts?? --- FS/FS/cust_main.pm | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index a52145b19..59295f3ee 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -540,11 +540,14 @@ sub _copy_skel { while ( my $row = $sel_sth->fetchrow_hashref ) { - my $ins_sth = - dbh->prepare("INSERT INTO $child_table $ins_columns". - " VALUES $placeholders") + my $statement = + "INSERT INTO $child_table $ins_columns VALUES $placeholders"; + my $ins_sth =dbh->prepare($statement) or return dbh->errstr; - $ins_sth->execute( $destid, map $row->{$_}, @ins_columns ) + my @param = ( $destid, map $row->{$_}, @ins_columns ); + warn " $statement: [ ". join(', ', @param). " ]\n" + if $DEBUG > 2; + $ins_sth->execute( @param ) or return $ins_sth->errstr; #next unless keys %{ $child_tables{$child_table} }; -- cgit v1.2.1 From a54563b7d5cbac55d89ddd1f11e6c9a12c46b6f1 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 10:18:13 +0000 Subject: even more skeleton debugging, ugh --- FS/FS/cust_main.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 59295f3ee..73afe2183 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -540,6 +540,10 @@ sub _copy_skel { while ( my $row = $sel_sth->fetchrow_hashref ) { + warn " selected row: ". + join(', ', map { "$_=".$row->{$_} } keys %$row ). "\n" + if $DEBUG > 2; + my $statement = "INSERT INTO $child_table $ins_columns VALUES $placeholders"; my $ins_sth =dbh->prepare($statement) -- cgit v1.2.1 From a131a3f3f8872ba175d4e213aac25624616e3b7d Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 10:26:29 +0000 Subject: W T F --- FS/FS/cust_main.pm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 73afe2183..b7545dee7 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -532,8 +532,11 @@ sub _copy_skel { my $ins_columns = ' ( '. join(', ', $primary_key, @ins_columns ). ' ) '; my $placeholders = ' ( ?, '. join(', ', map '?', @ins_columns ). ' ) '; - my $sel_sth = dbh->prepare( "SELECT $sel_columns FROM $child_table". - " WHERE $primary_key = $sourceid") + my $sel_st = "SELECT $sel_columns FROM $child_table". + " WHERE $primary_key = $sourceid"; + warn " $sel_st\n" + if $DEBUG > 2; + my $sel_sth = dbh->prepare( $sel_st ) or return dbh->errstr; $sel_sth->execute or return $sel_sth->errstr; -- cgit v1.2.1 From 78b0e4c62ec84a5e4082ee3d73f9825e498d3462 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 10:27:46 +0000 Subject: that was it, the sql had to be fixed... --- 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 b7545dee7..94564dc80 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -526,7 +526,7 @@ sub _copy_skel { my @sel_columns = grep { $_ ne $primary_key } dbdef->table($child_table)->columns; - my $sel_columns = ' ( '. join(', ', @sel_columns ). ' ) '; + my $sel_columns = join(', ', @sel_columns ); my @ins_columns = grep { $_ ne $child_pkey } @sel_columns; my $ins_columns = ' ( '. join(', ', $primary_key, @ins_columns ). ' ) '; -- cgit v1.2.1 From 25e1618079cfd1001b71288e2504e9eeb8887c05 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 10:31:41 +0000 Subject: want ALL of cust_main-skeleton tables config, not just the first line --- 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 94564dc80..8b0f1b5b4 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -480,7 +480,7 @@ sub start_copy_skel { #'mg_watchlist_header' => { 'mg_watchlist_details' }, #'mg_user_grid_header' => { 'mg_user_grid_details' }, #'mg_portfolio_header' => { 'mg_portfolio_trades' => { 'mg_portfolio_trades_positions' } }, - my @tables = eval($conf->config('cust_main-skeleton_tables')); + my @tables = eval($conf->config_binary('cust_main-skeleton_tables')); die $@ if $@; _copy_skel( 'cust_main', #tablename -- cgit v1.2.1 From a5fa93bd476f0f0e2c4ca265dc5a1ab20f7d26dd Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 10:34:44 +0000 Subject: suggestion to run dbdef-create here, yes... --- FS/FS/cust_main.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 8b0f1b5b4..f7e16cfd3 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -511,7 +511,8 @@ sub _copy_skel { my $sequence = ''; if ( keys %{ $child_tables{$child_table} } ) { - return "$child_table has no primary key\n" unless $child_pkey; + return "$child_table has no primary key". + " (or do you need to run dbdef-create?)\n" unless $child_pkey; #false laziness w/Record::insert and only works on Pg #refactor the proper last-inserted-id stuff out of Record::insert if this -- cgit v1.2.1 From 893ba3c4c659154ec34d30fb96853977a4479f09 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 11:36:57 +0000 Subject: allow explicitly specified primary keys (to get around big 8.1 Pg changes wrt reverse engineering --- FS/FS/cust_main.pm | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index f7e16cfd3..07b2c1157 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -476,10 +476,10 @@ sub start_copy_skel { my $self = shift; #'mg_user_preference' => {}, - #'mg_user_indicator_profile' => { 'mg_profile_indicator' => { 'mg_profile_details' }, }, - #'mg_watchlist_header' => { 'mg_watchlist_details' }, - #'mg_user_grid_header' => { 'mg_user_grid_details' }, - #'mg_portfolio_header' => { 'mg_portfolio_trades' => { 'mg_portfolio_trades_positions' } }, + #'mg_user_indicator_profile.user_indicator_profile_id' => { 'mg_profile_indicator.profile_indicator_id' => { 'mg_profile_details.profile_detail_id' }, }, + #'mg_watchlist_header.watchlist_header_id' => { 'mg_watchlist_details.watchlist_details_id' }, + #'mg_user_grid_header.grid_header_id' => { 'mg_user_grid_details.user_grid_details_id' }, + #'mg_portfolio_header.portfolio_header_id' => { 'mg_portfolio_trades.portfolio_trades_id' => { 'mg_portfolio_trades_positions.portfolio_trades_positions_id' } }, my @tables = eval($conf->config_binary('cust_main-skeleton_tables')); die $@ if $@; @@ -494,25 +494,40 @@ sub start_copy_skel { sub _copy_skel { my( $table, $sourceid, $destid, %child_tables ) = @_; - my $dbdef_table = dbdef->table($table); - my $primary_key = $dbdef_table->primary_key - or return "$table has no primary key". - " (or do you need to run dbdef-create?)"; + my $primary_key; + if ( $table =~ /^(\w+)\.(\w+)$/ ) { + ( $table, $primary_key ) = ( $1, $2 ); + } else { + my $dbdef_table = dbdef->table($table); + $primary_key = $dbdef_table->primary_key + or return "$table has no primary key". + " (or do you need to run dbdef-create?)"; + } warn " _copy_skel: $table.$primary_key $sourceid to $destid for ". join (', ', keys %child_tables). "\n" if $DEBUG > 2; - foreach my $child_table ( keys %child_tables ) { + foreach my $child_table_def ( keys %child_tables ) { + + my $child_table; + my $child_pkey = ''; + if ( $child_table =~ /^(\w+)\.(\w+)$/ ) { + ( $child_table, $child_pkey ) = ( $1, $2 ); + } else { + $child_table = $child_table_def; + + $child_pkey = dbdef->table($child_table)->primary_key; + # or return "$table has no primary key". + # " (or do you need to run dbdef-create?)\n"; + } - my $child_pkey = dbdef->table($child_table)->primary_key; - # or return "$table has no primary key". - # " (or do you need to run dbdef-create?)\n"; my $sequence = ''; if ( keys %{ $child_tables{$child_table} } ) { return "$child_table has no primary key". - " (or do you need to run dbdef-create?)\n" unless $child_pkey; + " (run dbdef-create or try specifying it?)\n" + unless $child_pkey; #false laziness w/Record::insert and only works on Pg #refactor the proper last-inserted-id stuff out of Record::insert if this @@ -572,7 +587,7 @@ sub _copy_skel { _copy_skel( $child_table, $row->{$child_pkey}, #sourceid $insertid, #destid - %{ $child_tables{$child_table} }, + %{ $child_tables{$child_table_def} }, ); return $error if $error; -- cgit v1.2.1 From bde46a1e81ab59c0068399903c5efe30c9ffc618 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 11:40:34 +0000 Subject: fix the explicitly specified primary keys --- 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 07b2c1157..e2bc95f70 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -512,7 +512,7 @@ sub _copy_skel { my $child_table; my $child_pkey = ''; - if ( $child_table =~ /^(\w+)\.(\w+)$/ ) { + if ( $child_table_def =~ /^(\w+)\.(\w+)$/ ) { ( $child_table, $child_pkey ) = ( $1, $2 ); } else { $child_table = $child_table_def; -- cgit v1.2.1 From 960253f3d34e0e782214b47cf4b71b3178360ffa Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 11:52:13 +0000 Subject: alas, now try with recursion --- 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 e2bc95f70..75ad1ef5c 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -523,7 +523,7 @@ sub _copy_skel { } my $sequence = ''; - if ( keys %{ $child_tables{$child_table} } ) { + if ( keys %{ $child_tables{$child_table_def} } ) { return "$child_table has no primary key". " (run dbdef-create or try specifying it?)\n" -- cgit v1.2.1 From aa69b255a6b5abe6117ad864b7521d498a618a3c Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 11:56:26 +0000 Subject: pass through the explicitly specified pkeys --- 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 75ad1ef5c..fd66f145c 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -584,7 +584,7 @@ sub _copy_skel { # don't drink soap! recurse! recurse! okay! my $error = - _copy_skel( $child_table, + _copy_skel( $child_table_def, $row->{$child_pkey}, #sourceid $insertid, #destid %{ $child_tables{$child_table_def} }, -- cgit v1.2.1 From 96301defaaafd4260c7282a3a4e1b4d29fcd85c8 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 18 Aug 2006 12:18:19 +0000 Subject: and a slight fix to the CSV import --- FS/FS/cust_main.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index fd66f145c..96241801e 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -4395,7 +4395,7 @@ sub batch_import { cust_pkg.pkgpart svc_acct.username svc_acct._password ); - $payby = 'CARD'; + $payby = 'BILL'; } else { die "unknown format $format"; } @@ -4484,6 +4484,8 @@ sub batch_import { } } + $cust_main{'payby'} = 'CARD' if length($cust_main{'payinfo'}); + my $invoicing_list = $cust_main{'invoicing_list'} ? [ delete $cust_main{'invoicing_list'} ] : []; -- cgit v1.2.1 From 0a632c6bfa376fbcfdf311122669991565877d2b Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 21 Aug 2006 09:47:49 +0000 Subject: we're off to see the wiki, the wonderful wiki of oz --- httemplate/docs/index.html | 2 +- httemplate/docs/install-rt.html | 78 ------------------ httemplate/docs/install.html | 173 ---------------------------------------- 3 files changed, 1 insertion(+), 252 deletions(-) delete mode 100644 httemplate/docs/install-rt.html delete mode 100644 httemplate/docs/install.html diff --git a/httemplate/docs/index.html b/httemplate/docs/index.html index 1693b6d9b..ee290ee0f 100644 --- a/httemplate/docs/index.html +++ b/httemplate/docs/index.html @@ -7,7 +7,7 @@

    Installation and upgrades

    • New Installation -
    • Installing integrated RT ticketing +
    • Installing integrated RT ticketing
    • Upgrading from 1.4.0 to 1.4.1
    • Upgrading from 1.4.1 to 1.4.2
    • Upgrading from 1.4.1 (or 1.4.2) to 1.5.8 diff --git a/httemplate/docs/install-rt.html b/httemplate/docs/install-rt.html deleted file mode 100644 index da0941a09..000000000 --- a/httemplate/docs/install-rt.html +++ /dev/null @@ -1,78 +0,0 @@ - - Installing integrated RT ticketing - - -

      Installing integrated RT ticketing

      - -

      Integrated ticketing is an new feature and these instructions are preliminary. Documentation contributions are welcome. - -

      There is also support for running this integration against an external RT installation, but it is not (yet) documented. - -

      Perl minimum version 5.8.3 is required. HTML::Mason is required. - -

      Install the following perl modules: -

      - -

      Create a new Unix group called 'rt' - -

      Edit the top-level Makefile, set RT_ENABLED to 1 and set the RT_DOMAIN, RT_TIMEZONE, and FREESIDE_URL variables. - -

      make configure-rt
      -make create-rt
      -make install-rt
      -
      - -

      Add the following to your httpd.conf: -

      -# replace /var/www/freeside with your freeside document root
      -<DirectoryMatch "^/var/www/freeside/rt/.*NoAuth">
      -<Limit GET POST>
      -allow from all
      -Satisfy any   
      -SetHandler perl-script
      -PerlHandler HTML::Mason
      -</Limit>
      -</DirectoryMatch>
      -# replace /var/www/freeside with your freeside document root
      -<DirectoryMatch "^/var/www/freeside/rt/.*NoAuth/images">
      -SetHandler None
      -</DirectoryMatch>
      -# replace /var/www/freeside with your freeside document root
      -<Directory /var/www/freeside/rt/Ticket/Attachment> 
      -SetHandler perl-script 
      -PerlHandler HTML::Mason 
      -</Directory>
      -
      - -

      Set the ticket_system configuration value to RT_Internal. You may also wish to set ticket_system-default_queueid once you have RT configured. - -

      Bootstrap RT's permissions: -

        -
      • Click on "Ticketing Main" on the Freeside main menu to auto-create an RT login for your username -
      • Run freeside-adduser -h /usr/local/etc/freeside/htpasswd root and set a (temporary) password -
      • Log into your Freeside installation as the "root" user you just created, by closing your browser or using https://root@yourmachone/freeside/ syntax. -
      • Click on "Ticketing Main" on the Freeside main menu. Click on "Configuration", then "Global", and then "User Rights". Grant the "SuperUser" right to your RT login. -
      • Remove the temporary "root" user from /usr/local/etc/freeside/mapsecrets and /usr/local/etc/freeside/htpasswd -
      - -

      Follow the regular RT documentation to configure RT, setup the mailgate, etc. - - diff --git a/httemplate/docs/install.html b/httemplate/docs/install.html deleted file mode 100644 index 78172e8e4..000000000 --- a/httemplate/docs/install.html +++ /dev/null @@ -1,173 +0,0 @@ - - Installation - - -

      Installation

      -Note: Install Freeside on a firewalled, private server, not a public (web, RADIUS, etc.) server.

      -Before installing, you need: - -Install the Freeside distribution: -
        -
      • Add the user and group `freeside' to your system. -
      • Allow the freeside user full access to the freeside database. -
          -
        • with PostgreSQL: -
          -$ su postgres (pgsql on some distributions)
          -$ createuser -P freeside
          -Enter password for user "freeside": 
          -Enter it again: 
          -Shall the new user be allowed to create databases? (y/n) y
          -Shall the new user be allowed to create more new users? (y/n) n
          -CREATE USER
          -
        • with MySQL: -
          -$ mysqladmin -u root password 'set_a_root_database_password'
          -$ mysql -u root -p
          -mysql> GRANT SELECT,INSERT,UPDATE,DELETE,INDEX,ALTER,CREATE,DROP on freeside.* TO freeside@localhost IDENTIFIED BY 'set_a_freeside_database_password';
          -
        - -
      • Edit the top-level Makefile: -
          -
        • Set DATASOURCE to your DBI data source, for example, DBI:Pg:dbname=freeside for PostgresSQL or DBI:mysql:freeside for MySQL. See the DBI manpage and the manpage for your DBD for the exact syntax of your DBI data source. -
        • Set DB_PASSWORD to the freeside database user's password. -
        -
      • Add the freeside database to your database engine: -
          -
        • with Postgres: -
          -$ su freeside
          -$ createdb -E sql_ascii freeside
          -
        • with MySQL: -
          -$ mysqladmin -u freeside -p create freeside 
          -
        -
      • Build and install the Perl modules: -
        -$ make perl-modules
        -$ su
        -# make install-perl-modules
        -
      • Create the necessary configuration files:
        -$ su
        -# make create-config
        -
        -
      • Run a separate iteration of Apache[-SSL] with mod_perl enabled as the freeside user. -
      • Edit the Makefile and set TEMPLATE to asp or mason. Also set FREESIDE_DOCUMENT_ROOT. -
      • Run make install-docs. -
      • Configure Apache: -
        -PerlModule HTML::Mason
        -# your freeside docuemnt root
        -<Directory /var/www/freeside>
        -<Files ~ (\.cgi|\.html)>
        -AddHandler perl-script .cgi .html
        -PerlHandler HTML::Mason
        -</Files>
        -<Perl>
        -require "/usr/local/etc/freeside/handler.pl";
        -</Perl>
        -</Directory>
        -
        -
      • Restrict access to this web interface - see the Apache documentation on user authentication. For example, to configure user authentication with mod_auth (flat files), add something like the following to your Apache httpd.conf file, adjusting for your actual paths: -
        -#your freeside document root
        -<Directory /var/www/freeside>
        -AuthName Freeside
        -AuthType Basic
        -AuthUserFile /usr/local/etc/freeside/htpasswd
        -require valid-user
        -</Directory>
        -
        -
      • Create one or more Freeside users (your internal sales/tech folks, not customer accounts). These users are setup using using Apache authentication, not UNIX user accounts. For example, using mod_auth (flat files): -
          -
        • First user: -
          $ su
          -# freeside-adduser -c -h /usr/local/etc/freeside/htpasswd username
          -
        • Additional users: -
          $ su
          -# freeside-adduser -h /usr/local/etc/freeside/htpasswd username
          -
        - (using other auth types, add each user to your Apache authentication and then run: freeside-adduser username) -
      • Create the Freeside system users: -
        $ su
        -# freeside-adduser fs_queue
        -# freeside-adduser fs_selfservice
        -
      • As the freeside UNIX user, run freeside-setup -d domain.name username to create the database tables and initial data, passing the username of a Freeside user you created above: -
        -$ su freeside
        -$ freeside-setup -d example.com username
        -
        -
      • freeside-queued was installed with the Perl modules. Start it now and ensure that is run upon system startup (Do this manually, or edit the top-level Makefile, replacing INIT_FILE with the appropriate location on your systemand QUEUED_USER with the username of a Freeside user you created above, and run make install-init) -
      • Now proceed to the initial administration of your installation. -
      - -- cgit v1.2.1 From 620cfcdb419e8bcdcbaecb2b011e5abd0ecc34e0 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 21 Aug 2006 12:38:15 +0000 Subject: better RT hint for smarter smart search --- rt/html/Ticket/Elements/EditCustomers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rt/html/Ticket/Elements/EditCustomers b/rt/html/Ticket/Elements/EditCustomers index 47d1aa222..c5a6f708c 100644 --- a/rt/html/Ticket/Elements/EditCustomers +++ b/rt/html/Ticket/Elements/EditCustomers @@ -43,7 +43,7 @@ <&|/l&>Find customer
      -
      cust #, last name, or company +
      cust #, name, company or phone
      %#
      %#<&|/l&>Find service
      -- cgit v1.2.1