From: ivan Date: Fri, 12 Apr 2002 13:22:03 +0000 (+0000) Subject: - should finish off the part_svc -> part_export s/one-to-many/many-to-many/ X-Git-Tag: freeside_1_4_0_pre12~95 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=e6ea57971831f25d682d97a0ba508c39b66ecd8b - should finish off the part_svc -> part_export s/one-to-many/many-to-many/ transition (closes: Bug#375) - fixes a nasty export scoping bug with message catalogs, whew --- diff --git a/FS/FS/Msgcat.pm b/FS/FS/Msgcat.pm new file mode 100644 index 000000000..625743dc0 --- /dev/null +++ b/FS/FS/Msgcat.pm @@ -0,0 +1,98 @@ +package FS::Msgcat; + +use strict; +use vars qw( @ISA @EXPORT_OK $conf $locale $debug ); +use Exporter; +use FS::UID; +#use FS::Record qw( qsearchs ); # wtf? won't import... +use FS::Record; +use FS::Conf; +use FS::msgcat; + +@ISA = qw(Exporter); +@EXPORT_OK = qw( gettext geterror ); + +$FS::UID::callback{'Msgcat'} = sub { + $conf = new FS::Conf; + $locale = $conf->config('locale') || 'en_US'; + $debug = $conf->exists('show-msgcat-codes') +}; + +=head1 NAME + +FS::Msgcat - Message catalog functions + +=head1 SYNOPSIS + + use FS::Msgcat qw(gettext geterror); + + #simple interface for retreiving messages... + $message = gettext('msgcode'); + #or errors (includes the error code) + $message = geterror('msgcode'); + +=head1 DESCRIPTION + +FS::Msgcat provides functions to use the message catalog. If you want to +maintain the message catalog database, see L instead. + +=head1 SUBROUTINES + +=over 4 + +=item gettext MSGCODE + +Returns the full message for the supplied message code. + +=cut + +sub gettext { + $debug ? geterror(@_) : _gettext(@_); +} + +sub _gettext { + my $msgcode = shift; + my $msgcat = FS::Record::qsearchs('msgcat', { + 'msgcode' => $msgcode, + 'locale' => $locale + } ); + if ( $msgcat ) { + $msgcat->msg; + } else { + warn "WARNING: message for msgcode $msgcode in locale $locale not found"; + $msgcode; + } + +} + +=item geterror MSGCODE + +Returns the full message for the supplied message code, including the message +code. + +=cut + +sub geterror { + my $msgcode = shift; + my $msg = _gettext($msgcode); + if ( $msg eq $msgcode ) { + "Error code $msgcode (message for locale $locale not found)"; + } else { + "$msg (error code $msgcode)"; + } +} + +=back + +=head1 BUGS + +i18n/l10n, eek + +=head1 SEE ALSO + +L, L, schema.html from the base documentation. + +=cut + +1; + diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 978ea72e7..cb42b266c 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -12,7 +12,7 @@ use DBI qw(:sql_types); use DBIx::DBSchema 0.19; use FS::UID qw(dbh checkruid getotaker datasrc driver_name); use FS::SearchCache; -use FS::msgcat qw(gettext); +use FS::Msgcat qw(gettext); @ISA = qw(Exporter); @EXPORT_OK = qw(dbh fields hfields qsearch qsearchs dbdef jsearch); @@ -572,7 +572,7 @@ sub delete { $h_sth->execute or return $h_sth->errstr if $h_sth; dbh->commit or croak dbh->errstr if $FS::UID::AutoCommit; - #no need to needlessly destoy the data either + #no need to needlessly destoy the data either (causes problems actually) #undef $self; #no need to keep object! ''; @@ -825,9 +825,12 @@ false. sub ut_text { my($self,$field)=@_; + warn "msgcat ". \&msgcat. "\n"; + warn "notexist ". \¬exist. "\n"; + warn "AUTOLOAD ". \&AUTOLOAD. "\n"; $self->getfield($field) =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/]+)$/ or return gettext('illegal_or_empty_text'). " $field: ". - $self->getfield($field); + $self->getfield($field); $self->setfield($field,$1); ''; } diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index e1a51732f..fd1ccd772 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -26,7 +26,7 @@ use FS::queue; use FS::part_pkg; use FS::part_bill_event; use FS::cust_bill_event; -use FS::msgcat qw(gettext); +use FS::Msgcat qw(gettext); @ISA = qw( FS::Record ); diff --git a/FS/FS/cust_main_invoice.pm b/FS/FS/cust_main_invoice.pm index b82e2e732..a5533a088 100644 --- a/FS/FS/cust_main_invoice.pm +++ b/FS/FS/cust_main_invoice.pm @@ -7,7 +7,7 @@ use FS::Record qw( qsearchs ); use FS::Conf; use FS::cust_main; use FS::svc_acct; -use FS::msgcat qw(gettext); +use FS::Msgcat qw(gettext); @ISA = qw( FS::Record ); @@ -170,7 +170,7 @@ sub address { =head1 VERSION -$Id: cust_main_invoice.pm,v 1.11 2002-04-10 13:42:48 ivan Exp $ +$Id: cust_main_invoice.pm,v 1.12 2002-04-12 13:22:02 ivan Exp $ =head1 BUGS diff --git a/FS/FS/msgcat.pm b/FS/FS/msgcat.pm index 3eca14b83..fa10d34fa 100644 --- a/FS/FS/msgcat.pm +++ b/FS/FS/msgcat.pm @@ -1,20 +1,12 @@ package FS::msgcat; use strict; -use vars qw( @ISA @EXPORT_OK $conf $locale $debug ); +use vars qw( @ISA ); use Exporter; use FS::UID; use FS::Record qw( qsearchs ); -use FS::Conf; @ISA = qw(FS::Record); -@EXPORT_OK = qw( gettext geterror ); - -$FS::UID::callback{'msgcat'} = sub { - $conf = new FS::Conf; - $locale = $conf->config('locale') || 'en_US'; - $debug = $conf->exists('show-msgcat-codes') -}; =head1 NAME @@ -22,14 +14,8 @@ FS::msgcat - Object methods for message catalog entries =head1 SYNOPSIS - use FS::msgcat qw(gettext); - - #simple interface for retreiving messages... - $message = gettext('msgcode'); - #or errors (includes the error code) - $message = geterror('msgcode'); + use FS::msgcat; - #maintenance stuff $record = new FS::msgcat \%hash; $record = new FS::msgcat { 'column' => 'value' }; @@ -58,6 +44,8 @@ from FS::Record. The following fields are currently supported: =back +If you just want to B message catalogs, see L. + =head1 METHODS =over 4 @@ -130,61 +118,13 @@ sub check { =back -=head1 SUBROUTINES - -=over 4 - -=item gettext MSGCODE - -Returns the full message for the supplied message code. - -=cut - -sub gettext { - $debug ? geterror(@_) : _gettext(@_); -} - -sub _gettext { - my $msgcode = shift; - my $msgcat = qsearchs('msgcat', { - 'msgcode' => $msgcode, - 'locale' => $locale - } ); - if ( $msgcat ) { - $msgcat->msg; - } else { - warn "WARNING: message for msgcode $msgcode in locale $locale not found"; - $msgcode; - } - -} - -=item geterror MSGCODE - -Returns the full message for the supplied message code, including the message -code. - -=cut - -sub geterror { - my $msgcode = shift; - my $msg = _gettext($msgcode); - if ( $msg eq $msgcode ) { - "Error code $msgcode (message for locale $locale not found)"; - } else { - "$msg (error code $msgcode)"; - } -} - -=back - =head1 BUGS i18n/l10n, eek =head1 SEE ALSO -L, schema.html from the base documentation. +L, L, schema.html from the base documentation. =cut diff --git a/FS/FS/part_export.pm b/FS/FS/part_export.pm index 8af413b1d..835f5318f 100644 --- a/FS/FS/part_export.pm +++ b/FS/FS/part_export.pm @@ -1,13 +1,15 @@ package FS::part_export; use strict; -use vars qw( @ISA ); +use vars qw( @ISA @EXPORT_OK %exports ); +use Exporter; use FS::Record qw( qsearch qsearchs dbh ); use FS::part_svc; use FS::part_export_option; use FS::export_svc; @ISA = qw(FS::Record); +@EXPORT_OK = qw(export_info); =head1 NAME @@ -428,10 +430,118 @@ sub _export_delete { =back +=head1 SUBROUTINES + +=over 4 + +=item export_info [ SVCDB ] + +Returns a hash reference of the exports for the given I, or if no +I is specified, for all exports. The keys of the hash are +Is and the values are again hash references containing information +on the export: + + 'desc' => 'Description', + 'options' => { + 'option' => { label=>'Option Label' }, + 'option2' => { label=>'Another label' }, + }, + 'nodomain' => 'Y', #or '' + 'notes' => 'Additional notes', + +=cut + +sub export_info { + #warn $_[0]; + return $exports{$_[0]} if @_; + #{ map { %{$exports{$_}} } keys %exports }; + my $r = { map { %{$exports{$_}} } keys %exports }; +} + +=item exporttype2svcdb EXPORTTYPE + +Returns the applicable I for an I. + +=cut + +sub exporttype2svcdb { + my $exporttype = $_[0]; + foreach my $svcdb ( keys %exports ) { + return $svcdb if grep { $exporttype eq $_ } keys %{$exports{$svcdb}}; + } + ''; +} + +%exports = ( + 'svc_acct' => { + 'sysvshell' => { + 'desc' => + 'Batch export of /etc/passwd and /etc/shadow files (Linux/SysV)', + 'options' => {}, + }, + 'bsdshell' => { + 'desc' => + 'Batch export of /etc/passwd and /etc/master.passwd files (BSD)', + 'options' => {}, + }, +# 'nis' => { +# 'desc' => +# 'Batch export of /etc/global/passwd and /etc/global/shadow for NIS ', +# 'options' => {}, +# }, + 'bsdshell' => { + 'desc' => + 'Batch export of /etc/passwd and /etc/master.passwd files (BSD)', + 'options' => {}, + }, + 'textradius' => { + 'desc' => 'Batch export of a text /etc/raddb/users file (Livingston, Cistron)', + }, + 'sqlradius' => { + 'desc' => 'Real-time export to SQL-backed RADIUS (ICRADIUS, FreeRADIUS)', + 'options' => { + 'datasrc' => { label=>'DBI data source' }, + 'username' => { label=>'Database username' }, + 'password' => { label=>'Database password' }, + }, + 'nodomain' => 'Y', + 'notes' => 'Not specifying datasrc will export to the freeside database? (no... notes on MySQL replication, DBI::Proxy, etc., from Conf.pm && export.html etc., reset with bin/sqlradius_reset', + }, + 'cyrus' => { + 'desc' => 'Real-time export to Cyrus IMAP server', + }, + 'cp' => { + 'desc' => 'Real-time export to Critical Path Account Provisioning Protocol', + }, + 'infostreet' => { + 'desc' => 'Real-time export to InfoStreet streetSmartAPI', + 'options' => { + 'url' => { label=>'XML-RPC Access URL', }, + 'login' => { label=>'InfoStreet login', }, + 'password' => { label=>'InfoStreet password', }, + 'groupID' => { label=>'InfoStreet groupID', }, + }, + 'nodomain' => 'Y', + 'notes' => 'Real-time export to InfoStreet streetSmartAPI. Requires installation of Frontier::Client from CPAN.', + } + }, + + 'svc_domain' => {}, + + 'svc_acct_sm' => {}, + + 'svc_forward' => {}, + + 'svc_www' => {}, + +); + +=back + =head1 NEW EXPORT CLASSES -Should be added to httemplate/edit/part_export.cgi and a module should -be FS/FS/part_export/ (an example may be found in eg/export_template.pm) +Should be added to the %export hash here, and a module should be added in +FS/FS/part_export/ (an example may be found in eg/export_template.pm) =head1 BUGS diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 030ffbe19..38e24c110 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -28,7 +28,7 @@ use FS::svc_domain; use FS::raddb; use FS::queue; use FS::radius_usergroup; -use FS::msgcat qw(gettext); +use FS::Msgcat qw(gettext); @ISA = qw( FS::svc_Common ); diff --git a/FS/MANIFEST b/FS/MANIFEST index 440c75565..1c90dfc0e 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -27,6 +27,7 @@ FS/UI/CGI.pm FS/UI/Gtk.pm FS/UI/agent.pm FS/UID.pm +FS/Msgcat.pm FS/agent.pm FS/agent_type.pm FS/cust_bill.pm @@ -80,6 +81,8 @@ t/CGI.t t/Conf.t t/ConfItem.t t/Record.t +t/UID.t +t/Msgcat.t t/cust_bill.t t/cust_bill_event.t t/cust_bill_pay.t @@ -124,5 +127,4 @@ t/type_pkgs.t t/queue.t t/queue_arg.t t/msgcat.t -t/UID.t t/raddb.t diff --git a/FS/t/Msgcat.t b/FS/t/Msgcat.t new file mode 100644 index 000000000..29e71b33c --- /dev/null +++ b/FS/t/Msgcat.t @@ -0,0 +1,5 @@ +BEGIN { $| = 1; print "1..1\n" } +END {print "not ok 1\n" unless $loaded;} +use FS::Msgcat; +$loaded=1; +print "ok 1\n"; diff --git a/fs_signup/fs_signup_server b/fs_signup/fs_signup_server index ebf424cd7..41fed9a14 100755 --- a/fs_signup/fs_signup_server +++ b/fs_signup/fs_signup_server @@ -13,7 +13,7 @@ use FS::Conf; use FS::Record qw( qsearch qsearchs ); use FS::cust_main_county; use FS::cust_main; -use FS::msgcat qw(gettext); +use FS::Msgcat qw(gettext); use vars qw( $opt $Debug ); diff --git a/htetc/global.asa b/htetc/global.asa index 057695183..62f454475 100644 --- a/htetc/global.asa +++ b/htetc/global.asa @@ -15,6 +15,7 @@ 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); +use FS::Msgcat qw(gettext geterror); use FS::agent; use FS::agent_type; @@ -48,7 +49,8 @@ use FS::svc_www; use FS::type_pkgs; use FS::part_export; use FS::part_export_option; -use FS::msgcat qw(gettext geterror); +use FS::export_svc; +use FS::msgcat; sub Script_OnStart { $Response->AddHeader('Pragma' => 'no-cache'); diff --git a/htetc/handler.pl b/htetc/handler.pl index 2f8146e3e..056efa88b 100644 --- a/htetc/handler.pl +++ b/htetc/handler.pl @@ -72,6 +72,7 @@ sub handler use FS::Conf; use FS::CGI qw(header menubar popurl table itable ntable idiot eidiot small_custview myexit); + use FS::Msgcat qw(gettext geterror); use FS::agent; use FS::agent_type; @@ -105,7 +106,8 @@ sub handler use FS::type_pkgs; use FS::part_export; use FS::part_export_option; - use FS::msgcat qw(gettext geterror); + use FS::export_svc; + use FS::msgcat; *CGI::redirect = sub { my( $self, $location ) = @_; diff --git a/httemplate/browse/part_svc.cgi b/httemplate/browse/part_svc.cgi index c8adebcd6..4c9b1bd95 100755 --- a/httemplate/browse/part_svc.cgi +++ b/httemplate/browse/part_svc.cgi @@ -66,8 +66,12 @@ function part_export_areyousure(href) { <%= $hashref->{svcdb} %> ><%= itable() %> <% - my @part_export = qsearch('part_export', { svcpart => $part_svc->svcpart } ); - foreach my $part_export ( @part_export ) { +# my @part_export = +map { qsearchs('part_export', { exportnum => $_->exportnum } ) } qsearch('export_svc', { svcpart => $part_svc->svcpart } ) ; + foreach my $part_export ( + map { qsearchs('part_export', { exportnum => $_->exportnum } ) } + qsearch('export_svc', { svcpart => $part_svc->svcpart } ) + ) { %> <%= $part_export->exporttype %> to <%= $part_export->machine %> diff --git a/httemplate/edit/part_export.cgi b/httemplate/edit/part_export.cgi index f3127403e..77b80d06f 100644 --- a/httemplate/edit/part_export.cgi +++ b/httemplate/edit/part_export.cgi @@ -21,76 +21,10 @@ if ( $cgi->param('error') ) { } $action ||= $part_export->exportnum ? 'Edit' : 'Add'; -my %exports = ( - 'svc_acct' => { - 'sysvshell' => { - 'desc' => - 'Batch export of /etc/passwd and /etc/shadow files (Linux/SysV)', - 'options' => {}, - }, - 'bsdshell' => { - 'desc' => - 'Batch export of /etc/passwd and /etc/master.passwd files (BSD)', - 'options' => {}, - }, -# 'nis' => { -# 'desc' => -# 'Batch export of /etc/global/passwd and /etc/global/shadow for NIS ', -# 'options' => {}, -# }, - 'bsdshell' => { - 'desc' => - 'Batch export of /etc/passwd and /etc/master.passwd files (BSD)', - 'options' => {}, - }, - 'textradius' => { - 'desc' => 'Batch export of a text /etc/raddb/users file (Livingston, Cistron)', - }, - 'sqlradius' => { - 'desc' => 'Real-time export to SQL-backed RADIUS (ICRADIUS, FreeRADIUS)', - 'options' => { - 'datasrc' => { label=>'DBI data source' }, - 'username' => { label=>'Database username' }, - 'password' => { label=>'Database password' }, - }, - 'nodomain' => 'Y', - 'notes' => 'Not specifying datasrc will export to the freeside database? (no... notes on MySQL replication, DBI::Proxy, etc., from Conf.pm && export.html etc., reset with bin/sqlradius_reset', - }, - 'cyrus' => { - 'desc' => 'Real-time export to Cyrus IMAP server', - }, - 'cp' => { - 'desc' => 'Real-time export to Critical Path Account Provisioning Protocol', - }, - 'infostreet' => { - 'desc' => 'Real-time export to InfoStreet streetSmartAPI', - 'options' => { - 'url' => { label=>'XML-RPC Access URL', }, - 'login' => { label=>'InfoStreet login', }, - 'password' => { label=>'InfoStreet password', }, - 'groupID' => { label=>'InfoStreet groupID', }, - }, - 'nodomain' => 'Y', - 'notes' => 'Real-time export to InfoStreet streetSmartAPI. Requires installation of Frontier::Client from CPAN.', - } - }, - - 'svc_domain' => {}, - - 'svc_acct_sm' => {}, - - 'svc_forward' => {}, - - 'svc_www' => {}, - -); - -#my $svcdb = $part_export->part_svc->svcdb; -#YUCK -my $svcdb = 'svc_acct'; +#my $exports = FS::part_export::export_info($svcdb); +my $exports = FS::part_export::export_info(); -my %layers = map { $_ => "$_ - ". $exports{$svcdb}{$_}{desc} } - keys %{$exports{$svcdb}}; +my %layers = map { $_ => "$_ - ". $exports->{$_}{desc} } keys %$exports; $layers{''}=''; my $widget = new HTML::Widgets::SelectLayers( @@ -105,9 +39,9 @@ my $widget = new HTML::Widgets::SelectLayers( my $layer = shift; my $html = qq!!. ntable("#cccccc",2); - foreach my $option ( keys %{$exports{$svcdb}->{$layer}{options}} ) { + foreach my $option ( keys %{$exports->{$layer}{options}} ) { # foreach my $option ( qw(url login password groupID ) ) { - my $optinfo = $exports{$svcdb}->{$layer}{options}{$option}; + my $optinfo = $exports->{$layer}{options}{$option}; my $label = $optinfo->{label}; my $value = $cgi->param($option) || $part_export->option($option); $html .= qq!$label!. @@ -117,10 +51,10 @@ my $widget = new HTML::Widgets::SelectLayers( $html .= ''; $html .= ''; + join(',', keys %{$exports->{$layer}{options}} ). '">'; $html .= ''; + $exports->{$layer}{nodomain}. '">'; $html .= ' [ 'disabled' ], 'layer_callback' => sub { my $layer = shift; - my $html = qq!!. - table(). "FieldModifier"; + my $html = qq!!; + + my $columns = 3; + my $count = 0; + my @part_export = + grep { $layer eq FS::part_export::exporttype2svcdb($_->exporttype) } + qsearch( 'part_export', {} ); + $html .= '

'. table(). + table(). "Exports"; + foreach my $part_export ( @part_export ) { + $html .= ' $part_export->exportnum, + svcpart => $part_svc->svcpart }); + $html .= '> '. $part_export->exporttype. ' to '. $part_export->machine. + ''; + $count++; + $html .= '' unless $count % $columns; + } + $html .= '

'; + + $html .= table(). "FieldModifier"; #yucky kludge my @fields = defined( $FS::Record::dbdef->table($layer) ) ? grep { $_ ne 'svcnum' } fields($layer) diff --git a/httemplate/edit/process/agent_type.cgi b/httemplate/edit/process/agent_type.cgi index 67aacfdd5..516594573 100755 --- a/httemplate/edit/process/agent_type.cgi +++ b/httemplate/edit/process/agent_type.cgi @@ -22,6 +22,7 @@ if ( $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'); diff --git a/httemplate/edit/process/part_export.cgi b/httemplate/edit/process/part_export.cgi index 34eb699bf..6b4d007e4 100644 --- a/httemplate/edit/process/part_export.cgi +++ b/httemplate/edit/process/part_export.cgi @@ -5,7 +5,7 @@ my $exportnum = $cgi->param('exportnum'); my $old = qsearchs('part_export', { 'exportnum'=>$exportnum } ) if $exportnum; #fixup options -warn join('-', split(',',$cgi->param('options'))); +#warn join('-', split(',',$cgi->param('options'))); my %options = map { $_=>$cgi->param($_) } split(',',$cgi->param('options')); my $new = new FS::part_export ( { @@ -16,9 +16,9 @@ my $new = new FS::part_export ( { my $error; if ( $exportnum ) { - warn $old; - warn $exportnum; - warn $new->machine; + #warn $old; + #warn $exportnum; + #warn $new->machine; $error = $new->replace($old,\%options); } else { $error = $new->insert(\%options); diff --git a/httemplate/edit/process/part_svc.cgi b/httemplate/edit/process/part_svc.cgi index 31ab13438..859670b17 100755 --- a/httemplate/edit/process/part_svc.cgi +++ b/httemplate/edit/process/part_svc.cgi @@ -33,6 +33,29 @@ if ( $error ) { $cgi->param('error', $error); print $cgi->redirect(popurl(2). "part_svc.cgi?". $cgi->query_string ); } else { + + #false laziness w/ edit/process/agent_type.cgi + foreach my $part_export (qsearch('part_export',{})) { + my $exportnum = $part_export->exportnum; + my $export_svc = qsearchs('export_svc', { + 'exportnum' => $part_export->exportnum, + 'svcpart' => $new->svcpart, + } ); + if ( $export_svc && ! $cgi->param("exportnum". $part_export->exportnum) ) { + $error = $export_svc->delete; + die $error if $error; + } elsif ( $cgi->param("exportnum". $part_export->exportnum) + && ! $export_svc ) { + $export_svc = new FS::export_svc ( { + 'exportnum' => $part_export->exportnum, + 'svcpart' => $new->svcpart, + } ); + $error = $export_svc->insert; + die $error if $error; + } + + } + print $cgi->redirect(popurl(3)."browse/part_svc.cgi"); } diff --git a/httemplate/misc/delete-customer.cgi b/httemplate/misc/delete-customer.cgi index 5088fef7b..7016c9166 100755 --- a/httemplate/misc/delete-customer.cgi +++ b/httemplate/misc/delete-customer.cgi @@ -43,4 +43,18 @@ canceled service with you. For that, cancel all of the customer's packages. END +#Deleting a customer you have financial records on (i.e. credits) is +#typically considered fraudulant bookkeeping. Remember, deleting +#customers should ONLY be used for completely bogus records. You should +#NOT delete real customers who simply discontinue service. +# +#For real customers who simply discontinue service, cancel all of the +#customer's packages. Customers with all cancelled packages are not +#billed. There is no need to take further action to prevent billing on +#customers with all cancelled packages. +# +#Also see the "hidecancelledcustomers" and "hidecancelledpackages" +#configuration options, which will allow you to surpress the display of +#cancelled customers and packages, respectively. + %>