From 27625d6ad7411e16c118e328514391cef0b6c110 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 3 Aug 2010 02:11:30 +0000 Subject: fix active customers sometimes showing in search results for new "ordered" status, RT#9381 --- FS/FS/cust_main.pm | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 002b0c1d1..47eccd7f8 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -7488,7 +7488,8 @@ recurring packages not yet setup). =cut sub ordered_sql { - " 0 < ( $select_count_pkgs AND ". FS::cust_pkg->ordered_sql. " ) "; + FS::cust_main->none_active_sql. + " AND 0 < ( $select_count_pkgs AND ". FS::cust_pkg->ordered_sql. " ) "; } =item active_sql @@ -7502,6 +7503,18 @@ sub active_sql { " 0 < ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " ) "; } +=item none_active_sql + +Returns an SQL expression identifying cust_main records with no active +recurring packages. This includes customers of status prospect, ordered, +inactive, and suspended. + +=cut + +sub none_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 @@ -7509,11 +7522,10 @@ no active recurring packages, but otherwise unsuspended/uncancelled). =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. " ) -"; } +sub inactive_sql { + FS::cust_main->none_active_sql. + " AND 0 < ( $select_count_pkgs AND ". FS::cust_pkg->inactive_sql. " ) "; +} =item susp_sql =item suspended_sql @@ -7524,11 +7536,10 @@ Returns an SQL expression identifying suspended cust_main records. sub suspended_sql { susp_sql(@_); } -sub susp_sql { " - 0 < ( $select_count_pkgs AND ". FS::cust_pkg->suspended_sql. " ) - AND - 0 = ( $select_count_pkgs AND ". FS::cust_pkg->active_sql. " ) -"; } +sub susp_sql { + FS::cust_main->none_active_sql. + " AND 0 < ( $select_count_pkgs AND ". FS::cust_pkg->suspended_sql. " ) "; +} =item cancel_sql =item cancelled_sql -- cgit v1.2.1 From 720cf723d2c8e88760704e2fdc50ebf48e0574f2 Mon Sep 17 00:00:00 2001 From: mark Date: Tue, 3 Aug 2010 03:30:20 +0000 Subject: customer view tab for an external info page, RT#8903 --- FS/FS/Conf.pm | 13 +++++++++++++ FS/FS/Mason.pm | 1 + 2 files changed, 14 insertions(+) (limited to 'FS') diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index ce8bd296e..fdb6e9a38 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -3891,6 +3891,19 @@ and customer address. Include units.', 'type' => 'checkbox', }, + { + 'key' => 'cust_main-custom_link', + 'section' => 'UI', + 'description' => 'URL to use as source for the "Custom" tab in the View Customer page. The custnum will be appended.', + 'type' => 'text', + }, + + { + 'key' => 'cust_main-custom_title', + 'section' => 'UI', + 'description' => 'Title for the "Custom" tab in the View Customer page.', + 'type' => 'text', + }, { key => "apacheroot", section => "deprecated", description => "DEPRECATED", type => "text" }, { key => "apachemachine", section => "deprecated", description => "DEPRECATED", type => "text" }, diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 0f1415009..bcf727e3c 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -111,6 +111,7 @@ if ( -e $addl_handler_use_file ) { #selectlayers.html use Locale::Country; use Business::US::USPS::WebTools::AddressStandardization; + use LWP::UserAgent; use FS; use FS::UID qw( getotaker dbh datasrc driver_name ); use FS::Record qw( qsearch qsearchs fields dbdef -- cgit v1.2.1 From dbab5e19dd56c2439a7c74c1dad5dcbd43e82865 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 3 Aug 2010 06:31:09 +0000 Subject: fix problem with expiring discounts, RT#6679 --- FS/FS/part_pkg/flat.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index 6db6eee24..cc2310503 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -192,7 +192,7 @@ sub calc_discount { my $months = $discount->months ? min( $chg_months, - $discount->months - $cust_pkg->months_used ) + $discount->months - $cust_pkg_discount->months_used ) : $chg_months; my $error = $cust_pkg_discount->increment_months_used($months); -- cgit v1.2.1 From f07fc98b146cdaa5861d766cc7c84ea0136f38ec Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 4 Aug 2010 01:15:52 +0000 Subject: better serialization on debugging data, RT#7514 --- FS/FS/part_export/communigate_pro.pm | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm index 3ac0dfd9c..76fd60815 100644 --- a/FS/FS/part_export/communigate_pro.pm +++ b/FS/FS/part_export/communigate_pro.pm @@ -732,7 +732,11 @@ sub export_getsettings_svc_domain { { my $value = $effective_settings->{$key}; if ( ref($value) eq 'ARRAY' ) { - $effective_settings->{$key} = join(' ', @$value); + $effective_settings->{$key} = + join(' ', map { ref($_) ? '['.join(', ', @$_).']' : $_ } @$value ); + } elsif ( ref($value) eq 'HASH' ) { + $effective_settings->{$key} = + join(', ', map { "$_:".$value->{$_} } keys %$value ); } else { #XXX warn "serializing ". ref($value). " for table display not yet handled"; @@ -821,7 +825,11 @@ sub export_getsettings_svc_acct { { my $value = $effective_settings->{$key}; if ( ref($value) eq 'ARRAY' ) { - $effective_settings->{$key} = join(' ', @$value); + $effective_settings->{$key} = + join(' ', map { ref($_) ? '['.join(', ', @$_).']' : $_ } @$value ); + } elsif ( ref($value) eq 'HASH' ) { + $effective_settings->{$key} = + join(', ', map { "$_:".$value->{$_} } keys %$value ); } else { #XXX warn "serializing ". ref($value). " for table display not yet handled"; -- cgit v1.2.1 From 6dcc4e277f46157a3df6c5d0b7ebde0fb848bb0b Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 4 Aug 2010 01:22:34 +0000 Subject: better serialization on debugging data, RT#7514 --- FS/FS/part_export/communigate_pro.pm | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm index 76fd60815..a3847bf2e 100644 --- a/FS/FS/part_export/communigate_pro.pm +++ b/FS/FS/part_export/communigate_pro.pm @@ -823,17 +823,7 @@ sub export_getsettings_svc_acct { foreach my $key ( grep ref($effective_settings->{$_}), keys %$effective_settings ) { - my $value = $effective_settings->{$key}; - if ( ref($value) eq 'ARRAY' ) { - $effective_settings->{$key} = - join(' ', map { ref($_) ? '['.join(', ', @$_).']' : $_ } @$value ); - } elsif ( ref($value) eq 'HASH' ) { - $effective_settings->{$key} = - join(', ', map { "$_:".$value->{$_} } keys %$value ); - } else { - #XXX - warn "serializing ". ref($value). " for table display not yet handled"; - } + $effective_settings->{$key} = _pretty( $effective_settings->{$key} ); } %{$settingsref} = %$effective_settings; @@ -843,6 +833,22 @@ sub export_getsettings_svc_acct { } +sub _pretty { + my $value = shift; + if ( ref($value) eq 'ARRAY' ) { + '['. join(' ', map { ref($_) ? _pretty($_) : $_ } @$value ). ']'; + } elsif ( ref($value) eq 'HASH' ) { + my $hv = $value->{$_}; + join(', ', map { my $v = $value->{$_}; + "$_:". ref($v) ? _pretty($v) : $_ + } + keys %$value + ); + } else { + warn "serializing ". ref($value). " for table display not yet handled"; + } +} + sub export_getsettings_svc_forward { my($self, $svc_forward, $settingsref, $defaultref ) = @_; -- cgit v1.2.1 From b80e90fc31364d7cad415d4c236c8429ec7c1e00 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 4 Aug 2010 01:23:05 +0000 Subject: better serialization on debugging data, RT#7514 --- FS/FS/part_export/communigate_pro.pm | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm index a3847bf2e..3d7d76ce3 100644 --- a/FS/FS/part_export/communigate_pro.pm +++ b/FS/FS/part_export/communigate_pro.pm @@ -730,17 +730,7 @@ sub export_getsettings_svc_domain { foreach my $key ( grep ref($effective_settings->{$_}), keys %$effective_settings ) { - my $value = $effective_settings->{$key}; - if ( ref($value) eq 'ARRAY' ) { - $effective_settings->{$key} = - join(' ', map { ref($_) ? '['.join(', ', @$_).']' : $_ } @$value ); - } elsif ( ref($value) eq 'HASH' ) { - $effective_settings->{$key} = - join(', ', map { "$_:".$value->{$_} } keys %$value ); - } else { - #XXX - warn "serializing ". ref($value). " for table display not yet handled"; - } + $effective_settings->{$key} = _pretty( $effective_settings->{$key} ); } %{$settingsref} = %$effective_settings; -- cgit v1.2.1 From 6f5d1fb4f6d927fed8171e93559e96644406729f Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 4 Aug 2010 01:26:32 +0000 Subject: better serialization on debugging data, RT#7514 --- FS/FS/part_export/communigate_pro.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm index 3d7d76ce3..86ae105b3 100644 --- a/FS/FS/part_export/communigate_pro.pm +++ b/FS/FS/part_export/communigate_pro.pm @@ -828,9 +828,8 @@ sub _pretty { if ( ref($value) eq 'ARRAY' ) { '['. join(' ', map { ref($_) ? _pretty($_) : $_ } @$value ). ']'; } elsif ( ref($value) eq 'HASH' ) { - my $hv = $value->{$_}; join(', ', map { my $v = $value->{$_}; - "$_:". ref($v) ? _pretty($v) : $_ + "$_:". ( ref($v) ? _pretty($v) : $_ ); } keys %$value ); -- cgit v1.2.1 From 01472ccb7460f5be007f11ae054c46844b3a7300 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 4 Aug 2010 01:29:09 +0000 Subject: better serialization on debugging data, RT#7514 --- FS/FS/part_export/communigate_pro.pm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm index 86ae105b3..90e697705 100644 --- a/FS/FS/part_export/communigate_pro.pm +++ b/FS/FS/part_export/communigate_pro.pm @@ -828,11 +828,12 @@ sub _pretty { if ( ref($value) eq 'ARRAY' ) { '['. join(' ', map { ref($_) ? _pretty($_) : $_ } @$value ). ']'; } elsif ( ref($value) eq 'HASH' ) { - join(', ', map { my $v = $value->{$_}; - "$_:". ( ref($v) ? _pretty($v) : $_ ); - } - keys %$value - ); + '{'. join(', ', + map { my $v = $value->{$_}; + "$_:". ( ref($v) ? _pretty($v) : $_ ); + } + keys %$value + ). '}'; } else { warn "serializing ". ref($value). " for table display not yet handled"; } -- cgit v1.2.1 From df08772b72f5a9231afc90eb12591a62dfdfbf9e Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 4 Aug 2010 01:31:28 +0000 Subject: better serialization on debugging data, RT#7514 --- FS/FS/part_export/communigate_pro.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm index 90e697705..2cad24467 100644 --- a/FS/FS/part_export/communigate_pro.pm +++ b/FS/FS/part_export/communigate_pro.pm @@ -830,7 +830,7 @@ sub _pretty { } elsif ( ref($value) eq 'HASH' ) { '{'. join(', ', map { my $v = $value->{$_}; - "$_:". ( ref($v) ? _pretty($v) : $_ ); + "$_:". ( ref($v) ? _pretty($v) : $v ); } keys %$value ). '}'; -- cgit v1.2.1 From 543cb4d548e42826e377c4790e28bb730d7ddf66 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 4 Aug 2010 18:50:08 +0000 Subject: fix scalar_sql not to return empty string for zero --- FS/FS/Record.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index cd5e2d4ca..44bc28d46 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -2817,7 +2817,8 @@ sub scalar_sql { my $sth = dbh->prepare($sql) or die dbh->errstr; $sth->execute or die "Unexpected error executing statement $sql: ". $sth->errstr; - $sth->fetchrow_arrayref->[0] || ''; + my $scalar = $sth->fetchrow_arrayref->[0]; + defined($scalar) ? $scalar : ''; } =back -- cgit v1.2.1 From 42a1267af992831cb8069835a18b8672a5f9afcb Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 4 Aug 2010 19:14:50 +0000 Subject: show cust_pay_pending attempted payments on customer payment history, RT#8815 --- FS/FS/Record.pm | 17 +++++++++-------- FS/FS/cust_main.pm | 47 ++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 13 deletions(-) (limited to 'FS') diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 44bc28d46..bc075dde9 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -2801,21 +2801,22 @@ sub h_date { $h ? $h->history_date : ''; } -=item scalar_sql SQL +=item scalar_sql SQL [ PLACEHOLDER, ... ] -A class method with a propensity for becoming an instance method. This -method executes the sql statement represented by SQL and returns a scalar -representing the result. Don't ask for rows -- you get the first column -of the first row. Don't give me bogus SQL or I'll die on you. +A class or object method. Executes the sql statement represented by SQL and +returns a scalar representing the result: the first column of the first row. -Returns an empty string in the event of no rows. +Dies on bogus SQL. Returns an empty string if no row is returned. + +Typically used for statments which return a single value such as "SELECT +COUNT(*) FROM table WHERE something" OR "SELECT column FROM table WHERE key = ?" =cut sub scalar_sql { - my($self, $sql ) = ( shift, shift ); + my($self, $sql) = (shift, shift); my $sth = dbh->prepare($sql) or die dbh->errstr; - $sth->execute + $sth->execute(@_) or die "Unexpected error executing statement $sql: ". $sth->errstr; my $scalar = $sth->fetchrow_arrayref->[0]; defined($scalar) ? $scalar : ''; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 47eccd7f8..b1bf1791c 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -7097,6 +7097,26 @@ sub cust_pay_pending { ); } +=item cust_pay_pending_attempt + +Returns all payment attempts / declined payments for this customer, as pending +payments objects (see L), with status "done" but without +a corresponding payment (see L). + +=cut + +sub cust_pay_pending_attempt { + my $self = shift; + return $self->num_cust_pay_pending_attempt unless wantarray; + sort { $a->_date <=> $b->_date } + qsearch( 'cust_pay_pending', { + 'custnum' => $self->custnum, + 'status' => 'done', + 'paynum' => '', + }, + ); +} + =item num_cust_pay_pending Returns the number of pending payments (see L) for this @@ -7107,11 +7127,28 @@ cust_pay_pending method is used in a scalar context. sub num_cust_pay_pending { my $self = shift; - my $sql = " SELECT COUNT(*) FROM cust_pay_pending ". - " WHERE custnum = ? AND status != 'done' "; - my $sth = dbh->prepare($sql) or die dbh->errstr; - $sth->execute($self->custnum) or die $sth->errstr; - $sth->fetchrow_arrayref->[0]; + $self->scalar_sql( + " SELECT COUNT(*) FROM cust_pay_pending ". + " WHERE custnum = ? AND status != 'done' ", + $self->custnum + ); +} + +=item num_cust_pay_pending_attempt + +Returns the number of pending payments (see L) for this +customer, with status "done" but without a corresp. Also called automatically when the +cust_pay_pending method is used in a scalar context. + +=cut + +sub num_cust_pay_pending_attempt { + my $self = shift; + $self->scalar_sql( + " SELECT COUNT(*) FROM cust_pay_pending ". + " WHERE custnum = ? AND status = 'done' AND paynum IS NULL", + $self->custnum + ); } =item cust_refund -- cgit v1.2.1 From 824b97e97e4e5ee914a4c936815a4413def71a4f Mon Sep 17 00:00:00 2001 From: mark Date: Thu, 5 Aug 2010 00:24:57 +0000 Subject: error message in decline templates, RT#9507 --- FS/FS/cust_main.pm | 5 ++++- FS/FS/msg_template.pm | 25 +++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index b1bf1791c..5898a6a07 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -5116,8 +5116,11 @@ sub _realtime_bop_result { my $msgnum = $conf->config('decline_msgnum', $self->agentnum); my $error = ''; if ( $msgnum ) { + # include the raw error message in the transaction state + $cust_pay_pending->setfield('error', $transaction->error_message); my $msg_template = qsearchs('msg_template', { msgnum => $msgnum }); - $error = $msg_template->send( 'cust_main' => $self ); + $error = $msg_template->send( 'cust_main' => $self, + 'object' => $cust_pay_pending ); } else { #!$msgnum diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm index 7d507f4fb..d1db17dbc 100644 --- a/FS/FS/msg_template.pm +++ b/FS/FS/msg_template.pm @@ -166,7 +166,7 @@ Customer object (required). =item object Additional context object (currently, can be a cust_main, cust_pkg, -cust_bill, svc_acct, or cust_pay object). +cust_bill, svc_acct, cust_pay, or cust_pay_pending object). =back @@ -324,6 +324,9 @@ sub substitutions { [ company_name => sub { $conf->config('company_name', shift->agentnum) } ], + [ company_address => sub { + $conf->config('company_address', shift->agentnum) + } ], ], # next_bill_date 'cust_pkg' => [qw( @@ -351,11 +354,13 @@ sub substitutions { )], #XXX not really thinking about cust_bill substitutions quite yet + # for welcome and limit warning messages 'svc_acct' => [qw( username ), [ password => sub { shift->getfield('_password') } ], - ], # for welcome messages + ], + # for payment receipts 'cust_pay' => [qw( paynum _date @@ -370,6 +375,22 @@ sub substitutions { $cust_pay->paymask : $cust_pay->decrypt($cust_pay->payinfo) } ], ], + # for payment decline messages + # try to support all cust_pay fields + # 'error' is a special case, it contains the raw error from the gateway + 'cust_pay_pending' => [qw( + _date + error + ), + [ paid => sub { sprintf("%.2f", shift->paid) } ], + [ payby => sub { FS::payby->shortname(shift->payby) } ], + [ date => sub { time2str("%a %B %o, %Y", shift->_date) } ], + [ payinfo => sub { + my $pending = shift; + ($pending->payby eq 'CARD' || $pending->payby eq 'CHEK') ? + $pending->paymask : $pending->decrypt($pending->payinfo) + } ], + ], }; } -- cgit v1.2.1 From b90c466e6b0d4477855ff3ff7b8f40937b129364 Mon Sep 17 00:00:00 2001 From: jeff Date: Thu, 5 Aug 2010 04:17:07 +0000 Subject: add options to only process account records from a particular realm and to ignore sessions that span billing periods RT8082 --- FS/FS/cust_svc.pm | 43 +++++++++++++++++--- FS/FS/part_export/sqlradius.pm | 90 ++++++++++++++++++++++++++---------------- 2 files changed, 95 insertions(+), 38 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index c0766e582..7b866fad3 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -539,15 +539,24 @@ sub seconds_since_sqlradacct { warn "$mes finding closed sessions completely within the given range\n" if $DEBUG; + my $realm = ''; + my $realmparam = ''; + if ($part_export->option('process_single_realm')) { + $realm = 'AND Realm = ?'; + $realmparam = $part_export->option('realm'); + } + my $sth = $dbh->prepare("SELECT SUM(acctsessiontime) FROM radacct WHERE UserName = ? + $realm AND $str2time AcctStartTime) >= ? AND $str2time AcctStopTime ) < ? AND $str2time AcctStopTime ) > 0 AND AcctStopTime IS NOT NULL" ) or die $dbh->errstr; - $sth->execute($username, $start, $end) or die $sth->errstr; + $sth->execute($username, ($realm ? $realmparam : ()), $start, $end) + or die $sth->errstr; my $regular = $sth->fetchrow_arrayref->[0]; warn "$mes finding open sessions which start in the range\n" @@ -557,13 +566,19 @@ sub seconds_since_sqlradacct { $query = "SELECT SUM( ? - $str2time AcctStartTime ) ) FROM radacct WHERE UserName = ? + $realm AND $str2time AcctStartTime ) >= ? AND $str2time AcctStartTime ) < ? AND ( ? - $str2time AcctStartTime ) ) < 86400 AND ( $str2time AcctStopTime ) = 0 OR AcctStopTime IS NULL )"; $sth = $dbh->prepare($query) or die $dbh->errstr; - $sth->execute($end, $username, $start, $end, $end) + $sth->execute( $end, + $username, + ($realm ? $realmparam : ()), + $start, + $end, + $end ) or die $sth->errstr. " executing query $query"; my $start_during = $sth->fetchrow_arrayref->[0]; @@ -574,13 +589,20 @@ sub seconds_since_sqlradacct { $sth = $dbh->prepare("SELECT SUM( $str2time AcctStopTime ) - ? ) FROM radacct WHERE UserName = ? + $realm AND $str2time AcctStartTime ) < ? AND $str2time AcctStopTime ) >= ? AND $str2time AcctStopTime ) < ? AND $str2time AcctStopTime ) > 0 AND AcctStopTime IS NOT NULL" ) or die $dbh->errstr; - $sth->execute($start, $username, $start, $start, $end ) or die $sth->errstr; + $sth->execute( $start, + $username, + ($realm ? $realmparam : ()), + $start, + $start, + $end ) + or die $sth->errstr; my $end_during = $sth->fetchrow_arrayref->[0]; warn "$mes finding closed sessions which start before the range but stop after\n" @@ -591,13 +613,15 @@ sub seconds_since_sqlradacct { $sth = $dbh->prepare("SELECT COUNT(*) FROM radacct WHERE UserName = ? + $realm AND $str2time AcctStartTime ) < ? AND ( $str2time AcctStopTime ) >= ? )" # OR AcctStopTime = 0 # OR AcctStopTime IS NULL )" ) or die $dbh->errstr; - $sth->execute($username, $start, $end ) or die $sth->errstr; + $sth->execute($username, ($realm ? $realmparam : ()), $start, $end ) + or die $sth->errstr; my $entire_range = ($end-$start) * $sth->fetchrow_arrayref->[0]; $seconds += $regular + $end_during + $start_during + $entire_range; @@ -658,14 +682,23 @@ sub attribute_since_sqlradacct { warn "$mes SUMing $attrib sessions\n" if $DEBUG; + my $realm = ''; + my $realmparam = ''; + if ($part_export->option('process_single_realm')) { + $realm = 'AND Realm = ?'; + $realmparam = $part_export->option('realm'); + } + my $sth = $dbh->prepare("SELECT SUM($attrib) FROM radacct WHERE UserName = ? + $realm AND $str2time AcctStopTime ) >= ? AND $str2time AcctStopTime ) < ? AND AcctStopTime IS NOT NULL" ) or die $dbh->errstr; - $sth->execute($username, $start, $end) or die $sth->errstr; + $sth->execute($username, ($realm ? $realmparam : ()), $start, $end) + or die $sth->errstr; my $row = $sth->fetchrow_arrayref; $sum += $row->[0] if defined($row->[0]); diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm index 4f67ac6c3..d8c5e0424 100644 --- a/FS/FS/part_export/sqlradius.pm +++ b/FS/FS/part_export/sqlradius.pm @@ -22,6 +22,15 @@ tie %options, 'Tie::IxHash', type => 'checkbox', label => 'Ignore accounting records from this database' }, + 'process_single_realm' => { + type => 'checkbox', + label => 'Only process one realm of accounting records', + }, + 'realm' => { label => 'The realm of of accounting records to be processed' }, + 'ignore_long_sessions' => { + type => 'checkbox', + label => 'Ignore sessions which span billing periods', + }, 'hide_ip' => { type => 'checkbox', label => 'Hide IP address information on session reports', @@ -617,7 +626,7 @@ sub usage_sessions { if ( $svc_acct ) { my $username = $self->export_username($svc_acct); - if ( $svc_acct =~ /^([^@]+)\@([^@]+)$/ ) { + if ( $username =~ /^([^@]+)\@([^@]+)$/ ) { push @where, '( UserName = ? OR ( UserName = ? AND Realm = ? ) )'; push @param, $username, $1, $2; } else { @@ -626,6 +635,11 @@ sub usage_sessions { } } + if ($self->option('process_single_realm')) { + push @where, 'Realm = ?'; + push @param, $self->option('realm'); + } + if ( length($ip) ) { push @where, ' FramedIPAddress = ?'; push @param, $ip; @@ -719,43 +733,53 @@ sub update_svc { my $oldAutoCommit = $FS::UID::AutoCommit; # can't undo side effects, but at local $FS::UID::AutoCommit = 0; # least we can avoid over counting - my @svc_acct = - grep { qsearch( 'export_svc', { 'exportnum' => $self->exportnum, - 'svcpart' => $_->cust_svc->svcpart, } ) - } - qsearch( 'svc_acct', - { 'username' => $UserName }, - '', - $extra_sql - ); - + my $status = 'skipped'; my $errinfo = "for RADIUS detail RadAcctID $RadAcctId ". "(UserName $UserName, Realm $Realm)"; - my $status = 'skipped'; - if ( !@svc_acct ) { - warn "WARNING: no svc_acct record found $errinfo - skipping\n"; - } elsif ( scalar(@svc_acct) > 1 ) { - warn "WARNING: multiple svc_acct records found $errinfo - skipping\n"; - } else { - - my $svc_acct = $svc_acct[0]; - warn "found svc_acct ". $svc_acct->svcnum. " $errinfo\n" if $DEBUG; - $svc_acct->last_login($AcctStartTime); - $svc_acct->last_logout($AcctStopTime); - - my $cust_pkg = $svc_acct->cust_svc->cust_pkg; - if ( $cust_pkg && $AcctStopTime < ( $cust_pkg->last_bill - || $cust_pkg->setup ) ) { - $status = 'skipped (too old)'; + if ( $self->option('process_single_realm') + && $self->option('realm') ne $Realm ) + { + warn "WARNING: wrong realm $errinfo - skipping\n" if $DEBUG; + } else { + my @svc_acct = + grep { qsearch( 'export_svc', { 'exportnum' => $self->exportnum, + 'svcpart' => $_->cust_svc->svcpart, } ) + } + qsearch( 'svc_acct', + { 'username' => $UserName }, + '', + $extra_sql + ); + + if ( !@svc_acct ) { + warn "WARNING: no svc_acct record found $errinfo - skipping\n"; + } elsif ( scalar(@svc_acct) > 1 ) { + warn "WARNING: multiple svc_acct records found $errinfo - skipping\n"; } else { - my @st; - push @st, _try_decrement($svc_acct, 'seconds', $AcctSessionTime ); - push @st, _try_decrement($svc_acct, 'upbytes', $AcctInputOctets ); - push @st, _try_decrement($svc_acct, 'downbytes', $AcctOutputOctets ); - push @st, _try_decrement($svc_acct, 'totalbytes', $AcctInputOctets + + my $svc_acct = $svc_acct[0]; + warn "found svc_acct ". $svc_acct->svcnum. " $errinfo\n" if $DEBUG; + + $svc_acct->last_login($AcctStartTime); + $svc_acct->last_logout($AcctStopTime); + + my $session_time = $AcctStopTime; + $session_time = $AcctStartTime if $self->option('ignore_long_sessions'); + + my $cust_pkg = $svc_acct->cust_svc->cust_pkg; + if ( $cust_pkg && $session_time < ( $cust_pkg->last_bill + || $cust_pkg->setup ) ) { + $status = 'skipped (too old)'; + } else { + my @st; + push @st, _try_decrement($svc_acct, 'seconds', $AcctSessionTime); + push @st, _try_decrement($svc_acct, 'upbytes', $AcctInputOctets); + push @st, _try_decrement($svc_acct, 'downbytes', $AcctOutputOctets); + push @st, _try_decrement($svc_acct, 'totalbytes', $AcctInputOctets + $AcctOutputOctets); - $status=join(' ', @st); + $status=join(' ', @st); + } } } -- cgit v1.2.1 From d311f62f655dc5cad8b9319f07f8c0e6bf344cc2 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 6 Aug 2010 21:28:09 +0000 Subject: communigate phase 3: archive messages, RT#7515 --- FS/FS/Schema.pm | 8 +++----- FS/FS/svc_acct.pm | 26 +++++++++++++++++++++++--- FS/FS/svc_domain.pm | 24 ++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 10 deletions(-) (limited to 'FS') diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 60d2bcef5..557ee6295 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1698,7 +1698,8 @@ sub tables_hashref { 'cgp_rpopallowed', 'char', 'NULL', 1, '', '', #RPOPAllowed 'cgp_mailtoall', 'char', 'NULL', 1, '', '', #MailToAll 'cgp_addmailtrailer', 'char', 'NULL', 1, '', '', #AddMailTrailer - #XXX archive messages, mailing lists + 'cgp_archiveafter', 'int', 'NULL', '', '', '', #ArchiveMessagesAfter + #XXX mailing lists #preferences 'cgp_deletemode', 'varchar', 'NULL', $char_d, '', '',#DeleteMode 'cgp_emptytrash', 'varchar', 'NULL', $char_d, '', '',#EmptyTrash @@ -1708,7 +1709,6 @@ sub tables_hashref { 'cgp_prontoskinname', 'varchar', 'NULL', $char_d, '', '',#ProntoSkinName 'cgp_sendmdnmode', 'varchar', 'NULL', $char_d, '', '',#SendMDNMode #mail -#vacation message, redirect all mail, mail rules #XXX RPOP settings ], 'primary_key' => 'svcnum', @@ -1770,7 +1770,7 @@ sub tables_hashref { 'acct_def_cgp_rpopallowed', 'char', 'NULL', 1, '', '', 'acct_def_cgp_mailtoall', 'char', 'NULL', 1, '', '', 'acct_def_cgp_addmailtrailer', 'char', 'NULL', 1, '', '', - #XXX archive messages + 'acct_def_cgp_archiveafter', 'int', 'NULL', '', '', '', #preferences 'acct_def_cgp_deletemode', 'varchar', 'NULL', $char_d, '', '', 'acct_def_cgp_emptytrash', 'varchar', 'NULL', $char_d, '', '', @@ -1779,8 +1779,6 @@ sub tables_hashref { 'acct_def_cgp_skinname', 'varchar', 'NULL', $char_d, '', '', 'acct_def_cgp_prontoskinname', 'varchar', 'NULL', $char_d, '', '', 'acct_def_cgp_sendmdnmode', 'varchar', 'NULL', $char_d, '', '', - #mail - #XXX rules, archive rule, spam foldering rule(s) ], 'primary_key' => 'svcnum', 'unique' => [ ], diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 3b26688bf..707b33a6f 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -440,7 +440,28 @@ sub table_info { 'cgp_addmailtrailer' => { label => 'Add trailer to sent mail', type => 'checkbox', }, - #XXX archive messages, mailing lists + 'cgp_archiveafter' => { + label => 'Archive messages after', + type => 'select', + select_hash => [ + -2 => 'default(730 day(s))', + 0 => 'Never', + 86400 => '24 hour(s)', + 172800 => '2 day(s)', + 259200 => '3 day(s)', + 432000 => '5 day(s)', + 604800 => '7 day(s)', + 1209600 => '2 week(s)', + 2592000 => '30 day(s)', + 7776000 => '90 day(s)', + 15552000 => '180 day(s)', + 31536000 => '365 day(s)', + 63072000 => '730 day(s)', + ], + disable_inventory => 1, + disable_select => 1, + }, + #XXX mailing lists #preferences 'cgp_deletemode' => { @@ -494,7 +515,6 @@ sub table_info { }, #mail - #XXX vacation message, redirect all mail, mail rules #XXX RPOP settings }, @@ -1190,6 +1210,7 @@ sub check { || $self->ut_enum('cgp_rpopallowed', [ '', 'Y' ]) || $self->ut_enum('cgp_mailtoall', [ '', 'Y' ]) || $self->ut_enum('cgp_addmailtrailer', [ '', 'Y' ]) + || $self->ut_snumbern('cgp_archiveafter') #preferences || $self->ut_alphasn('cgp_deletemode') || $self->ut_enum('cgp_emptytrash', $self->cgp_emptytrash_values) @@ -1198,7 +1219,6 @@ sub check { || $self->ut_textn('cgp_skinname') || $self->ut_textn('cgp_prontoskinname') || $self->ut_alphan('cgp_sendmdnmode') - #XXX vacation message, redirect all mail, mail rules #XXX RPOP settings ; return $error if $error; diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm index 4d85060d3..3dc352b7a 100644 --- a/FS/FS/svc_domain.pm +++ b/FS/FS/svc_domain.pm @@ -207,6 +207,27 @@ sub table_info { label => 'Acct. default Add trailer to sent mail', type => 'checkbox', }, + 'acct_def_cgp_archiveafter' => { + label => 'Archive messages after', + type => 'select', + select_hash => [ + -2 => 'default(730 days)', + 0 => 'Never', + 86400 => '24 hours', + 172800 => '2 days', + 259200 => '3 days', + 432000 => '5 days', + 604800 => '7 days', + 1209600 => '2 weeks', + 2592000 => '30 days', + 7776000 => '90 days', + 15552000 => '180 days', + 31536000 => '365 days', + 63072000 => '730 days', + ], + disable_inventory => 1, + disable_select => 1, + }, 'trailer' => { label => 'Mail trailer', type => 'textarea', @@ -490,7 +511,7 @@ sub check { || $self->ut_enum('acct_def_cgp_rpopallowed', [ '', 'Y' ]) || $self->ut_enum('acct_def_cgp_mailtoall', [ '', 'Y' ]) || $self->ut_enum('acct_def_cgp_addmailtrailer', [ '', 'Y' ]) - #XXX archive messages + || $self->ut_snumbern('acct_def_cgp_archiveafter') #preferences || $self->ut_alphasn('acct_def_cgp_deletemode') || $self->ut_enum('acct_def_cgp_emptytrash', @@ -501,7 +522,6 @@ sub check { || $self->ut_textn('acct_def_cgp_prontoskinname') || $self->ut_alphan('acct_def_cgp_sendmdnmode') #mail - #XXX rules, archive rule, spam foldering rule(s) ; return $error if $error; -- cgit v1.2.1 From 89da4b96c4e2fce7079be8d2729750c088f8035b Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 6 Aug 2010 21:31:04 +0000 Subject: communigate phase 3: archive messages, RT#7515 --- FS/FS/part_export/communigate_pro.pm | 10 ++++++++-- FS/FS/svc_acct.pm | 24 ++++++++++++------------ 2 files changed, 20 insertions(+), 14 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm index 2cad24467..3e1213e84 100644 --- a/FS/FS/part_export/communigate_pro.pm +++ b/FS/FS/part_export/communigate_pro.pm @@ -85,10 +85,12 @@ sub _export_insert_svc_acct { 'MailToAll' =>($svc_acct->cgp_mailtoall ?'YES':'NO'), 'AddMailTrailer' =>($svc_acct->cgp_addmailtrailer ?'YES':'NO'), + 'ArchiveMessagesAfter' => $svc_acct->cgp_archiveafter, + map { $quotas{$_} => $svc_acct->$_() } grep $svc_acct->$_(), keys %quotas ); - #XXX phase 3: archive messages, mailing lists + #XXX phase 3: mailing lists my @options = ( 'CreateAccount', 'accountName' => $self->export_username($svc_acct), @@ -194,6 +196,7 @@ sub _export_insert_svc_domain { 'RPOPAllowed' =>($svc_domain->acct_def_cgp_rpopallowed ?'YES':'NO'), 'MailToAll' =>($svc_domain->acct_def_cgp_mailtoall ?'YES':'NO'), 'AddMailTrailer' =>($svc_domain->acct_def_cgp_addmailtrailer ?'YES':'NO'), + 'ArchiveMessagesAfter' => $svc_domain->acct_def_cgp_archiveafter, ); warn "WARNING: error queueing SetAccountDefaults job: $def_err" if $def_err; @@ -318,8 +321,10 @@ sub _export_replace_svc_acct { if $old->cgp_mailtoall ne $new->cgp_mailtoall; $settings{'AddMailTrailer'} = ( $new->cgp_addmailtrailer ? 'YES':'NO' ) if $old->cgp_addmailtrailer ne $new->cgp_addmailtrailer; + $settings{'ArchiveMessagesAfter'} = $new->cgp_archiveafter + if $old->cgp_archiveafter ne $new->cgp_archiveafter; - #XXX phase 3: archive messages, mailing lists + #XXX phase 3: mailing lists if ( keys %settings ) { my $error = $self->communigate_pro_queue( @@ -441,6 +446,7 @@ sub _export_replace_svc_domain { 'RPOPAllowed' => ( $new->acct_def_cgp_rpopallowed ? 'YES' : 'NO' ), 'MailToAll' => ( $new->acct_def_cgp_mailtoall ? 'YES' : 'NO' ), 'AddMailTrailer' => ( $new->acct_def_cgp_addmailtrailer ? 'YES' : 'NO' ), + 'ArchiveMessagesAfter' => $new->acct_def_cgp_archiveafter, ); warn "WARNING: error queueing SetAccountDefaults job: $def_err" if $def_err; diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 707b33a6f..801c46533 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -444,19 +444,19 @@ sub table_info { label => 'Archive messages after', type => 'select', select_hash => [ - -2 => 'default(730 day(s))', + -2 => 'default(730 days)', 0 => 'Never', - 86400 => '24 hour(s)', - 172800 => '2 day(s)', - 259200 => '3 day(s)', - 432000 => '5 day(s)', - 604800 => '7 day(s)', - 1209600 => '2 week(s)', - 2592000 => '30 day(s)', - 7776000 => '90 day(s)', - 15552000 => '180 day(s)', - 31536000 => '365 day(s)', - 63072000 => '730 day(s)', + 86400 => '24 hours', + 172800 => '2 days', + 259200 => '3 days', + 432000 => '5 days', + 604800 => '7 days', + 1209600 => '2 weeks', + 2592000 => '30 days', + 7776000 => '90 days', + 15552000 => '180 days', + 31536000 => '365 days', + 63072000 => '730 days', ], disable_inventory => 1, disable_select => 1, -- cgit v1.2.1 From b3205ddf480401284a5fc4ccbcb45d9c42b0bcf9 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 7 Aug 2010 07:39:06 +0000 Subject: communigate phase 3: RPOP/acct_snarf, RT#7515 --- FS/FS/Mason.pm | 1 + FS/FS/Schema.pm | 18 ++++++++++++------ FS/FS/acct_snarf.pm | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) (limited to 'FS') diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index bcf727e3c..2a4b42ffe 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -249,6 +249,7 @@ if ( -e $addl_handler_use_file ) { use FS::rate_time_interval; use FS::msg_template; use FS::part_tag; + use FS::acct_snarf; # Sammath Naur if ( $FS::Mason::addl_handler_use ) { diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 557ee6295..d7d5a0413 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2171,12 +2171,18 @@ 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', 'serial', '', '', '', '', + 'snarfname', 'varchar', 'NULL', $char_d, '', '', + 'svcnum', 'int', '', '', '', '', + 'machine', 'varchar', '', 255, '', '', + 'protocol', 'varchar', '', $char_d, '', '', + 'username', 'varchar', '', $char_d, '', '', + '_password', 'varchar', '', $char_d, '', '', + 'check_freq', 'int', 'NULL', '', '', '', + 'leave', 'char', 'NULL', 1, '', '', + 'apop', 'char', 'NULL', 1, '', '', + 'tls', 'char', 'NULL', 1, '', '', + 'mailbox', 'varchar', 'NULL', $char_d, '', '', ], 'primary_key' => 'snarfnum', 'unique' => [], diff --git a/FS/FS/acct_snarf.pm b/FS/FS/acct_snarf.pm index b4e88bfc9..fb26cea06 100644 --- a/FS/FS/acct_snarf.pm +++ b/FS/FS/acct_snarf.pm @@ -2,6 +2,7 @@ package FS::acct_snarf; use strict; use vars qw( @ISA ); +use Tie::IxHash; use FS::Record; @ISA = qw( FS::Record ); @@ -35,6 +36,8 @@ fields are currently supported: =item snarfnum - primary key +=item snarfname - Label + =item svcnum - Account (see L) =item machine - external machine to download mail from @@ -100,11 +103,17 @@ sub check { my $self = shift; my $error = $self->ut_numbern('snarfnum') + || $self->ut_textn('snarfname') #alphasn? || $self->ut_number('svcnum') || $self->ut_foreign_key('svcnum', 'svc_acct', 'svcnum') || $self->ut_domain('machine') || $self->ut_alphan('protocol') || $self->ut_textn('username') + || $self->ut_numbern('check_freq') + || $self->ut_enum('leave', [ '', 'Y' ]) + || $self->ut_enum('apop', [ '', 'Y' ]) + || $self->ut_enum('tls', [ '', 'Y' ]) + || $self->ut_alphan('mailbox') ; return $error if $error; @@ -114,6 +123,32 @@ sub check { ''; #no error } +sub check_freq_labels { + + tie my %hash, 'Tie::IxHash', + 0 => 'Never', + 60 => 'minute', + 120 => '2 minutes', + 180 => '3 minutes', + 300 => '5 minutes', + 600 => '10 minutes', + 900 => '15 minutes', + 1800 => '30 minutes', + 3600 => 'hour', + 7200 => '2 hours', + 10800 => '3 hours', + 21600 => '6 hours', + 43200 => '12 hours', + 86400 => 'day', + 172800 => '2 days', + 259200 => '3 days', + 604800 => 'week', + 1000000000 => 'Disabled', + ; + + \%hash; +} + =back =head1 BUGS -- cgit v1.2.1 From 96bcb3256650a35d57c2ac487e990b78a2f88a74 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 7 Aug 2010 10:11:43 +0000 Subject: communigate phase 3: RPOP/acct_snarf, RT#7515 --- FS/FS/acct_snarf.pm | 54 +++++++++++++++++++++++++++++++++++- FS/FS/part_export/communigate_pro.pm | 41 +++++++++++++++++++++++++++ FS/FS/svc_acct.pm | 23 +++++++++++---- 3 files changed, 111 insertions(+), 7 deletions(-) (limited to 'FS') diff --git a/FS/FS/acct_snarf.pm b/FS/FS/acct_snarf.pm index fb26cea06..480a632bb 100644 --- a/FS/FS/acct_snarf.pm +++ b/FS/FS/acct_snarf.pm @@ -3,7 +3,8 @@ package FS::acct_snarf; use strict; use vars qw( @ISA ); use Tie::IxHash; -use FS::Record; +use FS::Record qw( qsearchs ); +use FS::cust_svc; @ISA = qw( FS::Record ); @@ -91,6 +92,37 @@ returns the error, otherwise returns false. # the replace method can be inherited from FS::Record +=item cust_svc + +=cut + +sub cust_svc { + my $self = shift; + qsearchs('cust_svc', { 'svcnum' => $self->svcnum } ); +} + + +=item svc_export + +Calls the replace export for any communigate exports attached to this rule's +service. + +=cut + +sub svc_export { + my $self = shift; + + my $cust_svc = $self->cust_svc; + my $svc_x = $cust_svc->svc_x; + + #_singledomain too + my @exports = $cust_svc->part_svc->part_export('communigate_pro'); + my @errors = map $_->export_replace($svc_x, $svc_x), @exports; + + @errors ? join(' / ', @errors) : ''; + +} + =item check Checks all fields to make sure this is a valid external mail account. If @@ -149,6 +181,26 @@ sub check_freq_labels { \%hash; } +=item cgp_hashref + +Returns a hashref representing this external mail account, suitable for +Communigate Pro API commands: + +=cut + +sub cgp_hashref { + my $self = shift; + { + 'authName' => $self->username, + 'domain' => $self->machine, + 'password' => $self->_password, + 'period' => $self->check_freq.'s', + 'APOP' => ( $self->apop eq 'Y' ? 'YES' : 'NO' ), + 'TLS' => ( $self->tls eq 'Y' ? 'YES' : 'NO' ), + 'Leave' => ( $self->leave eq 'Y' ? 'YES' : 'NO' ), #XXX leave?? + }; +} + =back =head1 BUGS diff --git a/FS/FS/part_export/communigate_pro.pm b/FS/FS/part_export/communigate_pro.pm index 3e1213e84..cc96086b1 100644 --- a/FS/FS/part_export/communigate_pro.pm +++ b/FS/FS/part_export/communigate_pro.pm @@ -146,6 +146,15 @@ sub _export_insert_svc_acct { warn "WARNING: error queueing SetAccountMailRules job: $rule_error" if $rule_error; + my $rpop_error = $self->communigate_pro_queue( + $svc_acct->svcnum, + 'SetAccountRPOPs', + $self->export_username($svc_acct), + $svc_acct->cgp_rpop_hashref, + ); + warn "WARNING: error queueing SetAccountMailRPOPs job: $rpop_error" + if $rpop_error; + ''; } @@ -381,6 +390,15 @@ sub _export_replace_svc_acct { warn "WARNING: error queueing SetAccountMailRules job: $rule_error" if $rule_error; + my $rpop_error = $self->communigate_pro_queue( + $new->svcnum, + 'SetAccountRPOPs', + $self->export_username($new), + $new->cgp_rpop_hashref, + ); + warn "WARNING: error queueing SetAccountMailRPOPs job: $rpop_error" + if $rpop_error; + ''; } @@ -801,6 +819,20 @@ sub export_getsettings_svc_acct { map _rule2string($_), @$rules ); +# #rpops too +# my $rpops = eval { $self->communigate_pro_runcommand( +# 'GetAccountRPOPs', +# $svc_acct->email +# ) }; +# return $@ if $@; +# +# %$effective_settings = ( %$effective_settings, +# map _rpop2string($_), %$rpops +# ); +# %$settings = ( %$settings, +# map _rpop2string($_), %rpops +# ); + #aliases too my $aliases = eval { $self->communigate_pro_runcommand( 'GetAccountAliases', @@ -870,6 +902,14 @@ sub _rule2string { ("Mail rule $name" => "$priority IF $conditions THEN $actions ($comment)"); } +#sub _rpop2string { +# my $rpop = shift; +# my($priority, $name, $conditions, $actions, $comment) = @$rule; +# $conditions = join(', ', map { my $a = $_; join(' ', @$a); } @$conditions); +# $actions = join(', ', map { my $a = $_; join(' ', @$a); } @$actions); +# ("Mail rule $name" => "$priority IF $conditions THEN $actions ($comment)"); +#} + sub export_getsettings_svc_mailinglist { my($self, $svc_mailinglist, $settingsref, $defaultref ) = @_; @@ -907,6 +947,7 @@ sub communigate_pro_queue_dep { 'UpdateAccountDefaults' => 'cp_Scalar_settingsHash', 'SetAccountDefaultPrefs' => 'cp_Scalar_settingsHash', 'UpdateAccountDefaultPrefs' => 'cp_Scalar_settingsHash', + 'SetAccountRPOPs' => 'cp_Scalar_Hash', ); my $sub = exists($kludge_methods{$method}) ? $kludge_methods{$method} diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index 801c46533..c301bcd87 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -47,6 +47,7 @@ use FS::part_export; use FS::svc_forward; use FS::svc_www; use FS::cdr; +use FS::acct_snarf; $DEBUG = 0; $me = '[FS::svc_acct]'; @@ -1929,17 +1930,27 @@ sub email { =item acct_snarf Returns an array of FS::acct_snarf records associated with the account. -If the acct_snarf table does not exist or there are no associated records, -an empty list is returned =cut sub acct_snarf { my $self = shift; - return () unless dbdef->table('acct_snarf'); - eval "use FS::acct_snarf;"; - die $@ if $@; - qsearch('acct_snarf', { 'svcnum' => $self->svcnum } ); + qsearch({ + 'table' => 'acct_snarf', + 'hashref' => { 'svcnum' => $self->svcnum }, + #'order_by' => 'ORDER BY priority ASC', + }); +} + +=item cgp_rpop_hashref + +Returns an arrayref of RPOP data suitable for Communigate Pro API commands. + +=cut + +sub cgp_rpop_hashref { + my $self = shift; + { map { $_->snarfname => $_->cgp_hashref } $self->acct_snarf }; } =item decrement_upbytes OCTETS -- cgit v1.2.1 From b1d445f94514a29e5d4753839798b0291d89aee3 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 9 Aug 2010 01:03:49 +0000 Subject: package web import from CSV/XLS, RT#9529 --- FS/FS/Mason.pm | 3 + FS/FS/Record.pm | 98 +++++++++---- FS/FS/Schema.pm | 4 +- FS/FS/cust_pkg.pm | 9 ++ FS/FS/cust_pkg/Import.pm | 373 +++++++++++++++++++++++++++++++++++++++++++++++ FS/bin/freeside-queued | 2 +- 6 files changed, 458 insertions(+), 31 deletions(-) create mode 100644 FS/FS/cust_pkg/Import.pm (limited to 'FS') diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 2a4b42ffe..f5d7c8566 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -3,6 +3,7 @@ package FS::Mason; use strict; use vars qw( @ISA @EXPORT_OK $addl_handler_use ); use Exporter; +use Carp; use File::Slurp qw( slurp ); use HTML::Mason 1.27; #http://www.masonhq.com/?ApacheModPerl2Redirect use HTML::Mason::Interp; @@ -146,6 +147,7 @@ if ( -e $addl_handler_use_file ) { use FS::cust_location; use FS::cust_pay; use FS::cust_pkg; + use FS::cust_pkg::Import; use FS::part_pkg_taxclass; use FS::cust_pkg_reason; use FS::cust_refund; @@ -361,6 +363,7 @@ if ( -e $addl_handler_use_file ) { sub include { use vars qw($m); + #carp #should just switch to <& &> syntax $m->scomp(@_); } diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index bc075dde9..758e0f96c 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -1611,6 +1611,8 @@ Class method for batch imports. Available params: =item table +=item format - usual way to specify import, with this format string selecting data from the formats and format_* info hashes + =item formats =item format_types @@ -1623,6 +1625,10 @@ Class method for batch imports. Available params: =item format_row_callbacks +=item fields - Alternate way to specify import, specifying import fields directly as a listref + +=item postinsert_callback + =item params =item job @@ -1635,8 +1641,6 @@ FS::queue object, will be updated with progress csv, xls or fixedlength -=item format - =item empty_ok =back @@ -1647,21 +1651,64 @@ sub batch_import { my $param = shift; warn "$me batch_import call with params: \n". Dumper($param) - ;# if $DEBUG; + if $DEBUG; my $table = $param->{table}; - my $formats = $param->{formats}; my $job = $param->{job}; my $file = $param->{file}; - my $format = $param->{'format'}; my $params = $param->{params} || {}; - die "unknown format $format" unless exists $formats->{ $format }; + my( $type, $header, $sep_char, $fixedlength_format, $row_callback, @fields ); + my $postinsert_callback = ''; + if ( $param->{'format'} ) { + + my $format = $param->{'format'}; + my $formats = $param->{formats}; + die "unknown format $format" unless exists $formats->{ $format }; + + $type = $param->{'format_types'} + ? $param->{'format_types'}{ $format } + : $param->{type} || 'csv'; + + + $header = $param->{'format_headers'} + ? $param->{'format_headers'}{ $param->{'format'} } + : 0; + + $sep_char = $param->{'format_sep_chars'} + ? $param->{'format_sep_chars'}{ $param->{'format'} } + : ','; + + $fixedlength_format = + $param->{'format_fixedlength_formats'} + ? $param->{'format_fixedlength_formats'}{ $param->{'format'} } + : ''; + + $row_callback = + $param->{'format_row_callbacks'} + ? $param->{'format_row_callbacks'}{ $param->{'format'} } + : ''; + + @fields = @{ $formats->{ $format } }; + + } elsif ( $param->{'fields'} ) { + + $type = ''; #infer from filename + $header = 0; + $sep_char = ','; + $fixedlength_format = ''; + $row_callback = ''; + @fields = @{ $param->{'fields'} }; - my $type = $param->{'format_types'} - ? $param->{'format_types'}{ $format } - : $param->{type} || 'csv'; + $postinsert_callback = $param->{'postinsert_callback'} + if $param->{'postinsert_callback'} + + } else { + die "neither format nor fields specified"; + } + + #my $file = $param->{file}; unless ( $type ) { if ( $file =~ /\.(\w+)$/i ) { @@ -1675,25 +1722,6 @@ sub batch_import { if $param->{'default_csv'} && $type ne 'xls'; } - my $header = $param->{'format_headers'} - ? $param->{'format_headers'}{ $param->{'format'} } - : 0; - - my $sep_char = $param->{'format_sep_chars'} - ? $param->{'format_sep_chars'}{ $param->{'format'} } - : ','; - - my $fixedlength_format = - $param->{'format_fixedlength_formats'} - ? $param->{'format_fixedlength_formats'}{ $param->{'format'} } - : ''; - - my $row_callback = - $param->{'format_row_callbacks'} - ? $param->{'format_row_callbacks'}{ $param->{'format'} } - : ''; - - my @fields = @{ $formats->{ $format } }; my $row = 0; my $count; @@ -1757,6 +1785,7 @@ sub batch_import { local $FS::UID::AutoCommit = 0; my $dbh = dbh; + #my $params = $param->{params} || {}; if ( $param->{'batch_namecol'} && $param->{'batch_namevalue'} ) { my $batch_col = $param->{'batch_keycol'}; @@ -1774,7 +1803,8 @@ sub batch_import { $params->{ $batch_col } = $batch_value; } - + + #my $job = $param->{job}; my $line; my $imported = 0; my( $last, $min_sec ) = ( time, 5 ); #progressbar foo @@ -1832,6 +1862,7 @@ sub batch_import { } + #my $table = $param->{table}; my $class = "FS::$table"; my $record = $class->new( \%hash ); @@ -1855,6 +1886,15 @@ sub batch_import { $row++; $imported++; + if ( $postinsert_callback ) { + my $error = &{$postinsert_callback}($record, $param); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "postinsert_callback error". ( $line ? " for $line" : '' ). + ": $error"; + } + } + if ( $job && time - $min_sec > $last ) { #progress bar $job->update_statustext( int(100 * $imported / $count) ); $last = time; diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index d7d5a0413..dc8f2f3aa 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1289,6 +1289,7 @@ sub tables_hashref { 'pkgnum', 'serial', '', '', '', '', 'custnum', 'int', '', '', '', '', 'pkgpart', 'int', '', '', '', '', + 'pkgbatch', 'varchar', 'NULL', $char_d, '', '', 'locationnum', 'int', 'NULL', '', '', '', 'otaker', 'varchar', 'NULL', 32, '', '', 'usernum', 'int', 'NULL', '', '', '', @@ -1310,7 +1311,8 @@ sub tables_hashref { ], 'primary_key' => 'pkgnum', 'unique' => [], - 'index' => [ ['custnum'], ['pkgpart'], [ 'locationnum' ], [ 'usernum' ], + 'index' => [ ['custnum'], ['pkgpart'], [ 'pkgbatch' ], [ 'locationnum' ], + [ 'usernum' ], [ 'start_date' ], ['setup'], ['last_bill'], ['bill'], ['susp'], ['adjourn'], ['expire'], ['cancel'], ['change_date'], diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index 0f9a611eb..c3ee4e40a 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -2623,6 +2623,15 @@ sub search { "cust_pkg.custnum = $1"; } + ## + # custbatch + ## + + if ( $params->{'pkgbatch'} =~ /^([\w\/\-\:\.]+)$/ and $1 ) { + push @where, + "cust_pkg.pkgbatch = '$1'"; + } + ## # parse status ## diff --git a/FS/FS/cust_pkg/Import.pm b/FS/FS/cust_pkg/Import.pm new file mode 100644 index 000000000..7a4b9d50c --- /dev/null +++ b/FS/FS/cust_pkg/Import.pm @@ -0,0 +1,373 @@ +package FS::cust_pkg::Import; + +use strict; +use vars qw( $DEBUG ); #$conf ); +use Storable qw(thaw); +use Data::Dumper; +use MIME::Base64; +use FS::Misc::DateTime qw( parse_datetime ); +use FS::Record qw( qsearchs ); +use FS::cust_pkg; +use FS::cust_main; +use FS::svc_acct; +use FS::svc_external; +use FS::svc_phone; + +$DEBUG = 0; + +#install_callback FS::UID sub { +# $conf = new FS::Conf; +#}; + +=head1 NAME + +FS::cust_pkg::Import - Batch customer importing + +=head1 SYNOPSIS + + use FS::cust_pkg::Import; + + #import + FS::cust_pkg::Import::batch_import( { + file => $file, #filename + type => $type, #csv or xls + format => $format, #extended, extended-plus_company, svc_external, + # or svc_external_svc_phone + agentnum => $agentnum, + job => $job, #optional job queue job, for progressbar updates + pkgbatch => $pkgbatch, #optional batch unique identifier + } ); + die $error if $error; + + #ajax helper + use FS::UI::Web::JSRPC; + my $server = + new FS::UI::Web::JSRPC 'FS::cust_pkg::Import::process_batch_import', $cgi; + print $server->process; + +=head1 DESCRIPTION + +Batch package importing. + +=head1 SUBROUTINES + +=item process_batch_import + +Load a batch import as a queued JSRPC job + +=cut + +sub process_batch_import { + my $job = shift; + + my $param = thaw(decode_base64(shift)); + warn Dumper($param) if $DEBUG; + + my $files = $param->{'uploaded_files'} + or die "No files provided.\n"; + + my (%files) = map { /^(\w+):([\.\w]+)$/ ? ($1,$2):() } split /,/, $files; + + my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc. '/'; + my $file = $dir. $files{'file'}; + + my $type; + if ( $file =~ /\.(\w+)$/i ) { + $type = lc($1); + } else { + #or error out??? + warn "can't parse file type from filename $file; defaulting to CSV"; + $type = 'csv'; + } + + my $error = + FS::cust_pkg::Import::batch_import( { + job => $job, + file => $file, + type => $type, + 'params' => { pkgbatch => $param->{pkgbatch} }, + agentnum => $param->{'agentnum'}, + 'format' => $param->{'format'}, + } ); + + unlink $file; + + die "$error\n" if $error; + +} + +=item batch_import + +=cut + +my %formatfields = ( + 'default' => [], + 'svc_acct' => [qw( username _password )], + 'svc_phone' => [qw( countrycode phonenum sip_password pin )], + 'svc_external' => [qw( id title )], +); + +sub _formatfields { + \%formatfields; +} + +my %import_options = ( + 'table' => 'cust_pkg', + + 'postinsert_callback' => sub { + my( $record, $param ) = @_; + + my $formatfields = _formatfields; + foreach my $svc_x ( grep { $_ ne 'default' } keys %$formatfields ) { + + my $ff = $formatfields->{$svc_x}; + + if ( grep $param->{"$svc_x.$_"}, @$ff ) { + my $svc_x = "FS::$svc_x"->new( { + 'pkgnum' => $record->pkgnum, + 'svcpart' => $record->part_pkg->svcpart($svc_x), + map { $_ => $param->{"$svc_x.$_"} } @$ff + } ); + my $error = $svc_x->insert; + return $error if $error; + } + + } + + return ''; #no error + + }, +); + +sub _import_options { + \%import_options; +} + +sub batch_import { + my $opt = shift; + + my $iopt = _import_options; + $opt->{$_} = $iopt->{$_} foreach keys %$iopt; + + my $agentnum = delete $opt->{agentnum}; # i like closures (delete though?) + + my $format = delete $opt->{'format'}; + my @fields = (); + + if ( $format =~ /^(.*)-agent_custid$/ ) { + $format = $1; + @fields = ( + sub { + my( $self, $value ) = @_; # $conf, $param + my $cust_main = qsearchs('cust_main', { + 'agentnum' => $agentnum, + 'agent_custid' => $value, + }); + $self->custnum($cust_main->custnum) if $cust_main; + }, + ); + } else { + @fields = ( 'custnum' ); + } + + push @fields, ( 'pkgpart', 'discountnum' ); + + foreach my $field ( + qw( start_date setup bill last_bill susp adjourn cancel expire ) + ) { + push @fields, sub { + my( $self, $value ) = @_; # $conf, $param + #->$field has undesirable effects + $self->set($field, parse_datetime($value) ); #$field closure + }; + } + + my $formatfields = _formatfields(); + + die "unknown format $format" unless $formatfields->{$format}; + + foreach my $field ( @{ $formatfields->{$format} } ) { + + push @fields, sub { + my( $self, $value, $conf, $param ) = @_; + $param->{"$format.$field"} = $value; + }; + + } + + $opt->{'fields'} = \@fields; + + FS::Record::batch_import( $opt ); + +} + +=for comment + + my $billtime = time; + my %cust_pkg = ( pkgpart => $pkgpart ); + my %svc_x = (); + foreach my $field ( @fields ) { + + if ( $field =~ /^cust_pkg\.(pkgpart|setup|bill|susp|adjourn|expire|cancel)$/ ) { + + #$cust_pkg{$1} = parse_datetime( shift @$columns ); + if ( $1 eq 'pkgpart' ) { + $cust_pkg{$1} = shift @columns; + } elsif ( $1 eq 'setup' ) { + $billtime = parse_datetime(shift @columns); + } else { + $cust_pkg{$1} = parse_datetime( shift @columns ); + } + + } elsif ( $field =~ /^svc_acct\.(username|_password)$/ ) { + + $svc_x{$1} = shift @columns; + + } elsif ( $field =~ /^svc_external\.(id|title)$/ ) { + + $svc_x{$1} = shift @columns; + + } elsif ( $field =~ /^svc_phone\.(countrycode|phonenum|sip_password|pin)$/ ) { + $svc_x{$1} = shift @columns; + + } else { + + #refnum interception + if ( $field eq 'refnum' && $columns[0] !~ /^\s*(\d+)\s*$/ ) { + + my $referral = $columns[0]; + my %hash = ( 'referral' => $referral, + 'agentnum' => $agentnum, + 'disabled' => '', + ); + + my $part_referral = qsearchs('part_referral', \%hash ) + || new FS::part_referral \%hash; + + unless ( $part_referral->refnum ) { + 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; + } + + my $value = shift @columns; + $cust_main{$field} = $value if length($value); + } + } + + $cust_main{'payby'} = 'CARD' + if defined $cust_main{'payinfo'} + && length $cust_main{'payinfo'}; + + 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 + + if ( $cust_pkg{'pkgpart'} ) { + my $cust_pkg = new FS::cust_pkg ( \%cust_pkg ); + + my @svc_x = (); + my $svcdb = ''; + if ( $svc_x{'username'} ) { + $svcdb = 'svc_acct'; + } elsif ( $svc_x{'id'} || $svc_x{'title'} ) { + $svcdb = 'svc_external'; + } + + my $svc_phone = ''; + if ( $svc_x{'countrycode'} || $svc_x{'phonenum'} ) { + $svc_phone = FS::svc_phone->new( { + map { $_ => delete($svc_x{$_}) } + qw( countrycode phonenum sip_password pin) + } ); + } + + if ( $svcdb || $svc_phone ) { + my $part_pkg = $cust_pkg->part_pkg; + unless ( $part_pkg ) { + $dbh->rollback if $oldAutoCommit; + return "unknown pkgpart: ". $cust_pkg{'pkgpart'}; + } + if ( $svcdb ) { + $svc_x{svcpart} = $part_pkg->svcpart_unique_svcdb( $svcdb ); + my $class = "FS::$svcdb"; + push @svc_x, $class->new( \%svc_x ); + } + if ( $svc_phone ) { + $svc_phone->svcpart( $part_pkg->svcpart_unique_svcdb('svc_phone') ); + push @svc_x, $svc_phone; + } + } + + $hash{$cust_pkg} = \@svc_x; + } + + my $error = $cust_main->insert( \%hash, $invoicing_list ); + + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't insert customer". ( $line ? " for $line" : '' ). ": $error"; + } + + 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"; + } + + $error = $cust_main->apply_payments_and_credits; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't bill customer for $line: $error"; + } + + $error = $cust_main->collect(); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "can't collect customer for $line: $error"; + } + + } + + $row++; + + if ( $job && time - $min_sec > $last ) { #progress bar + $job->update_statustext( int(100 * $row / $count) ); + $last = time; + } + + } + + $dbh->commit or die $dbh->errstr if $oldAutoCommit;; + + return "Empty file!" unless $row; + + ''; #no error + +} + +=head1 BUGS + +Not enough documentation. + +=head1 SEE ALSO + +L, L, +L, L, L + +=cut + +1; diff --git a/FS/bin/freeside-queued b/FS/bin/freeside-queued index c9b0edb10..756b699d4 100644 --- a/FS/bin/freeside-queued +++ b/FS/bin/freeside-queued @@ -186,7 +186,7 @@ while (1) { dbh->{'private_profile'} = {} if UNIVERSAL::can(dbh, 'sprintProfile'); #auto-use classes... - if ( $ljob->job =~ /(FS::(part_export|cust_main)::\w+)::/ + if ( $ljob->job =~ /(FS::(part_export|cust_main|cust_pkg)::\w+)::/ || $ljob->job =~ /(FS::\w+)::/ ) { -- cgit v1.2.1 From 54dee8801bee6e4cb938629bc086f386d9bc58d8 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 9 Aug 2010 16:20:39 +0000 Subject: fix cancellation error "No schema for table table found", seems to be fallout from cust_tag work, RT#9502 --- FS/FS/cust_main.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 5898a6a07..955daf8b7 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1382,7 +1382,7 @@ sub delete { } foreach my $table (qw( cust_main_invoice cust_main_exemption cust_tag )) { - foreach my $record ( qsearch( 'table', { 'custnum' => $self->custnum } ) ) { + foreach my $record ( qsearch( $table, { 'custnum' => $self->custnum } ) ) { my $error = $record->delete; if ( $error ) { $dbh->rollback if $oldAutoCommit; @@ -6275,7 +6275,7 @@ sub balance_date_range { my $self = shift; my $sql = 'SELECT SUM('. $self->balance_date_sql(@_). ') FROM cust_main WHERE custnum='. $self->custnum; - sprintf( "%.2f", $self->scalar_sql($sql) ); + sprintf( '%.2f', $self->scalar_sql($sql) ); } =item balance_pkgnum PKGNUM -- cgit v1.2.1 From efa7c9c0233f2c3d1e39d62f17cac30b36f593c4 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 9 Aug 2010 17:22:59 +0000 Subject: return username as a name lable for "Lastname, Firstname" employees --- FS/FS/access_user.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/access_user.pm b/FS/FS/access_user.pm index 8c8ba8b9f..72e914068 100644 --- a/FS/FS/access_user.pm +++ b/FS/FS/access_user.pm @@ -270,7 +270,9 @@ Returns a name string for this user: "Last, First". sub name { my $self = shift; - $self->get('last'). ', '. $self->first; + return $self->username + if $self->get('last') eq 'Lastname' && $self->first eq 'Firstname'; + return $self->get('last'). ', '. $self->first; } =item user_cust_main -- cgit v1.2.1 From 5302c3ef626bbde7afdb7606176210f528992661 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 9 Aug 2010 19:20:13 +0000 Subject: package web import from CSV/XLS, RT#9529 --- FS/FS/cust_pkg/Import.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/cust_pkg/Import.pm b/FS/FS/cust_pkg/Import.pm index 7a4b9d50c..43470a4c0 100644 --- a/FS/FS/cust_pkg/Import.pm +++ b/FS/FS/cust_pkg/Import.pm @@ -102,7 +102,7 @@ sub process_batch_import { my %formatfields = ( 'default' => [], - 'svc_acct' => [qw( username _password )], + 'svc_acct' => [qw( username _password domsvc )], 'svc_phone' => [qw( countrycode phonenum sip_password pin )], 'svc_external' => [qw( id title )], ); -- cgit v1.2.1 From a416f179e995b0d4f53e8c58e9904f31371a938b Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 9 Aug 2010 20:30:22 +0000 Subject: fix harmless cdr_svc_method noise, RT#9428 --- FS/FS/part_pkg/voip_cdr.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm index 1d2f6733c..984a65068 100644 --- a/FS/FS/part_pkg/voip_cdr.pm +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -314,7 +314,7 @@ sub calc_usage { # my $downstream_cdr = ''; - my $cdr_svc_method = $self->option('cdr_svc_method')||'svc_phone.phonenum'; + my $cdr_svc_method = $self->option('cdr_svc_method',1)||'svc_phone.phonenum'; my $rating_method = $self->option('rating_method') || 'prefix'; my $intl = $self->option('international_prefix') || '011'; my $domestic_prefix = $self->option('domestic_prefix'); -- cgit v1.2.1 From 1ec723c2b944c08c32362d05cefe8b332c80276d Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 10 Aug 2010 06:28:40 +0000 Subject: add logo_file support to welcome_letter and fix leaving temp files around for invoices and letters, RT#9497 --- FS/FS/cust_bill.pm | 2 ++ FS/FS/cust_main.pm | 23 ++++++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 4bd9aa16a..3e7109ec9 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -3039,6 +3039,7 @@ sub print_ps { my ($file, $lfile) = $self->print_latex(@_); my $ps = generate_ps($file); + unlink($file.'.tex'); unlink($lfile); $ps; @@ -3067,6 +3068,7 @@ sub print_pdf { my ($file, $lfile) = $self->print_latex(@_); my $pdf = generate_pdf($file); + unlink($file.'.tex'); unlink($lfile); $pdf; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 955daf8b7..e107e6c91 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -8937,6 +8937,7 @@ I<$returnaddress> - the return address defaults to invoice_latexreturnaddress or =cut +# a lot like cust_bill::print_latex sub generate_letter { my ($self, $template, %options) = @_; @@ -9000,6 +9001,17 @@ sub generate_letter { $letter_data{company_name} = $conf->config('company_name', $self->agentnum); my $dir = $FS::UID::conf_dir."/cache.". $FS::UID::datasrc; + + my $lh = new File::Temp( TEMPLATE => 'letter.'. $self->custnum. '.XXXXXXXX', + DIR => $dir, + SUFFIX => '.eps', + UNLINK => 0, + ) or die "can't open temp file: $!\n"; + print $lh $conf->config_binary('logo.eps', $self->agentnum) + or die "can't write temp file: $!\n"; + close $lh; + $letter_data{'logo_file'} = $lh->filename; + my $fh = new File::Temp( TEMPLATE => 'letter.'. $self->custnum. '.XXXXXXXX', DIR => $dir, SUFFIX => '.tex', @@ -9009,7 +9021,8 @@ sub generate_letter { $letter_template->fill_in( OUTPUT => $fh, HASH => \%letter_data ); close $fh; $fh->filename =~ /^(.*).tex$/ or die "unparsable filename: ". $fh->filename; - return $1; + return ($1, $letter_data{'logo_file'}); + } =item print_ps TEMPLATE @@ -9020,8 +9033,12 @@ Returns an postscript letter filled in from TEMPLATE, as a scalar. sub print_ps { my $self = shift; - my $file = $self->generate_letter(@_); - FS::Misc::generate_ps($file); + my($file, $lfile) = $self->generate_letter(@_); + my $ps = FS::Misc::generate_ps($file); + unlink($file.'.tex'); + unlink($lfile); + + $ps; } =item print TEMPLATE -- cgit v1.2.1 From ff8aa9f859273dd5fbbed2ee80ebc63caa27ea32 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 11 Aug 2010 00:08:41 +0000 Subject: fix return address in welcome letters, RT#9497 --- FS/FS/cust_main.pm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index e107e6c91..3bfccfc66 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -8988,8 +8988,13 @@ sub generate_letter { $letter_data{returnaddress} = $retadd; } elsif ( grep /\S/, $conf->config('company_address', $self->agentnum) ) { $letter_data{returnaddress} = - join( '\\*'."\n", map s/( {2,})/'~' x length($1)/eg, - $conf->config('company_address', $self->agentnum) + join( "\n", map { s/( {2,})/'~' x length($1)/eg; + s/$/\\\\\*/; + $_; + } + ( $conf->config('company_name', $self->agentnum), + $conf->config('company_address', $self->agentnum), + ) ); } else { $letter_data{returnaddress} = '~'; -- cgit v1.2.1 From 9f3d1466f4dd917aeb07d7e85222d97e131062f1 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 11 Aug 2010 06:35:20 +0000 Subject: a better customer delete, RT#9564 --- FS/FS/cust_main.pm | 87 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 65 insertions(+), 22 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 3bfccfc66..83cb25b3e 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1301,7 +1301,7 @@ sub reexport { } -=item delete NEW_CUSTNUM +=item delete [ OPTION => VALUE ... ] This deletes the customer. If there is an error, returns the error, otherwise returns false. @@ -1311,18 +1311,20 @@ what you want when a customer cancels service; for that, cancel all of the customer's packages (see L). If the customer has any uncancelled packages, you need to pass a new (valid) -customer number for those packages to be transferred to. Cancelled packages -will be deleted. Did I mention that this is NOT what you want when a customer -cancels service and that you really should be looking see L? +customer number for those packages to be transferred to, as the "new_customer" +option. Cancelled packages will be deleted. Did I mention that this is NOT +what you want when a customer cancels service and that you really should be +looking at L? You can't delete a customer with invoices (see L), -or credits (see L), payments (see L) or -refunds (see L). +statements (see L), credits (see L), +payments (see L) or refunds (see L), unless you +set the "delete_financials" option to a true value. =cut sub delete { - my $self = shift; + my( $self, %opt ) = @_; local $SIG{HUP} = 'IGNORE'; local $SIG{INT} = 'IGNORE'; @@ -1335,26 +1337,47 @@ sub delete { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - if ( $self->cust_bill ) { - $dbh->rollback if $oldAutoCommit; - return "Can't delete a customer with invoices"; + if ( qsearch('agent', { 'agent_custnum' => $self->custnum } ) ) { + $dbh->rollback if $oldAutoCommit; + return "Can't delete a master agent customer"; } - if ( $self->cust_credit ) { - $dbh->rollback if $oldAutoCommit; - return "Can't delete a customer with credits"; - } - if ( $self->cust_pay ) { - $dbh->rollback if $oldAutoCommit; - return "Can't delete a customer with payments"; + + #use FS::access_user + if ( qsearch('access_user', { 'user_custnum' => $self->custnum } ) ) { + $dbh->rollback if $oldAutoCommit; + return "Can't delete a master employee customer"; } - if ( $self->cust_refund ) { - $dbh->rollback if $oldAutoCommit; - return "Can't delete a customer with refunds"; + + tie my %financial_tables, 'Tie::IxHash', + 'cust_bill' => 'invoices', + 'cust_statement' => 'statements', + 'cust_credit' => 'credits', + 'cust_pay' => 'payments', + 'cust_refund' => 'refunds', + ; + + foreach my $table ( keys %financial_tables ) { + + my @records = $self->$table(); + + if ( @records && ! $opt{'delete_financials'} ) { + $dbh->rollback if $oldAutoCommit; + return "Can't delete a customer with ". $financial_tables{$table}; + } + + foreach my $record ( @records ) { + my $error = $record->delete; + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return "Error deleting ". $financial_tables{$table}. ": $error\n"; + } + } + } my @cust_pkg = $self->ncancelled_pkgs; if ( @cust_pkg ) { - my $new_custnum = shift; + my $new_custnum = $opt{'new_custnum'}; unless ( qsearchs( 'cust_main', { 'custnum' => $new_custnum } ) ) { $dbh->rollback if $oldAutoCommit; return "Invalid new customer number: $new_custnum"; @@ -1381,7 +1404,14 @@ sub delete { } } - foreach my $table (qw( cust_main_invoice cust_main_exemption cust_tag )) { + #cust_tax_adjustment in financials? + #cust_pay_pending? ouch + foreach my $table (qw( + cust_main_invoice cust_main_exemption cust_tag cust_attachment contact + cust_location cust_main_note cust_tax_adjustment + cust_pay_void cust_pay_batch queue cust_tax_exempt + cust_recon + )) { foreach my $record ( qsearch( $table, { 'custnum' => $self->custnum } ) ) { my $error = $record->delete; if ( $error ) { @@ -1391,6 +1421,19 @@ sub delete { } } + my $sth = $dbh->prepare( + 'UPDATE cust_main SET referral_custnum = NULL WHERE referral_custnum = ?' + ) or do { + my $errstr = $dbh->errstr; + $dbh->rollback if $oldAutoCommit; + return $errstr; + }; + $sth->execute($self->custnum) or do { + my $errstr = $sth->errstr; + $dbh->rollback if $oldAutoCommit; + return $errstr; + }; + my $error = $self->SUPER::delete; if ( $error ) { $dbh->rollback if $oldAutoCommit; -- cgit v1.2.1 From 14958f5ddaaa3216fe0d5f895c7552405ef3f923 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 11 Aug 2010 21:53:56 +0000 Subject: at least show an error for bad pkgparts instead of a hang, RT#9578 --- FS/FS/cust_main/Import.pm | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'FS') diff --git a/FS/FS/cust_main/Import.pm b/FS/FS/cust_main/Import.pm index 901ff18ae..e2165a096 100644 --- a/FS/FS/cust_main/Import.pm +++ b/FS/FS/cust_main/Import.pm @@ -328,6 +328,12 @@ sub batch_import { tie my %hash, 'Tie::RefHash'; #this part is important if ( $cust_pkg{'pkgpart'} ) { + + unless ( $cust_pkg{'pkgpart'} =~ /^\d+$/ ) { + $dbh->rollback if $oldAutoCommit; + return 'illegal pkgpart: '. $cust_pkg{'pkgpart'}; + } + my $cust_pkg = new FS::cust_pkg ( \%cust_pkg ); my @svc_x = (); -- cgit v1.2.1 From 930618349ad4478a6fd537685e61f17e1b3af5b1 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 12 Aug 2010 17:36:32 +0000 Subject: cust_recon throws errors and it is not a normally used table anyway --- FS/FS/cust_main.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 83cb25b3e..6b34712e9 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1406,11 +1406,11 @@ sub delete { #cust_tax_adjustment in financials? #cust_pay_pending? ouch + #cust_recon? foreach my $table (qw( cust_main_invoice cust_main_exemption cust_tag cust_attachment contact cust_location cust_main_note cust_tax_adjustment cust_pay_void cust_pay_batch queue cust_tax_exempt - cust_recon )) { foreach my $record ( qsearch( $table, { 'custnum' => $self->custnum } ) ) { my $error = $record->delete; -- cgit v1.2.1 From d1c3bdca8b00a5accace9121edabc675698af001 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 12 Aug 2010 17:43:21 +0000 Subject: referraldefault dropdown in config, RT#9599 --- FS/FS/Conf.pm | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index fdb6e9a38..8f6a70ca5 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1361,7 +1361,20 @@ and customer address. Include units.', 'key' => 'referraldefault', 'section' => 'UI', 'description' => 'Default referral, specified by refnum', - 'type' => 'text', + 'type' => 'select-sub', + 'options_sub' => sub { require FS::Record; + require FS::part_referral; + map { $_->refnum => $_->referral } + FS::Record::qsearch( 'part_referral', + { 'disabled' => '' } + ); + }, + 'option_sub' => sub { require FS::Record; + require FS::part_referral; + my $part_referral = FS::Record::qsearchs( + 'part_referral', { 'refnum'=>shift } ); + $part_referral ? $part_referral->referral : ''; + }, }, # { -- cgit v1.2.1 From 3706609762d9cec964f337e74829031b895ddbac Mon Sep 17 00:00:00 2001 From: mark Date: Thu, 12 Aug 2010 21:31:19 +0000 Subject: self-service Drupal module, RT#9380 --- FS/FS/ClientAPI/Signup.pm | 5 ++++- FS/FS/ClientAPI_XMLRPC.pm | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'FS') diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm index 5d70325d2..a4032f3b1 100644 --- a/FS/FS/ClientAPI/Signup.pm +++ b/FS/FS/ClientAPI/Signup.pm @@ -90,7 +90,7 @@ sub signup_info { ], 'agent' => [ map { my $agent = $_; - map { $_ => $agent->get($_) } @agent_fields; + +{ map { $_ => $agent->get($_) } @agent_fields } } qsearch('agent', { 'disabled' => '' } ) ], @@ -111,6 +111,9 @@ sub signup_info { 'payby' => [ $conf->config('signup_server-payby') ], + 'payby_longname' => [ map { FS::payby->longname($_) } + $conf->config('signup_server-payby') ], + 'card_types' => card_types(), 'paytypes' => [ @FS::cust_main::paytypes ], diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm index 138ad06a4..cfaf009c7 100644 --- a/FS/FS/ClientAPI_XMLRPC.pm +++ b/FS/FS/ClientAPI_XMLRPC.pm @@ -49,7 +49,7 @@ sub AUTOLOAD { #no strict 'refs'; #&{$call}(@_); #FS::ClientAPI->dispatch($autoload->{$call}, @_); - FS::ClientAPI->dispatch($autoload->{$call}, { @_ } ); + FS::ClientAPI->dispatch($autoload->{$call}, @_ ); }else{ die "No such procedure: $call"; } -- cgit v1.2.1 From bdfbb5a929adcaa493d046145bec6d6914e9611b Mon Sep 17 00:00:00 2001 From: jeff Date: Fri, 13 Aug 2010 05:51:25 +0000 Subject: make ut_textn analogous to ut_text --- FS/FS/Record.pm | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'FS') diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 758e0f96c..ab4ea2a5b 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -2178,11 +2178,8 @@ May be null. If there is an error, returns the error, otherwise returns false. sub ut_textn { my($self,$field)=@_; - $self->getfield($field) - =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/ - or return gettext('illegal_text'). " $field: ". $self->getfield($field); - $self->setfield($field,$1); - ''; + return $self->setfield($field, '') if $self-getfield($field) =~ /^$/; + $self->ut_text($field); } =item ut_alpha COLUMN -- cgit v1.2.1 From 162706c35d0523d437038fbb28dfe626ac0252f4 Mon Sep 17 00:00:00 2001 From: jeff Date: Fri, 13 Aug 2010 05:55:33 +0000 Subject: tyop --- FS/FS/Record.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index ab4ea2a5b..71cc69ca9 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -2178,7 +2178,7 @@ May be null. If there is an error, returns the error, otherwise returns false. sub ut_textn { my($self,$field)=@_; - return $self->setfield($field, '') if $self-getfield($field) =~ /^$/; + return $self->setfield($field, '') if $self->getfield($field) =~ /^$/; $self->ut_text($field); } -- cgit v1.2.1 From 938615d6374c8e868647b88e3aa27c15e942b8ed Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 13 Aug 2010 17:41:58 +0000 Subject: ignore expired cards on customer import --- FS/FS/cust_main/Import.pm | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'FS') diff --git a/FS/FS/cust_main/Import.pm b/FS/FS/cust_main/Import.pm index e2165a096..e58a0447b 100644 --- a/FS/FS/cust_main/Import.pm +++ b/FS/FS/cust_main/Import.pm @@ -214,6 +214,10 @@ sub batch_import { my $oldAutoCommit = $FS::UID::AutoCommit; local $FS::UID::AutoCommit = 0; my $dbh = dbh; + + #implies ignore_expired_card + local($FS::cust_main::import) = 1; + local($FS::cust_main::import) = 1; my $line; my $row = 0; -- cgit v1.2.1 From 955c17771c7aa05a2609309809809be37f4c8267 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 13 Aug 2010 19:26:22 +0000 Subject: add cust_main-title-display_custnum, RT#9621 --- FS/FS/Conf.pm | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'FS') diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 8f6a70ca5..c436413d6 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -3257,6 +3257,13 @@ and customer address. Include units.', 'type' => 'checkbox', }, + { + 'key' => 'cust_main-title-display_custnum', + 'section' => 'UI', + 'description' => 'Add the display_custom (agent_custid or custnum) to the title on customer view pages.', + 'type' => 'checkbox', + }, + { 'key' => 'cust_bill-default_agent_invid', 'section' => 'UI', -- cgit v1.2.1 From e79d507abfc9edacbc24e19753493974c9f1661a Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 13 Aug 2010 19:53:52 +0000 Subject: slightly better customer delete; remove links to tickets, RT#9626 --- FS/FS/cust_main.pm | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 6b34712e9..f7f8facb4 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1434,6 +1434,41 @@ sub delete { return $errstr; }; + #tickets + + my $ticket_dbh = ''; + if ($conf->config('ticket_system') eq 'RT_Internal') { + $ticket_dbh = $dbh; + } elsif ($conf->config('ticket_system') eq 'RT_External') { + my ($datasrc, $user, $pass) = $conf->config('ticket_system-rt_external_datasrc'); + $ticket_dbh = DBI->connect($datasrc, $user, $pass, { 'ChopBlanks' => 1 }); + #or die "RT_External DBI->connect error: $DBI::errstr\n"; + } + + if ( $ticket_dbh ) { + + my $ticket_sth = $ticket_dbh->prepare( + 'DELETE FROM Links WHERE Target = ?' + ) or do { + my $errstr = $ticket_dbh->errstr; + $dbh->rollback if $oldAutoCommit; + return $errstr; + }; + $ticket_sth->execute('freeside://freeside/cust_main/'.$self->custnum) + or do { + my $errstr = $ticket_sth->errstr; + $dbh->rollback if $oldAutoCommit; + return $errstr; + }; + + #check and see if the customer is the only link on the ticket, and + #if so, set the ticket to deleted status in RT? + #maybe someday, for now this will at least fix tickets not displaying + + } + + #delete the customer record + my $error = $self->SUPER::delete; if ( $error ) { $dbh->rollback if $oldAutoCommit; -- cgit v1.2.1 From 7515782ca6e453f2e4c9a52c62429e73ce047247 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 15 Aug 2010 00:44:55 +0000 Subject: address root cause of rt/rt links and remove the workarounds, RT#9280 --- FS/FS/Mason.pm | 20 ++++++++++++++------ FS/FS/Mason/Request.pm | 28 +++++++++++++--------------- 2 files changed, 27 insertions(+), 21 deletions(-) (limited to 'FS') diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index f5d7c8566..7be78aa03 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -453,7 +453,7 @@ sub mason_interps { RT::LoadConfig(); } - # A hook supporting strange legacy ways people have added stuff on + # A hook supporting strange legacy ways people (well, SG) have added stuff on my @addl_comp_root = (); my $addl_comp_root_file = '%%%FREESIDE_CONF%%%/addl_comp_root.pl'; @@ -468,17 +468,20 @@ sub mason_interps { } } + my $fs_comp_root = + scalar(@addl_comp_root) + ? [ + [ 'freeside'=>'%%%FREESIDE_DOCUMENT_ROOT%%%' ], + @addl_comp_root, + ] + : '%%%FREESIDE_DOCUMENT_ROOT%%%'; + my %interp = ( request_class => $request_class, data_dir => '%%%MASONDATA%%%', error_mode => 'output', error_format => 'html', ignore_warnings_expr => '.', - comp_root => [ - [ 'freeside'=>'%%%FREESIDE_DOCUMENT_ROOT%%%' ], - [ 'rt' =>'%%%FREESIDE_DOCUMENT_ROOT%%%/rt' ], - @addl_comp_root, - ], ); $interp{out_method} = $opt{outbuf} if $mode eq 'standalone' && $opt{outbuf}; @@ -495,6 +498,7 @@ sub mason_interps { my $fs_interp = new HTML::Mason::Interp ( %interp, + comp_root => $fs_comp_root, escape_flags => { 'js_string' => $js_string_sub, 'defang' => sub { ${$_[0]} = $html_defang->defang(${$_[0]}); @@ -507,6 +511,10 @@ sub mason_interps { my $rt_interp = new HTML::Mason::Interp ( %interp, + comp_root => [ + [ 'rt' => '%%%FREESIDE_DOCUMENT_ROOT%%%/rt' ], + [ 'freeside' => '%%%FREESIDE_DOCUMENT_ROOT%%%' ], + ], escape_flags => { 'h' => \&RT::Interface::Web::EscapeUTF8, 'js_string' => $js_string_sub, }, diff --git a/FS/FS/Mason/Request.pm b/FS/FS/Mason/Request.pm index 8d66f4fff..95c802796 100644 --- a/FS/FS/Mason/Request.pm +++ b/FS/FS/Mason/Request.pm @@ -37,9 +37,19 @@ sub freeside_setup { my( $filename, $mode ) = @_; - #warn "initializing for $filename\n"; + if ( $filename =~ qr(/REST/\d+\.\d+/NoAuth/) ) { - if ( $filename !~ /\/rt\/.*NoAuth/ ) { #not RT images/JS + package HTML::Mason::Commands; #? + use FS::UID qw( adminsuidsetup ); + + #need to log somebody in for the mail gw + + ##old installs w/fs_selfs or selfserv?? + #&adminsuidsetup('fs_selfservice'); + + &adminsuidsetup('fs_queue'); + + } else { package HTML::Mason::Commands; use vars qw( $cgi $p $fsurl ); @@ -62,19 +72,7 @@ sub freeside_setup { die "unknown mode $mode"; } - } elsif ( $filename =~ /\/rt\/REST\/.*NoAuth/ ) { - - package HTML::Mason::Commands; #? - use FS::UID qw( adminsuidsetup ); - - #need to log somebody in for the mail gw - - ##old installs w/fs_selfs or selfserv?? - #&adminsuidsetup('fs_selfservice'); - - &adminsuidsetup('fs_queue'); - - } + } } -- cgit v1.2.1 From 4774ede40353662ddcb4181d824ab2167c68ff8c Mon Sep 17 00:00:00 2001 From: mark Date: Mon, 16 Aug 2010 17:49:03 +0000 Subject: Bcc address for impending recur notices, RT#8953 --- FS/FS/Misc.pm | 14 +++++++++++--- FS/FS/Schema.pm | 1 + FS/FS/msg_template.pm | 20 +++++++++++--------- 3 files changed, 23 insertions(+), 12 deletions(-) (limited to 'FS') diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm index 3b0616a91..0e8d92bb3 100644 --- a/FS/FS/Misc.pm +++ b/FS/FS/Misc.pm @@ -113,7 +113,7 @@ sub send_email { # join("\n", map { " $_: ". $options{$_} } keys %options ). "\n" } - my $to = ref($options{to}) ? join(', ', @{ $options{to} } ) : $options{to}; + my @to = ref($options{to}) ? @{ $options{to} } : ( $options{to} ); my @mimeargs = (); my @mimeparts = (); @@ -172,7 +172,7 @@ sub send_email { my $message = MIME::Entity->build( 'From' => $options{'from'}, - 'To' => $to, + 'To' => join(', ', @to), 'Sender' => $options{'from'}, 'Reply-To' => $options{'from'}, 'Date' => time2str("%a, %d %b %Y %X %z", time), @@ -232,8 +232,11 @@ sub send_email { $transport = Email::Sender::Transport::SMTP->new( %smtp_opt ); } + push @to, $options{bcc} if defined($options{bcc}); local $@; # just in case - eval { sendmail($message, { transport => $transport }) }; + eval { sendmail($message, { transport => $transport, + from => $options{from}, + to => \@to }) }; if(ref($@) and $@->isa('Email::Sender::Failure')) { return ($@->code ? $@->code.' ' : '').$@->message @@ -257,6 +260,10 @@ Sender address, required Recipient address, required +=item bcc + +Blind copy address, optional + =item subject email subject, required @@ -290,6 +297,7 @@ sub generate_email { my %return = ( 'from' => $args{'from'}, 'to' => $args{'to'}, + 'bcc' => $args{'bcc'}, 'subject' => $args{'subject'}, ); diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index dc8f2f3aa..076be7ed8 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2946,6 +2946,7 @@ sub tables_hashref { 'body', 'blob', 'NULL', '', '', '', 'disabled', 'char', 'NULL', 1, '', '', 'from_addr', 'varchar', 'NULL', 255, '', '', + 'bcc_addr', 'varchar', 'NULL', 255, '', '', ], 'primary_key' => 'msgnum', 'unique' => [ ['msgname', 'mime_type'] ], diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm index d1db17dbc..121742129 100644 --- a/FS/FS/msg_template.pm +++ b/FS/FS/msg_template.pm @@ -255,9 +255,10 @@ sub prepare { my $conf = new FS::Conf; ( - 'from' => $self->from || + 'from' => $self->from_addr || scalar( $conf->config('invoice_from', $cust_main->agentnum) ), 'to' => \@to, + 'bcc' => $self->bcc_addr || undef, 'subject' => $subject, 'html_body' => $body, 'text_body' => HTML::FormatText->new(leftmargin => 0, rightmargin => 70 @@ -398,26 +399,27 @@ sub _upgrade_data { my ($self, %opts) = @_; my @fixes = ( - [ 'alerter_msgnum', 'alerter_template', '', '' ], - [ 'cancel_msgnum', 'cancelmessage', 'cancelsubject', '' ], - [ 'decline_msgnum', 'declinetemplate', '', '' ], - [ 'impending_recur_msgnum', 'impending_recur_template', '', '' ], - [ 'payment_receipt_msgnum', 'payment_receipt_email', '', '' ], - [ 'welcome_msgnum', 'welcome_email', 'welcome_email-subject', 'welcome_email-from' ], - [ 'warning_msgnum', 'warning_email', 'warning_email-subject', 'warning_email-from' ], + [ 'alerter_msgnum', 'alerter_template', '', '', '' ], + [ 'cancel_msgnum', 'cancelmessage', 'cancelsubject', '', '' ], + [ 'decline_msgnum', 'declinetemplate', '', '', '' ], + [ 'impending_recur_msgnum', 'impending_recur_template', '', '', 'impending_recur_bcc' ], + [ 'payment_receipt_msgnum', 'payment_receipt_email', '', '', '' ], + [ 'welcome_msgnum', 'welcome_email', 'welcome_email-subject', 'welcome_email-from', '' ], + [ 'warning_msgnum', 'warning_email', 'warning_email-subject', 'warning_email-from', '' ], ); my $conf = new FS::Conf; my @agentnums = ('', map {$_->agentnum} qsearch('agent', {})); foreach my $agentnum (@agentnums) { foreach (@fixes) { - my ($newname, $oldname, $subject, $from) = @$_; + my ($newname, $oldname, $subject, $from, $bcc) = @$_; if ($conf->exists($oldname, $agentnum)) { my $new = new FS::msg_template({ 'msgname' => $oldname, 'agentnum' => $agentnum, 'from_addr' => ($from && $conf->config($from, $agentnum)) || $conf->config('invoice_from', $agentnum), + 'bcc_addr' => ($bcc && $conf->config($from, $agentnum)) || '', 'subject' => ($subject && $conf->config($subject, $agentnum)) || '', 'mime_type' => 'text/html', 'body' => join('
',$conf->config($oldname, $agentnum)), -- cgit v1.2.1 From d9c554c746466a20bbbbc2eb69fc737cfe598316 Mon Sep 17 00:00:00 2001 From: ivan Date: Mon, 16 Aug 2010 20:12:45 +0000 Subject: fix upgrade with ancient cust_bill_pkg_detail.classnum but new DBIx::DBSchema, RT#9640 --- FS/FS/Upgrade.pm | 68 ++++++++++++++++++++++++++++++++- FS/FS/cust_bill_pkg_detail.pm | 88 +++++++++++++++++++++---------------------- FS/bin/freeside-upgrade | 4 +- 3 files changed, 111 insertions(+), 49 deletions(-) (limited to 'FS') diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm index b7a1c661a..185ba16fc 100644 --- a/FS/FS/Upgrade.pm +++ b/FS/FS/Upgrade.pm @@ -12,7 +12,7 @@ use FS::svc_domain; $FS::svc_domain::whois_hack = 1; @ISA = qw( Exporter ); -@EXPORT_OK = qw( upgrade upgrade_sqlradius ); +@EXPORT_OK = qw( upgrade_schema upgrade upgrade_sqlradius ); $DEBUG = 1; @@ -33,7 +33,7 @@ database upgrades. =over 4 -=item +=item upgrade =cut @@ -86,6 +86,9 @@ sub upgrade { } +=item upgrade_data + +=cut sub upgrade_data { my %opt = @_; @@ -166,6 +169,67 @@ sub upgrade_data { } +=item upgrade_schema + +=cut + +sub upgrade_schema { + my %opt = @_; + + my $data = upgrade_schema_data(%opt); + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + local $FS::UID::AutoCommit = 0; + + foreach my $table ( keys %$data ) { + + my $class = "FS::$table"; + eval "use $class;"; + die $@ if $@; + + if ( $class->can('_upgrade_schema') ) { + warn "Upgrading $table schema...\n"; + + my $start = time; + + $class->_upgrade_schema(%opt); + + if ( $oldAutoCommit ) { + warn " committing\n"; + dbh->commit or die dbh->errstr; + } + + #warn "\e[1K\rUpgrading $table... done in ". (time-$start). " seconds\n"; + warn " done in ". (time-$start). " seconds\n"; + + } else { + warn "WARNING: asked for schema upgrade of $table,". + " but FS::$table has no _upgrade_schema method\n"; + } + + } + +} + +=item upgrade_schema_data + +=cut + +sub upgrade_schema_data { + my %opt = @_; + + tie my %hash, 'Tie::IxHash', + + #fix classnum character(1) + 'cust_bill_pkg_detail' => [], + + ; + + \%hash; + +} + sub upgrade_sqlradius { #my %opt = @_; diff --git a/FS/FS/cust_bill_pkg_detail.pm b/FS/FS/cust_bill_pkg_detail.pm index 4d9ee8191..b8af01303 100644 --- a/FS/FS/cust_bill_pkg_detail.pm +++ b/FS/FS/cust_bill_pkg_detail.pm @@ -231,11 +231,8 @@ sub formatted { } -# _upgrade_data -# -# Used by FS::Upgrade to migrate to a new database. - -sub _upgrade_data { # class method +# Used by FS::Upgrade to migrate to a new database schema +sub _upgrade_schema { # class method my ($class, %opts) = @_; @@ -313,51 +310,50 @@ sub _upgrade_data { # class method } +} - if ( defined( dbdef->table($class->table)->column('billpkgnum') ) && - defined( dbdef->table($class->table)->column('invnum') ) && - defined( dbdef->table($class->table)->column('pkgnum') ) - ) { - - warn "$me Checking for unmigrated invoice line item details\n" if $DEBUG; - - my @cbpd = qsearch({ 'table' => $class->table, - 'hashref' => {}, - 'extra_sql' => 'WHERE invnum IS NOT NULL AND '. - 'pkgnum IS NOT NULL', - }); - - if (scalar(@cbpd)) { - warn "$me Found unmigrated invoice line item details\n" if $DEBUG; - - foreach my $cbpd ( @cbpd ) { - my $detailnum = $cbpd->detailnum; - warn "$me Contemplating detail $detailnum\n" if $DEBUG > 1; - my $cust_bill_pkg = - qsearchs({ 'table' => 'cust_bill_pkg', - 'hashref' => { 'invnum' => $cbpd->invnum, - 'pkgnum' => $cbpd->pkgnum, - }, - 'order_by' => 'ORDER BY billpkgnum LIMIT 1', - }); - if ($cust_bill_pkg) { - $cbpd->billpkgnum($cust_bill_pkg->billpkgnum); - $cbpd->invnum(''); - $cbpd->pkgnum(''); - my $error = $cbpd->replace; - - warn "*** WARNING: error replacing line item detail ". - "(cust_bill_pkg_detail) $detailnum: $error ***\n" - if $error; - } else { - warn "Found orphaned line item detail $detailnum during upgrade.\n"; - } +# Used by FS::Upgrade to migrate to a new database +sub _upgrade_data { # class method - } # foreach $cbpd + my ($class, %opts) = @_; + + warn "$me Checking for unmigrated invoice line item details\n" if $DEBUG; + + my @cbpd = qsearch({ 'table' => $class->table, + 'hashref' => {}, + 'extra_sql' => 'WHERE invnum IS NOT NULL AND '. + 'pkgnum IS NOT NULL', + }); + + if (scalar(@cbpd)) { + warn "$me Found unmigrated invoice line item details\n" if $DEBUG; + + foreach my $cbpd ( @cbpd ) { + my $detailnum = $cbpd->detailnum; + warn "$me Contemplating detail $detailnum\n" if $DEBUG > 1; + my $cust_bill_pkg = + qsearchs({ 'table' => 'cust_bill_pkg', + 'hashref' => { 'invnum' => $cbpd->invnum, + 'pkgnum' => $cbpd->pkgnum, + }, + 'order_by' => 'ORDER BY billpkgnum LIMIT 1', + }); + if ($cust_bill_pkg) { + $cbpd->billpkgnum($cust_bill_pkg->billpkgnum); + $cbpd->invnum(''); + $cbpd->pkgnum(''); + my $error = $cbpd->replace; + + warn "*** WARNING: error replacing line item detail ". + "(cust_bill_pkg_detail) $detailnum: $error ***\n" + if $error; + } else { + warn "Found orphaned line item detail $detailnum during upgrade.\n"; + } - } # if @cbpd + } # foreach $cbpd - } # if billpkgnum, invnum, and pkgnum columns defined + } # if @cbpd ''; diff --git a/FS/bin/freeside-upgrade b/FS/bin/freeside-upgrade index 4a6fac293..e22afa26c 100755 --- a/FS/bin/freeside-upgrade +++ b/FS/bin/freeside-upgrade @@ -11,7 +11,7 @@ use FS::Schema qw( dbdef dbdef_dist reload_dbdef ); use FS::Misc::prune qw(prune_applications); use FS::Conf; use FS::Record qw(qsearch); -use FS::Upgrade qw(upgrade upgrade_sqlradius); +use FS::Upgrade qw(upgrade_schema upgrade upgrade_sqlradius); my $start = time; @@ -82,6 +82,8 @@ if ( $DRY_RUN ) { or die "Error: ". $dbh->errstr. "\n executing: $statement"; } + upgrade_schema(); + dbdef_create($dbh, $dbdef_file); delete $FS::Schema::dbdef_cache{$dbdef_file}; #force an actual reload reload_dbdef($dbdef_file); -- cgit v1.2.1 From 4cf169cc5a1de8b1ab40f88b1ee388efca7fedaf Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 17 Aug 2010 06:41:29 +0000 Subject: LEAVE is reserved in msyql --- FS/FS/Schema.pm | 2 +- FS/FS/acct_snarf.pm | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'FS') diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 076be7ed8..cc6438ab6 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2181,7 +2181,7 @@ sub tables_hashref { 'username', 'varchar', '', $char_d, '', '', '_password', 'varchar', '', $char_d, '', '', 'check_freq', 'int', 'NULL', '', '', '', - 'leave', 'char', 'NULL', 1, '', '', + 'leavemail', 'char', 'NULL', 1, '', '', 'apop', 'char', 'NULL', 1, '', '', 'tls', 'char', 'NULL', 1, '', '', 'mailbox', 'varchar', 'NULL', $char_d, '', '', diff --git a/FS/FS/acct_snarf.pm b/FS/FS/acct_snarf.pm index 480a632bb..9816de965 100644 --- a/FS/FS/acct_snarf.pm +++ b/FS/FS/acct_snarf.pm @@ -142,7 +142,7 @@ sub check { || $self->ut_alphan('protocol') || $self->ut_textn('username') || $self->ut_numbern('check_freq') - || $self->ut_enum('leave', [ '', 'Y' ]) + || $self->ut_enum('leavemail', [ '', 'Y' ]) || $self->ut_enum('apop', [ '', 'Y' ]) || $self->ut_enum('tls', [ '', 'Y' ]) || $self->ut_alphan('mailbox') @@ -195,9 +195,9 @@ sub cgp_hashref { 'domain' => $self->machine, 'password' => $self->_password, 'period' => $self->check_freq.'s', - 'APOP' => ( $self->apop eq 'Y' ? 'YES' : 'NO' ), - 'TLS' => ( $self->tls eq 'Y' ? 'YES' : 'NO' ), - 'Leave' => ( $self->leave eq 'Y' ? 'YES' : 'NO' ), #XXX leave?? + 'APOP' => ( $self->apop eq 'Y' ? 'YES' : 'NO' ), + 'TLS' => ( $self->tls eq 'Y' ? 'YES' : 'NO' ), + 'Leave' => ( $self->leavemail eq 'Y' ? 'YES' : 'NO' ), #XXX leave?? }; } -- cgit v1.2.1 From 96ae2087c30bbe8f4db97879c105843bdd3fc295 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 17 Aug 2010 17:05:06 +0000 Subject: communigate phase 3: certificates, RT#7515 --- FS/MANIFEST | 2 ++ 1 file changed, 2 insertions(+) (limited to 'FS') diff --git a/FS/MANIFEST b/FS/MANIFEST index 1b2e08df8..db3f5cfe6 100644 --- a/FS/MANIFEST +++ b/FS/MANIFEST @@ -528,3 +528,5 @@ FS/part_tag.pm t/part_tag.t FS/svc_CGP_Mixin.pm FS/svc_CGPRule_Mixin.pm +FS/svc_cert.pm +t/svc_cert.t -- cgit v1.2.1 From 9bdca2fe23584918219395b62effbb9ef0fc5f1c Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 17 Aug 2010 17:08:22 +0000 Subject: DNS, RT#8933 --- FS/FS/Conf.pm | 7 ++++++- FS/FS/domain_record.pm | 12 ++++++++++++ FS/FS/svc_domain.pm | 32 ++++++++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 5 deletions(-) (limited to 'FS') diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index c436413d6..fe010f77b 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -1308,7 +1308,12 @@ and customer address. Include units.', 'editlist_parts' => [ { type=>'text' }, { type=>'immutable', value=>'IN' }, { type=>'select', - select_enum=>{ map { $_=>$_ } qw(A CNAME MX NS TXT)} }, + select_enum => { + map { $_=>$_ } + #@{ FS::domain_record->rectypes } + qw(A AAAA CNAME MX NS PTR SPF SRV TXT) + }, + }, { type=> 'text' }, ], }, diff --git a/FS/FS/domain_record.pm b/FS/FS/domain_record.pm index 6513abf25..e7e9f70b7 100644 --- a/FS/FS/domain_record.pm +++ b/FS/FS/domain_record.pm @@ -419,6 +419,18 @@ sub reverse_record { or new FS::domain_record { %hash, 'recdata' => $self->zone.'.' }; } +=item rectypes + +=cut +#http://en.wikipedia.org/wiki/List_of_DNS_record_types +#DHCID? other things? +sub rectypes { + [ qw(A AAAA CNAME MX NS PTR SPF SRV TXT), #most common types + #qw(DNAME), #uncommon types + qw(DLV DNSKEY DS NSEC NSEC3 NSEC3PARAM RRSIG), #DNSSEC types + ]; +} + =back =head1 BUGS diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm index 3dc352b7a..7d527e5be 100644 --- a/FS/FS/svc_domain.pm +++ b/FS/FS/svc_domain.pm @@ -341,12 +341,36 @@ sub insert { local $FS::UID::AutoCommit = 0; my $dbh = dbh; - $error = $self->SUPER::insert(@_); + $error = $self->SUPER::insert(@_) + || $self->insert_defaultrecords; if ( $error ) { $dbh->rollback if $oldAutoCommit; return $error; } + $dbh->commit or die $dbh->errstr if $oldAutoCommit; + + ''; #no error +} + +=item insert_defaultrecords + +=cut + +sub insert_defaultrecords { + 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; + if ( $soamachine ) { my $soa = new FS::domain_record { 'svcnum' => $self->svcnum, @@ -356,10 +380,10 @@ sub insert { 'recdata' => "$soamachine $soaemail ( ". time2str("%Y%m%d", time). "00 ". "$soarefresh $soaretry $soaexpire $soadefaultttl )" }; - $error = $soa->insert; + my $error = $soa->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "couldn't insert SOA record for new domain: $error"; + return "couldn't insert SOA record: $error"; } foreach my $record ( @defaultrecords ) { @@ -374,7 +398,7 @@ sub insert { my $error = $domain_record->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "couldn't insert record for new domain: $error"; + return "couldn't insert record: $error"; } } -- cgit v1.2.1 From 397c392e39c4006361144db5e262779df80ac0c2 Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 18 Aug 2010 01:33:26 +0000 Subject: allow sections to work without 'use_separation,' correct packages hidden behind zero value packages, correct section handling, and fix propogation of other display attributes to child packages --- FS/FS/cust_bill.pm | 9 ++++--- FS/FS/cust_main.pm | 76 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 66 insertions(+), 19 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 3e7109ec9..b73e360af 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -2657,6 +2657,7 @@ sub print_generic { $options{'skip_usage'} = scalar(@$extra_sections) && !grep{$section == $_} @$extra_sections; $options{'multilocation'} = $multilocation; + $options{'multisection'} = $multisection; foreach my $line_item ( $self->_items_pkg(%options) ) { my $detail = { @@ -3991,6 +3992,7 @@ sub _items_cust_bill_pkg { my $section = $opt{section}->{description} if $opt{section}; my $summary_page = $opt{summary_page} || ''; my $multilocation = $opt{multilocation} || ''; + my $multisection = $opt{multisection} || ''; my @b = (); my ($s, $r, $u) = ( undef, undef, undef ); @@ -4012,7 +4014,8 @@ sub _items_cust_bill_pkg { ? $_->section eq $section : 1 } - grep { !$_->summary || !$summary_page } + #grep { !$_->summary || !$summary_page } # bunk! + grep { !$_->summary || $multisection } $cust_bill_pkg->cust_bill_pkg_display ) { @@ -4071,7 +4074,7 @@ sub _items_cust_bill_pkg { } - if ( $cust_bill_pkg->recur != 0 && + if ( ( $cust_bill_pkg->recur != 0 || $cust_bill_pkg->setup == 0 ) && ( !$type || $type eq 'R' || $type eq 'U' ) ) { @@ -4141,7 +4144,7 @@ sub _items_cust_bill_pkg { }; } - } elsif ( $amount ) { # && $type eq 'U' + } else { # $type eq 'U' if ( $cust_bill_pkg->hidden ) { $u->{amount} += $amount; diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index f7f8facb4..168403482 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2979,7 +2979,13 @@ sub bill { my $real_pkgpart = $cust_pkg->pkgpart; my %hash = $cust_pkg->hash; - foreach my $part_pkg ( $cust_pkg->part_pkg->self_and_bill_linked ) { + # we could implement this bit as FS::part_pkg::has_hidden, but we already + # suffer from performance issues + $options{has_hidden} = 0; + my @part_pkg = $cust_pkg->part_pkg->self_and_bill_linked; + $options{has_hidden} = 1 if ($part_pkg[1] && $part_pkg[1]->hidden); + + foreach my $part_pkg ( @part_pkg ) { $cust_pkg->set($_, $hash{$_}) foreach qw ( setup last_bill bill ); @@ -3033,7 +3039,13 @@ sub bill { } elsif ( $postal_pkg ) { my $real_pkgpart = $postal_pkg->pkgpart; - foreach my $part_pkg ( $postal_pkg->part_pkg->self_and_bill_linked ) { + # we could implement this bit as FS::part_pkg::has_hidden, but we already + # suffer from performance issues + $options{has_hidden} = 0; + my @part_pkg = $postal_pkg->part_pkg->self_and_bill_linked; + $options{has_hidden} = 1 if ($part_pkg[1] && $part_pkg[1]->hidden); + + foreach my $part_pkg ( @part_pkg ) { my %postal_options = %options; delete $postal_options{cancel}; my $error = @@ -3128,12 +3140,24 @@ sub bill { return "can't create invoice for customer #". $self->custnum. ": $error"; } + my @cust_bill_pkg_bundle = (); foreach my $cust_bill_pkg ( @cust_bill_pkg ) { $cust_bill_pkg->invnum($cust_bill->invnum); - my $error = $cust_bill_pkg->insert; + if (scalar(@cust_bill_pkg_bundle) && !$cust_bill_pkg->pkgpart_override) { + $error = $self->_insert_cust_bill_pkg_bundle( @cust_bill_pkg_bundle ); + if ( $error ) { + $dbh->rollback if $oldAutoCommit; + return $error; + } + @cust_bill_pkg_bundle = (); + } + push @cust_bill_pkg_bundle, $cust_bill_pkg; + } + if (scalar(@cust_bill_pkg_bundle)) { + $error = $self->_insert_cust_bill_pkg_bundle( @cust_bill_pkg_bundle ); if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "can't create invoice line item: $error"; + return $error; } } @@ -3153,6 +3177,22 @@ sub bill { ''; #no error } +#insert line items while discarding bundled packages of 0 value +sub _insert_cust_bill_pkg_bundle { + my $self = shift; + my @cust_bill_pkg = @_; + + my $sum = 0; + $sum += $_->setup + $_->recur foreach @cust_bill_pkg; + return '' unless $sum > 0; + + foreach my $cust_bill_pkg ( @cust_bill_pkg ) { + my $error = $cust_bill_pkg->insert; + return "can't create invoice line item: $error" if $error; + } + +} + =item calculate_taxes LINEITEMREF TAXHASHREF INVOICE_TIME This is a weird one. Perhaps it should not even be exposed. @@ -3471,7 +3511,7 @@ sub _make_lines { # If $cust_pkg has been modified, update it (if we're a real pkgpart) ### - if ( $lineitems ) { + if ( $lineitems || $options{has_hidden} ) { if ( $cust_pkg->modified && $cust_pkg->pkgpart == $real_pkgpart ) { # hmm.. and if just the options are modified in some weird price plan? @@ -3495,7 +3535,10 @@ sub _make_lines { return "negative recur $recur for pkgnum ". $cust_pkg->pkgnum; } - if ( $setup != 0 || $recur != 0 ) { + if ( $setup != 0 || + $recur != 0 || + !$part_pkg->hidden && $options{has_hidden} ) #include some $0 lines + { warn " charges (setup=$setup, recur=$recur); adding line items\n" if $DEBUG > 1; @@ -3662,16 +3705,15 @@ sub _handle_taxes { my @display = (); my $separate = $conf->exists('separate_usage'); - my $usage_mandate = $cust_pkg->part_pkg->option('usage_mandate', 'Hush!'); - if ( $separate || $cust_bill_pkg->hidden || $usage_mandate ) { + my $temp_pkg = new FS::cust_pkg { pkgpart => $real_pkgpart }; + my $usage_mandate = $temp_pkg->part_pkg->option('usage_mandate', 'Hush!'); + my $section = $temp_pkg->part_pkg->categoryname; + if ( $separate || $section || $usage_mandate ) { - my $temp_pkg = new FS::cust_pkg { pkgpart => $real_pkgpart }; - my %hash = $cust_bill_pkg->hidden # maybe for all bill linked? - ? ( 'section' => $temp_pkg->part_pkg->categoryname ) - : (); + my %hash = ( 'section' => $section ); - my $section = $cust_pkg->part_pkg->option('usage_section', 'Hush!'); - my $summary = $cust_pkg->part_pkg->option('summarize_usage', 'Hush!'); + $section = $temp_pkg->part_pkg->option('usage_section', 'Hush!'); + my $summary = $temp_pkg->part_pkg->option('summarize_usage', 'Hush!'); if ( $separate ) { push @display, new FS::cust_bill_pkg_display { type => 'S', %hash }; push @display, new FS::cust_bill_pkg_display { type => 'R', %hash }; @@ -3693,8 +3735,10 @@ sub _handle_taxes { $hash{post_total} = 'Y'; } - $hash{section} = $section if ($separate || $usage_mandate); - push @display, new FS::cust_bill_pkg_display { type => 'U', %hash }; + if ($separate || $usage_mandate) { + $hash{section} = $section if ($separate || $usage_mandate); + push @display, new FS::cust_bill_pkg_display { type => 'U', %hash }; + } } $cust_bill_pkg->set('display', \@display); -- cgit v1.2.1 From 64f9efad9014caf9f0395f60afd6a7a5fedd0490 Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 18 Aug 2010 03:43:42 +0000 Subject: handle the usage_class-less details in svc_phone sections --- FS/FS/cust_bill.pm | 1 + 1 file changed, 1 insertion(+) (limited to 'FS') diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index b73e360af..8468f3abf 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -3750,6 +3750,7 @@ sub _items_svc_phone_sections { my %lines = (); my %usage_class = map { $_->classnum => $_ } qsearch( 'usage_class', {} ); + $usage_class{''} ||= new FS::usage_class { 'classname' => '', 'weight' => 0 }; foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) { next unless $cust_bill_pkg->pkgnum > 0; -- cgit v1.2.1 From 2e45f85a3b2544f89fb149a77b3a20df3381d48f Mon Sep 17 00:00:00 2001 From: jeff Date: Wed, 18 Aug 2010 18:59:30 +0000 Subject: still don't want invoices without line items --- FS/FS/cust_main.pm | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 168403482..ac5e45614 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -3020,7 +3020,7 @@ sub bill { foreach my $pass (@passes) { # keys %cust_bill_pkg ) { - my @cust_bill_pkg = @{ $cust_bill_pkg{$pass} }; + my @cust_bill_pkg = _omit_zero_value_bundles(@{ $cust_bill_pkg{$pass} }); next unless @cust_bill_pkg; #don't create an invoice w/o line items @@ -3066,6 +3066,9 @@ sub bill { } } + # it's silly to have a zero value postal_pkg, but.... + @cust_bill_pkg = _omit_zero_value_bundles(@cust_bill_pkg); + } } @@ -3140,24 +3143,12 @@ sub bill { return "can't create invoice for customer #". $self->custnum. ": $error"; } - my @cust_bill_pkg_bundle = (); foreach my $cust_bill_pkg ( @cust_bill_pkg ) { $cust_bill_pkg->invnum($cust_bill->invnum); - if (scalar(@cust_bill_pkg_bundle) && !$cust_bill_pkg->pkgpart_override) { - $error = $self->_insert_cust_bill_pkg_bundle( @cust_bill_pkg_bundle ); - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return $error; - } - @cust_bill_pkg_bundle = (); - } - push @cust_bill_pkg_bundle, $cust_bill_pkg; - } - if (scalar(@cust_bill_pkg_bundle)) { - $error = $self->_insert_cust_bill_pkg_bundle( @cust_bill_pkg_bundle ); + my $error = $cust_bill_pkg->insert; if ( $error ) { $dbh->rollback if $oldAutoCommit; - return $error; + return "can't create invoice line item: $error"; } } @@ -3177,19 +3168,25 @@ sub bill { ''; #no error } -#insert line items while discarding bundled packages of 0 value -sub _insert_cust_bill_pkg_bundle { - my $self = shift; - my @cust_bill_pkg = @_; +#discard bundled packages of 0 value +sub _omit_zero_value_bundles { + my @cust_bill_pkg = (); + my @cust_bill_pkg_bundle = (); my $sum = 0; - $sum += $_->setup + $_->recur foreach @cust_bill_pkg; - return '' unless $sum > 0; - foreach my $cust_bill_pkg ( @cust_bill_pkg ) { - my $error = $cust_bill_pkg->insert; - return "can't create invoice line item: $error" if $error; + foreach my $cust_bill_pkg ( @_ ) { + if (scalar(@cust_bill_pkg_bundle) && !$cust_bill_pkg->pkgpart_override) { + push @cust_bill_pkg, @cust_bill_pkg_bundle if $sum > 0; + @cust_bill_pkg_bundle = (); + $sum = 0; + } + $sum += $cust_bill_pkg->setup + $cust_bill_pkg->recur; + push @cust_bill_pkg_bundle, $cust_bill_pkg; } + push @cust_bill_pkg, @cust_bill_pkg_bundle if $sum > 0; + + (@cust_bill_pkg); } -- cgit v1.2.1 From 3f4c9d34c4a031ca061f2f53f6ec4d893b5c3d40 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 19 Aug 2010 10:15:21 +0000 Subject: fix fixed-amount discounts against packages with pkg add-ons, RT#9669 --- FS/FS/cust_main.pm | 1 + FS/FS/part_pkg/flat.pm | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index ac5e45614..c0fb5d297 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -3473,6 +3473,7 @@ sub _make_lines { my %param = ( 'precommit_hooks' => $precommit_hooks, 'increment_next_bill' => $increment_next_bill, 'discounts' => \@discounts, + 'real_pkgpart' => $real_pkgpart, ); my $method = $options{cancel} ? 'calc_cancel' : 'calc_recur'; diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index cc2310503..648a83ddc 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -154,7 +154,7 @@ sub calc_setup { sub unit_setup { my($self, $cust_pkg, $sdate, $details ) = @_; - $self->option('setup_fee'); + $self->option('setup_fee') || 0; } sub calc_recur { @@ -185,7 +185,8 @@ sub calc_discount { my $discount = $cust_pkg_discount->discount; #UI enforces one or the other (for now? probably for good) my $amount = 0; - $amount += $discount->amount; + $amount += $discount->amount + if $cust_pkg->pkgpart == $param->{real_pkgpart}; $amount += sprintf('%.2f', $discount->percent * $br / 100 ); my $chg_months = $param->{'months'} || $cust_pkg->part_pkg->freq; -- cgit v1.2.1 From bb617fa9977d6886ac930d7a97e9221b33899474 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 19 Aug 2010 11:55:33 +0000 Subject: fix batching protection against transactions settled in the meantime, RT#7905 --- FS/FS/pay_batch.pm | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) (limited to 'FS') diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm index 3abb06d2a..0b8c9f79b 100644 --- a/FS/FS/pay_batch.pm +++ b/FS/FS/pay_batch.pm @@ -478,29 +478,34 @@ sub export_batch { $batch .= $h . "\n"; } foreach my $cust_pay_batch (@cust_pay_batch) { - if($first_download) { + + if ($first_download) { my $balance = $cust_pay_batch->cust_main->balance; - my $error = ''; - if($balance <= 0) { # then don't charge this customer - $error = $cust_pay_batch->delete; - undef $cust_pay_batch; - } - elsif($balance < $cust_pay_batch->amount) { # then reduce the charge to the remaining balance + if ($balance <= 0) { # then don't charge this customer + my $error = $cust_pay_batch->delete; + if ( $error ) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + die $error; + } + next; + } elsif ($balance < $cust_pay_batch->amount) { + # reduce the charge to the remaining balance $cust_pay_batch->amount($balance); - $error = $cust_pay_batch->replace; + my $error = $cust_pay_batch->replace; + if ( $error ) { + $dbh->rollback or die $dbh->errstr if $oldAutoCommit; + die $error; + } } # else $balance >= $cust_pay_batch->amount - if($error) { - $dbh->rollback or die $dbh->errstr if $oldAutoCommit; - die $error; - } - } - if($cust_pay_batch) { # that is, it wasn't deleted - $batchcount++; - $batchtotal += $cust_pay_batch->amount; - $batch .= &{$info->{'row'}}($cust_pay_batch, $self, $batchcount, $batchtotal) . "\n"; } + + $batchcount++; + $batchtotal += $cust_pay_batch->amount; + $batch .= &{$info->{'row'}}($cust_pay_batch, $self, $batchcount, $batchtotal) . "\n"; + } + my $f = $info->{'footer'}; if(ref($f) eq 'CODE') { $batch .= &$f($self, $batchcount, $batchtotal) . "\n"; -- cgit v1.2.1 From 4117c20f85ce085d7dd42b8970ce9c65b95d7e27 Mon Sep 17 00:00:00 2001 From: mark Date: Thu, 19 Aug 2010 19:11:45 +0000 Subject: part_pkg prorate mixin and sync_bill_date option, RT#9554 --- FS/FS/Conf.pm | 7 +++ FS/FS/part_pkg/flat.pm | 23 +++++++--- FS/FS/part_pkg/prorate.pm | 30 +------------ FS/FS/part_pkg/prorate_Mixin.pm | 96 +++++++++++++++++++++++++++++++++++++++++ FS/FS/part_pkg/recur_Common.pm | 23 +++++----- 5 files changed, 132 insertions(+), 47 deletions(-) create mode 100644 FS/FS/part_pkg/prorate_Mixin.pm (limited to 'FS') diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index fe010f77b..ce2c01d46 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -3293,6 +3293,13 @@ and customer address. Include units.', 'type' => 'text', }, + { + 'key' => 'order_pkg-no_start_date', + 'section' => 'UI', + 'description' => 'Don\'t set a default start date for new packages.', + 'type' => 'checkbox', + }, + { 'key' => 'mcp_svcpart', 'section' => '', diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index 648a83ddc..a04f44ae4 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -13,7 +13,7 @@ use FS::Conf; use FS::part_pkg; use FS::cust_bill_pkg_discount; -@ISA = qw(FS::part_pkg); +@ISA = qw(FS::part_pkg FS::part_pkg::prorate_Mixin); tie my %temporalities, 'Tie::IxHash', 'upcoming' => "Upcoming (future)", @@ -119,6 +119,10 @@ tie my %temporalities, 'Tie::IxHash', 'start_1st' => { 'name' => 'Auto-add a start date to the 1st, ignoring the current month.', 'type' => 'checkbox', }, + 'sync_bill_date' => { 'name' => 'Prorate first month to synchronize '. + 'with the customer\'s other packages', + 'type' => 'checkbox', + }, %usage_fields, %usage_recharge_fields, @@ -129,7 +133,7 @@ tie my %temporalities, 'Tie::IxHash', }, 'fieldorder' => [ qw( setup_fee recur_fee recur_temporality unused_credit - expire_months start_1st + expire_months start_1st sync_bill_date ), @usage_fieldorder, @usage_recharge_fieldorder, qw( externalid ), @@ -158,7 +162,8 @@ sub unit_setup { } sub calc_recur { - my($self, $cust_pkg, $sdate, $details, $param ) = @_; + my $self = shift; + my($cust_pkg, $sdate, $details, $param ) = @_; #my $last_bill = $cust_pkg->last_bill; my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup @@ -166,11 +171,15 @@ sub calc_recur { return 0 if $self->option('recur_temporality', 1) eq 'preceding' && $last_bill == 0; - my $br = $self->base_recur($cust_pkg); - - my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param); + if( $self->option('sync_bill_date') ) { + return $self->calc_prorate(@_); + } + else { + my $charge = $self->base_recur($cust_pkg); + my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param); - sprintf('%.2f', $br - $discount); + return sprintf('%.2f', $charge - $discount); + } } sub calc_discount { diff --git a/FS/FS/part_pkg/prorate.pm b/FS/FS/part_pkg/prorate.pm index 09561cf51..918b910be 100644 --- a/FS/FS/part_pkg/prorate.pm +++ b/FS/FS/part_pkg/prorate.pm @@ -95,34 +95,8 @@ use FS::part_pkg::flat; ); sub calc_recur { - my($self, $cust_pkg, $sdate, $details, $param ) = @_; - 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; - 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; - - my $months = ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) ); - - $param->{'months'} = $months; - my $discount = $self->calc_discount( $cust_pkg, $sdate, $details, $param); - - sprintf('%.2f', $permonth * $months - $discount); + my $self = shift; + $self->calc_prorate(@_); } 1; diff --git a/FS/FS/part_pkg/prorate_Mixin.pm b/FS/FS/part_pkg/prorate_Mixin.pm new file mode 100644 index 000000000..a60858b37 --- /dev/null +++ b/FS/FS/part_pkg/prorate_Mixin.pm @@ -0,0 +1,96 @@ +package FS::part_pkg::prorate_Mixin; + +use strict; +use vars qw(@ISA %info); +use Time::Local qw(timelocal); + +@ISA = qw(FS::part_pkg); +%info = ( 'disabled' => 1 ); + +=head1 NAME + +FS::part_pkg::prorate_Mixin - Mixin class for part_pkg:: classes that +need to prorate partial months + +=head1 SYNOPSIS + +package FS::part_pkg::...; +use base qw( FS::part_pkg::prorate_Mixin ); + +sub calc_recur { + ... + if( conditions that trigger prorate ) { + # sets $$sdate and $param->{'months'}, returns the prorated charge + $charges = $self->calc_prorate($cust_pkg, $sdate, $param, $cutoff_day); + } + ... +} + +=head METHODS + +=item calc_prorate + +Takes all the arguments of calc_recur, and calculates a prorated charge +in one of two ways: + +- If 'sync_bill_date' is set: Charge for a number of days to synchronize + this package to the customer's next bill date. If this is their only + package (or they're already synchronized), that will take them through + one billing cycle. +- If 'cutoff_day' is set: Prorate this package so that its next bill date + falls on that day of the month. + +=cut + +sub calc_prorate { + my $self = shift; + my ($cust_pkg, $sdate, $details, $param) = @_; + + my $charge = $self->option('recur_fee') || 0; + my $cutoff_day; + if( $self->option('sync_bill_date') ) { + my $next_bill = $cust_pkg->cust_main->next_bill_date; + if( defined($next_bill) and $next_bill != $$sdate ) { + $cutoff_day = (localtime($next_bill))[3]; + } + else { + # don't prorate, assume a full month + $param->{'months'} = $self->freq; + } + } + else { # no sync, use cutoff_day or day 1 + $cutoff_day = $self->option('cutoff_day') || 1; + } + + if($cutoff_day) { + # only works for freq >= 1 month; probably can't be fixed + my $mnow = $$sdate; + my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($mnow))[0..5]; + 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); + $mstart = + timelocal(0,0,0,$cutoff_day,$mon == 0 ? 11 : $mon - 1,$year-($mon==11)); + } + + $$sdate = $mstart; + + my $permonth = $self->option('recur_fee', 1) / $self->freq; + my $months = ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) ); + + $param->{'months'} = $months; + $charge = sprintf('%.2f', $permonth * $months); + } + my $discount = $self->calc_discount(@_); + return ($charge - $discount); +} + +1; diff --git a/FS/FS/part_pkg/recur_Common.pm b/FS/FS/part_pkg/recur_Common.pm index 8ed9eb6af..9a6774579 100644 --- a/FS/FS/part_pkg/recur_Common.pm +++ b/FS/FS/part_pkg/recur_Common.pm @@ -4,9 +4,9 @@ use strict; use vars qw( @ISA %info %recur_method ); use Tie::IxHash; use Time::Local; -use FS::part_pkg::prorate; +use FS::part_pkg::prorate_Mixin; -@ISA = qw(FS::part_pkg::prorate); +@ISA = qw(FS::part_pkg::prorate_Mixin); %info = ( 'disabled' => 1 ); #recur_Common not a usable price plan directly @@ -26,11 +26,12 @@ sub calc_recur_Common { my $recur_method = $self->option('recur_method', 1) || 'anniversary'; - if ( $recur_method eq 'prorate' ) { - - $charges = $self->SUPER::calc_recur(@_); - - } else { + if ( $recur_method eq 'prorate' + or ($recur_method eq 'anniversary' and $self->option('sync_bill_date')) + ) { + $charges = $self->calc_prorate(@_); + } + else { $charges = $self->option('recur_fee'); @@ -47,14 +48,12 @@ sub calc_recur_Common { $$sdate = timelocal(0, 0, 0, $cutoff_day, $mon, $year); }#$recur_method eq 'subscription' + $charges -= $self->calc_discount( $cust_pkg, $sdate, $details, $param ); - $charges -= $self->calc_discount( $cust_pkg, $sdate, $details, $param ); - - }#$recur_method eq 'prorate' - + }#$recur_method eq 'prorate' or ... }#increment_next_bill - $charges; + return $charges; } -- cgit v1.2.1 From becc3877b546ffb36d4ce674848d9da51c44153f Mon Sep 17 00:00:00 2001 From: mark Date: Sat, 21 Aug 2010 00:17:26 +0000 Subject: new event conditions, RT#8896 --- FS/FS/part_event/Condition/once_every.pm | 46 +++++++++++++++++ FS/FS/part_event/Condition/once_perinv.pm | 57 ++++++++++++++++++++++ FS/FS/part_event/Condition/pkg_next_bill_within.pm | 51 +++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 FS/FS/part_event/Condition/once_every.pm create mode 100644 FS/FS/part_event/Condition/once_perinv.pm create mode 100644 FS/FS/part_event/Condition/pkg_next_bill_within.pm (limited to 'FS') diff --git a/FS/FS/part_event/Condition/once_every.pm b/FS/FS/part_event/Condition/once_every.pm new file mode 100644 index 000000000..a0d9d6802 --- /dev/null +++ b/FS/FS/part_event/Condition/once_every.pm @@ -0,0 +1,46 @@ +package FS::part_event::Condition::once_every; + +use strict; +use FS::Record qw( qsearch ); +use FS::part_event; +use FS::cust_event; + +use base qw( FS::part_event::Condition ); + +sub description { "Don't run this event more than once in interval"; } + +# Runs the event at most "once every X". + +sub option_fields { + ( + 'run_delay' => { label=>'Interval', type=>'freq', value=>'1m', }, + ); +} + +sub condition { + my($self, $object, %opt) = @_; + + my $obj_pkey = $object->primary_key; + my $tablenum = $object->$obj_pkey(); + + my $max_date = $self->option_age_from('run_delay',$opt{'time'}); + + my @existing = qsearch( { + 'table' => 'cust_event', + 'hashref' => { + 'eventpart' => $self->eventpart, + 'tablenum' => $tablenum, + 'status' => { op=>'!=', value=>'failed' }, + '_date' => { op=>'>=', value=>$max_date }, + }, + 'extra_sql' => ( $opt{'cust_event'}->eventnum =~ /^(\d+)$/ + ? " AND eventnum != $1 " + : '' + ), + } ); + + ! scalar(@existing); + +} + +1; diff --git a/FS/FS/part_event/Condition/once_perinv.pm b/FS/FS/part_event/Condition/once_perinv.pm new file mode 100644 index 000000000..f85a05665 --- /dev/null +++ b/FS/FS/part_event/Condition/once_perinv.pm @@ -0,0 +1,57 @@ +package FS::part_event::Condition::once_perinv; + +use strict; +use FS::Record qw( qsearch ); +use FS::part_event; +use FS::cust_event; + +use base qw( FS::part_event::Condition ); + +sub description { "Run only once for each time the package has been billed"; } + +# Run the event, at most, a number of times equal to the number of +# distinct invoices that contain line items from this package. + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 0, + 'cust_pkg' => 1, + }; +} + +sub condition { + my($self, $cust_pkg, %opt) = @_; + + my %invnum; + $invnum{$_->invnum} = 1 + foreach ( qsearch('cust_bill_pkg', { 'pkgnum' => $cust_pkg->pkgnum }) ); + my @events = qsearch( { + 'table' => 'cust_event', + 'hashref' => { 'eventpart' => $self->eventpart, + 'status' => { op=>'!=', value=>'failed' }, + 'tablenum' => $cust_pkg->pkgnum, + }, + 'extra_sql' => ( $opt{'cust_event'}->eventnum =~ /^(\d+)$/ + ? " AND eventnum != $1 " : '' ), + } ); + scalar(@events) < scalar(keys %invnum); +} + +sub condition_sql { + my( $self, $table ) = @_; + + "( + ( SELECT COUNT(distinct(invnum)) + FROM cust_bill_pkg + WHERE cust_bill_pkg.pkgnum = cust_pkg.pkgnum ) + > + ( SELECT COUNT(*) + FROM cust_event + WHERE cust_event.eventpart = part_event.eventpart + AND cust_event.tablenum = cust_pkg.pkgnum + AND status != 'failed' ) + )" + +} + +1; diff --git a/FS/FS/part_event/Condition/pkg_next_bill_within.pm b/FS/FS/part_event/Condition/pkg_next_bill_within.pm new file mode 100644 index 000000000..dc16ce843 --- /dev/null +++ b/FS/FS/part_event/Condition/pkg_next_bill_within.pm @@ -0,0 +1,51 @@ +package FS::part_event::Condition::pkg_next_bill_within; + +use strict; +use base qw( FS::part_event::Condition ); +use FS::Record qw( qsearch ); + +sub description { + 'Next bill date within interval'; +} + +# Run the event when the next bill date is within X days. +# To clarify, that's within X days _after_ the current date, +# not before. +# Combine this with a "once_every" condition so that the event +# won't repeat every day until the bill date. + +sub eventtable_hashref { + { 'cust_main' => 0, + 'cust_bill' => 0, + 'cust_pkg' => 1, + }; +} + +sub option_fields { + ( + 'within' => { 'label' => 'Bill date within', + 'type' => 'freq', + }, + # possibly "field" to allow date fields besides 'bill'? + ); +} + +sub condition { + my( $self, $cust_pkg, %opt ) = @_; + + my $pkg_date = $cust_pkg->get('bill') or return 0; + $pkg_date = $self->option_age_from('within', $pkg_date ); + + $opt{'time'} >= $pkg_date; + +} + +#XXX write me for efficiency +sub condition_sql { + my ($self, $table, %opt) = @_; + $opt{'time'}.' >= '. + $self->condition_sql_option_age_from('within', 'cust_pkg.bill') +} + +1; + -- cgit v1.2.1 From 57999adbee73616a2c18b82df74d476f5005dcd8 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 23 Aug 2010 16:35:33 +0000 Subject: work around ffiec bug and add year 2010 --- FS/FS/Conf.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index ce2c01d46..a4b842322 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -3217,7 +3217,7 @@ and customer address. Include units.', 'section' => 'UI', 'description' => 'The year to use in census tract lookups', 'type' => 'select', - 'select_enum' => [ qw( 2009 2008 2007 2006 ) ], + 'select_enum' => [ qw( 2010 2009 2008 ) ], }, { -- cgit v1.2.1 From c8cd96b69c9c1ede44c06c04f2703079d1afdf2b Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 23 Aug 2010 16:47:00 +0000 Subject: create a default finance section and have hidden sectionless line items remain sectionless --- FS/FS/cust_bill.pm | 4 +++- FS/FS/cust_bill_pkg_display.pm | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 8468f3abf..e4215bf2b 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -2386,7 +2386,8 @@ sub print_generic { qsearchs('pkg_class', { classnum => $conf->config('finance_pkgclass') }); $invoice_data{finance_section} = $pkg_class->categoryname; } - $invoice_data{finance_amount} = '0.00'; + $invoice_data{finance_amount} = '0.00'; + $invoice_data{finance_section} ||= 'Finance Charges'; #avoid config confusion my $countrydefault = $conf->config('countrydefault') || 'US'; my $prefix = $cust_main->has_ship_address ? 'ship_' : ''; @@ -3359,6 +3360,7 @@ sub _items_sections { if ( $summarypage ) { @sections = grep { exists($subtotal{$_}) || ! _pkg_category($_)->disabled } map { $_->categoryname } qsearch('pkg_category', {}); + push @sections, '' if exists($subtotal{''}); } else { @sections = keys %subtotal; } diff --git a/FS/FS/cust_bill_pkg_display.pm b/FS/FS/cust_bill_pkg_display.pm index e9da18dec..a864ec114 100644 --- a/FS/FS/cust_bill_pkg_display.pm +++ b/FS/FS/cust_bill_pkg_display.pm @@ -55,7 +55,7 @@ sub section { my $section = $self->getfield('section'); unless ($section) { my $cust_bill_pkg = $self->cust_bill_pkg; - if ( $cust_bill_pkg->pkgnum > 0 ) { + if ( $cust_bill_pkg->pkgnum > 0 && !$cust_bill_pkg->hidden ) { my $part_pkg = $cust_bill_pkg->part_pkg; $section = $part_pkg->categoryname if $part_pkg; } -- cgit v1.2.1 From 0a9b7ad6786deed6f8b8c770d128182c7b9b7097 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 23 Aug 2010 19:51:25 +0000 Subject: allow importation of customers with no tax rates --- FS/FS/cust_main.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index c0fb5d297..520372360 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -1790,7 +1790,8 @@ sub check { # bad idea to disable, causes billing to fail because of no tax rates later -# unless ( $import ) { +# except we don't fail any more + unless ( $import ) { unless ( qsearch('cust_main_county', { 'country' => $self->country, 'state' => '', @@ -1803,7 +1804,7 @@ sub check { 'country' => $self->country, } ); } -# } + } $error = $self->ut_phonen('daytime', $self->country) -- cgit v1.2.1 From 4289a88d7ac580599e4af7242f6af90aa2653cb8 Mon Sep 17 00:00:00 2001 From: jeff Date: Mon, 23 Aug 2010 20:02:41 +0000 Subject: support importing charges by agent_custid --- FS/FS/cust_main.pm | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 520372360..355687820 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -8865,7 +8865,17 @@ sub batch_charge { my $param = shift; #warn join('-',keys %$param); my $fh = $param->{filehandle}; - my @fields = @{$param->{fields}}; + my $agentnum = $param->{agentnum}; + my $format = $param->{format}; + + my $extra_sql = ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql; + + my @fields; + if ( $format eq 'simple' ) { + @fields = qw( custnum agent_custid amount pkg ); + } else { + die "unknown format $format"; + } eval "use Text::CSV_XS;"; die $@ if $@; @@ -8905,10 +8915,32 @@ sub batch_charge { $row{$field} = shift @columns; } - my $cust_main = qsearchs('cust_main', { 'custnum' => $row{'custnum'} } ); + if ( $row{custnum} && $row{agent_custid} ) { + dbh->rollback if $oldAutoCommit; + return "can't specify custnum with agent_custid $row{agent_custid}"; + } + + my %hash = (); + if ( $row{agent_custid} && $agentnum ) { + %hash = ( 'agent_custid' => $row{agent_custid}, + 'agentnum' => $agentnum, + ); + } + + if ( $row{custnum} ) { + %hash = ( 'custnum' => $row{custnum} ); + } + + unless ( scalar(keys %hash) ) { + $dbh->rollback if $oldAutoCommit; + return "can't find customer without custnum or agent_custid and agentnum"; + } + + my $cust_main = qsearchs('cust_main', { %hash } ); unless ( $cust_main ) { $dbh->rollback if $oldAutoCommit; - return "unknown custnum $row{'custnum'}"; + my $custnum = $row{custnum} || $row{agent_custid}; + return "unknown custnum $custnum"; } if ( $row{'amount'} > 0 ) { -- cgit v1.2.1 From 1f979608d9a16baa1c9d91e203d1b4d86b3f1276 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 24 Aug 2010 00:55:48 +0000 Subject: fix payment receipts when payment_receipt_msgnum is unconfigured --- FS/FS/cust_pay.pm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index eee263a1d..accc8260f 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -458,12 +458,15 @@ sub send_receipt { my $error = ''; - if( $conf->exists('payment_receipt_msgnum') ) { + if ( $conf->exists('payment_receipt_msgnum') + && $conf->config('payment_receipt_msgnum') + ) + { my $msg_template = FS::msg_template->by_key($conf->config('payment_receipt_msgnum')); $error = $msg_template->send('cust_main'=> $cust_main, 'object'=> $self); - } - elsif ( $conf->exists('payment_receipt_email') ) { + + } elsif ( $conf->exists('payment_receipt_email') ) { my $receipt_template = new Text::Template ( TYPE => 'ARRAY', SOURCE => [ map "$_\n", $conf->config('payment_receipt_email') ], @@ -506,8 +509,7 @@ sub send_receipt { 'body' => [ $receipt_template->fill_in( HASH => \%fill_in ) ], ); - } - else { # no payment_receipt_msgnum or payment_receipt_email + } else { # no payment_receipt_msgnum or payment_receipt_email my $queue = new FS::queue { 'paynum' => $self->paynum, -- cgit v1.2.1 From b48548f7e5fb15aacc0040ae0557d05b87fecfb2 Mon Sep 17 00:00:00 2001 From: mark Date: Tue, 24 Aug 2010 01:59:31 +0000 Subject: delete CVV when processing batch results, RT#9652 --- FS/FS/cust_main.pm | 3 +-- FS/FS/pay_batch.pm | 10 +++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 355687820..2a31a5b44 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -4926,8 +4926,7 @@ sub realtime_bop { #false laziness w/misc/process/payment.cgi - check both to make sure working # correctly - if ( defined $self->dbdef_table->column('paycvv') - && length($self->paycvv) + if ( length($self->paycvv) && ! grep { $_ eq cardtype($options{payinfo}) } $conf->config('cvv-save') ) { my $error = $self->remove_cvv; diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm index 0b8c9f79b..9a0b235a9 100644 --- a/FS/FS/pay_batch.pm +++ b/FS/FS/pay_batch.pm @@ -356,6 +356,10 @@ sub import_results { return "error updating status of paybatchnum $hash{'paybatchnum'}: $error\n"; } + # purge CVV when the batch is processed + $new_cust_pay_batch->cust_main->remove_cvv + if ( $payby eq 'CARD' or $payby eq 'DCRD' ); + if ( $new_cust_pay_batch->status =~ /Approved/i ) { my $cust_pay = new FS::cust_pay ( { @@ -402,11 +406,11 @@ sub import_results { return $error; } - } + } # foreach $cust_event - } + } # if(status eq 'Approved') - } + } # foreach(@all_values) $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; -- cgit v1.2.1 From 31d70c3482ad3aecc79ac4d69c15c35713484359 Mon Sep 17 00:00:00 2001 From: mark Date: Tue, 24 Aug 2010 02:17:22 +0000 Subject: premature commit --- FS/FS/pay_batch.pm | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'FS') diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm index 9a0b235a9..0b8c9f79b 100644 --- a/FS/FS/pay_batch.pm +++ b/FS/FS/pay_batch.pm @@ -356,10 +356,6 @@ sub import_results { return "error updating status of paybatchnum $hash{'paybatchnum'}: $error\n"; } - # purge CVV when the batch is processed - $new_cust_pay_batch->cust_main->remove_cvv - if ( $payby eq 'CARD' or $payby eq 'DCRD' ); - if ( $new_cust_pay_batch->status =~ /Approved/i ) { my $cust_pay = new FS::cust_pay ( { @@ -406,11 +402,11 @@ sub import_results { return $error; } - } # foreach $cust_event + } - } # if(status eq 'Approved') + } - } # foreach(@all_values) + } $dbh->commit or die $dbh->errstr if $oldAutoCommit; ''; -- cgit v1.2.1 From f0bb712b44ff110ce3441a32a5226837d34738fe Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 24 Aug 2010 02:27:46 +0000 Subject: fix otaker still getting assigned and usernum missing after otaker->usernum upgrade, causes credit report to barf, RT#9712 --- FS/FS/banned_pay.pm | 5 +++-- FS/FS/cust_credit.pm | 3 ++- FS/FS/cust_main.pm | 3 ++- FS/FS/cust_pay.pm | 3 ++- FS/FS/cust_pay_void.pm | 3 ++- FS/FS/cust_pkg.pm | 3 ++- FS/FS/cust_refund.pm | 3 ++- 7 files changed, 15 insertions(+), 8 deletions(-) (limited to 'FS') diff --git a/FS/FS/banned_pay.pm b/FS/FS/banned_pay.pm index a86202824..337965324 100644 --- a/FS/FS/banned_pay.pm +++ b/FS/FS/banned_pay.pm @@ -4,6 +4,7 @@ use strict; use base qw( FS::otaker_Mixin FS::Record ); use FS::Record qw( qsearch qsearchs ); use FS::UID qw( getotaker ); +use FS::CurrentUser; =head1 NAME @@ -41,7 +42,7 @@ supported: =item _date - specified as a UNIX timestamp; see L. Also see L and L for conversion functions. -=item otaker - order taker (assigned automatically, see L) +=item usernum - order taker (assigned automatically, see L) =item reason - reason (text) @@ -115,7 +116,7 @@ sub check { $self->_date(time) unless $self->_date; - $self->otaker(getotaker) unless $self->otaker; + $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum; $self->SUPER::check; } diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm index 1ddcb8b9f..1ebff3e87 100644 --- a/FS/FS/cust_credit.pm +++ b/FS/FS/cust_credit.pm @@ -7,6 +7,7 @@ use Date::Format; use FS::UID qw( dbh getotaker ); use FS::Misc qw(send_email); use FS::Record qw( qsearch qsearchs dbdef ); +use FS::CurrentUser; use FS::cust_main; use FS::cust_pkg; use FS::cust_refund; @@ -290,7 +291,7 @@ methods. sub check { my $self = shift; - $self->otaker(getotaker) unless ($self->otaker); + $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum; my $error = $self->ut_numbern('crednum') diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 2a31a5b44..f4b9c5993 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -31,6 +31,7 @@ use FS::UID qw( getotaker dbh driver_name ); use FS::Record qw( qsearchs qsearch dbdef regexp_sql ); use FS::Misc qw( generate_email send_email generate_ps do_print ); use FS::Msgcat qw(gettext); +use FS::CurrentUser; use FS::payby; use FS::cust_pkg; use FS::cust_svc; @@ -2073,7 +2074,7 @@ sub check { $self->$flag($1); } - $self->otaker(getotaker) unless $self->otaker; + $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum; warn "$me check AFTER: \n". $self->_dump if $DEBUG > 2; diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index accc8260f..388aef79b 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -12,6 +12,7 @@ use Text::Template; use FS::UID qw( getotaker ); use FS::Misc qw( send_email ); use FS::Record qw( dbh qsearch qsearchs ); +use FS::CurrentUser; use FS::payby; use FS::cust_main_Mixin; use FS::payinfo_transaction_Mixin; @@ -374,7 +375,7 @@ returns the error, otherwise returns false. Called by the insert method. sub check { my $self = shift; - $self->otaker(getotaker) unless ($self->otaker); + $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum; my $error = $self->ut_numbern('paynum') diff --git a/FS/FS/cust_pay_void.pm b/FS/FS/cust_pay_void.pm index e18a4f686..9293ef6d7 100644 --- a/FS/FS/cust_pay_void.pm +++ b/FS/FS/cust_pay_void.pm @@ -6,6 +6,7 @@ use vars qw( @encrypted_fields $otaker_upgrade_kludge ); use Business::CreditCard; use FS::UID qw(getotaker); use FS::Record qw(qsearchs dbh fields); # qsearch ); +use FS::CurrentUser; use FS::cust_pay; #use FS::cust_bill; #use FS::cust_bill_pay; @@ -221,7 +222,7 @@ sub check { return $error if $error; } - $self->otaker(getotaker) unless $self->otaker; + $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum; $self->SUPER::check; } diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index c3ee4e40a..f367fd34e 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -13,6 +13,7 @@ use MIME::Entity; use FS::UID qw( getotaker dbh ); use FS::Misc qw( send_email ); use FS::Record qw( qsearch qsearchs ); +use FS::CurrentUser; use FS::cust_svc; use FS::part_pkg; use FS::cust_main; @@ -563,7 +564,7 @@ sub check { } - $self->otaker(getotaker) unless $self->otaker; + $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum; if ( $self->dbdef_table->column('manual_flag') ) { $self->manual_flag('') if $self->manual_flag eq ' '; diff --git a/FS/FS/cust_refund.pm b/FS/FS/cust_refund.pm index 39603701f..4086f0f95 100644 --- a/FS/FS/cust_refund.pm +++ b/FS/FS/cust_refund.pm @@ -7,6 +7,7 @@ use vars qw( @encrypted_fields ); use Business::CreditCard; use FS::UID qw(getotaker); use FS::Record qw( qsearch qsearchs dbh ); +use FS::CurrentUser; use FS::cust_credit; use FS::cust_credit_refund; use FS::cust_pay_refund; @@ -256,7 +257,7 @@ returns the error, otherwise returns false. Called by the insert method. sub check { my $self = shift; - $self->otaker(getotaker) unless $self->otaker; + $self->usernum($FS::CurrentUser::CurrentUser->usernum) unless $self->usernum; my $error = $self->ut_numbern('refundnum') -- cgit v1.2.1 From 8ad28f5361b81fe97654f4b8df8539ff1d2be189 Mon Sep 17 00:00:00 2001 From: mark Date: Tue, 24 Aug 2010 03:03:09 +0000 Subject: delete CVV when processing batch results, RT#9652 --- FS/FS/pay_batch.pm | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'FS') diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm index 0b8c9f79b..2561d3dc4 100644 --- a/FS/FS/pay_batch.pm +++ b/FS/FS/pay_batch.pm @@ -7,6 +7,7 @@ use Text::CSV_XS; use FS::Record qw( dbh qsearch qsearchs ); use FS::cust_pay; use FS::Conf; +use Business::CreditCard qw(cardtype); @ISA = qw(FS::Record); @@ -198,6 +199,8 @@ sub import_results { my $job = $param->{'job'}; $job->update_statustext(0) if $job; + my $conf = new FS::Conf; + my $filetype = $info->{'filetype'}; # CSV or fixed my @fields = @{ $info->{'fields'}}; my $formatre = $info->{'formatre'}; # for fixed @@ -356,6 +359,15 @@ sub import_results { return "error updating status of paybatchnum $hash{'paybatchnum'}: $error\n"; } + # purge CVV when the batch is processed + if ( $payby =~ /^(CARD|DCRD)$/ ) { + my $payinfo = $hash{'payinfo'} || $cust_pay_batch->payinfo; + if ( ! grep { $_ eq cardtype($payinfo) } + $conf->config('cvv-save') ) { + $new_cust_pay_batch->cust_main->remove_cvv; + } + } + if ( $new_cust_pay_batch->status =~ /Approved/i ) { my $cust_pay = new FS::cust_pay ( { -- cgit v1.2.1 From 798c63aa6265165ea56c3a7543e3e477e6dc12d4 Mon Sep 17 00:00:00 2001 From: mark Date: Tue, 24 Aug 2010 03:06:52 +0000 Subject: script to remove payment info from canceled customers, RT#9652 --- FS/bin/freeside-wipe-cvv | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100755 FS/bin/freeside-wipe-cvv (limited to 'FS') diff --git a/FS/bin/freeside-wipe-cvv b/FS/bin/freeside-wipe-cvv new file mode 100755 index 000000000..611e841ae --- /dev/null +++ b/FS/bin/freeside-wipe-cvv @@ -0,0 +1,87 @@ +#!/usr/bin/perl -w + +use strict; +use Getopt::Std; +use FS::UID qw(adminsuidsetup dbh); +use FS::Record qw(qsearch qsearchs); +use Time::Local 'timelocal'; +use Date::Format 'time2str'; + +my %opt; +getopts('vnd:', \%opt); + +my $user = shift or die &usage; +adminsuidsetup $user; +$FS::UID::AutoCommit = 0; +$FS::Record::nowarn_identical = 1; + +my $extra_sql = FS::cust_main->cancel_sql; +$extra_sql = "WHERE $extra_sql +AND cust_main.payby IN('CARD','DCRD','CHEK','DCHK') +"; + +if($opt{'d'}) { + $opt{'d'} =~ /^(\d+)$/ or die &usage; + my $time = timelocal(0,0,0,(localtime(time-(86400*$1)))[3..5]); + print "Excluding customers canceled after ".time2str("%D",$time)."\n" + if $opt{'v'}; + $extra_sql .= ' AND 0 = (' . FS::cust_main->select_count_pkgs_sql . + " AND cust_pkg.cancel > $time)"; +} + +foreach my $cust_main ( qsearch({ + 'table' => 'cust_main', + 'hashref' => {}, + 'extra_sql' => $extra_sql + }) ) { + if($opt{'v'}) { + print $cust_main->name, "\n"; + } + if($opt{'n'}) { + $cust_main->payinfo('deleted'); + $cust_main->paydate(''); + $cust_main->payby('BILL'); +# can't have a CARD or CHEK without a valid payinfo + } + $cust_main->paycvv(''); + my $error = $cust_main->replace; + if($error) { + dbh->rollback; + die "$error (changes reverted)\n"; + } +} +dbh->commit; + +sub usage { + "Usage:\n\n freeside-wipe-cvv [ -v ] [ -n ] [ -d days ] user\n" +} + +=head1 NAME + +freeside-wipe-cvv - Wipe sensitive payment information from customer records. + +=head1 SYNOPSIS + + freeside-wipe-cvv [ -v ] [ -n ] [ -d days ] user + +=head1 DESCRIPTION + +freeside-wipe-cvv deletes the CVV numbers (and, optionally, credit +card or bank account numbers) of customers who have no non-canceled +packages. Normally CVV numbers are deleted as soon as a payment is +processed; if the customer is canceled before a payment is processed, +this may not happen and the CVV will remain indefinitely, violating +good security practice and (possibly) your merchant agreement. +Running freeside-wipe-cvv will remove this data. + +-v: Be verbose. + +-n: Remove card and account numbers in addition to CVV numbers. This +will also set the customer's payment method to 'BILL'. + +-d days: Only remove CVV/card numbers from customers who have been +inactive for at least that many days. Optional; will default to +all canceled customers. + +=cut + -- cgit v1.2.1 From 03eaa0e229c5f5fc68a178d20fb69733db709787 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 24 Aug 2010 18:57:11 +0000 Subject: add debugging and ->finish() before ->disconnect call --- FS/FS/part_export/sqlradius.pm | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'FS') diff --git a/FS/FS/part_export/sqlradius.pm b/FS/FS/part_export/sqlradius.pm index d8c5e0424..f1ba3d2ff 100644 --- a/FS/FS/part_export/sqlradius.pm +++ b/FS/FS/part_export/sqlradius.pm @@ -408,6 +408,14 @@ sub sqlradius_usergroup_insert { #subroutine, not method $sth->execute( $username, $group ) or die "can't insert into groupname table: ". $sth->errstr; } + if ( $s_sth->{Active} ) { + warn "sqlradius s_sth still active; calling ->finish()"; + $s_sth->finish; + } + if ( $sth->{Active} ) { + warn "sqlradius sth still active; calling ->finish()"; + $sth->finish; + } $dbh->disconnect; } -- cgit v1.2.1 From ced6be92d868addbed9ff93b39bbd6a1f634bcb7 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 24 Aug 2010 19:07:59 +0000 Subject: eliminate needless noise on lack of sync_bill_date option --- FS/FS/part_pkg/flat.pm | 2 +- FS/FS/part_pkg/prorate_Mixin.pm | 2 +- FS/FS/part_pkg/recur_Common.pm | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index a04f44ae4..537cdcf56 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -171,7 +171,7 @@ sub calc_recur { return 0 if $self->option('recur_temporality', 1) eq 'preceding' && $last_bill == 0; - if( $self->option('sync_bill_date') ) { + if( $self->option('sync_bill_date',1) ) { return $self->calc_prorate(@_); } else { diff --git a/FS/FS/part_pkg/prorate_Mixin.pm b/FS/FS/part_pkg/prorate_Mixin.pm index a60858b37..2adf2f16a 100644 --- a/FS/FS/part_pkg/prorate_Mixin.pm +++ b/FS/FS/part_pkg/prorate_Mixin.pm @@ -48,7 +48,7 @@ sub calc_prorate { my $charge = $self->option('recur_fee') || 0; my $cutoff_day; - if( $self->option('sync_bill_date') ) { + if( $self->option('sync_bill_date',1) ) { my $next_bill = $cust_pkg->cust_main->next_bill_date; if( defined($next_bill) and $next_bill != $$sdate ) { $cutoff_day = (localtime($next_bill))[3]; diff --git a/FS/FS/part_pkg/recur_Common.pm b/FS/FS/part_pkg/recur_Common.pm index 9a6774579..21a78c00a 100644 --- a/FS/FS/part_pkg/recur_Common.pm +++ b/FS/FS/part_pkg/recur_Common.pm @@ -27,7 +27,7 @@ sub calc_recur_Common { my $recur_method = $self->option('recur_method', 1) || 'anniversary'; if ( $recur_method eq 'prorate' - or ($recur_method eq 'anniversary' and $self->option('sync_bill_date')) + or ($recur_method eq 'anniversary' and $self->option('sync_bill_date',1)) ) { $charges = $self->calc_prorate(@_); } -- cgit v1.2.1 From ca44d2a9e3faeb47ad8a9419dd535a2d1ba53a30 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 24 Aug 2010 20:09:51 +0000 Subject: insurance against prepaid double-billing, RT#9689 --- FS/bin/freeside-prepaidd | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'FS') diff --git a/FS/bin/freeside-prepaidd b/FS/bin/freeside-prepaidd index 86bfe8794..2d64221de 100644 --- a/FS/bin/freeside-prepaidd +++ b/FS/bin/freeside-prepaidd @@ -41,21 +41,30 @@ while (1) { my $work_cust_pkg = $cust_pkg; my $cust_main = $cust_pkg->cust_main; + + #insurance: somehow winding up here without things properly applied... + my $a_error = $cust_main->apply_payments_and_credits; + if ( $a_error ) { + warn "Error applying payments&credits, customer #". $cust_main->custnum; + next; + } + if ( $cust_main->total_unapplied_payments > 0 - or $cust_main->total_credited > 0 + || $cust_main->total_credited > 0 ) { + #this needs a flag to say only do the prepaid packages... # and only try em if the renewal price matches.. but this will do for now my $b_error = $cust_main->bill; if ( $b_error ) { warn "Error billing customer #". $cust_main->custnum; - next; + next; } $b_error = $cust_main->apply_payments_and_credits; if ( $b_error ) { warn "Error applying payments&credits, customer #". $cust_main->custnum; - next; + next; } $work_cust_pkg = qsearchs('cust_pkg', { 'pkgnum' => $work_cust_pkg->pkgnum } ); -- cgit v1.2.1 From 2bdb2bb70aee8faf11da3b09f110be892103c0a7 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 24 Aug 2010 20:11:08 +0000 Subject: ensure signup payments are applied, RT#9689 --- FS/FS/ClientAPI/Signup.pm | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'FS') diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm index a4032f3b1..2aca88c98 100644 --- a/FS/FS/ClientAPI/Signup.pm +++ b/FS/FS/ClientAPI/Signup.pm @@ -641,14 +641,14 @@ sub new_customer { if ( $conf->exists('signup_server-realtime') ) { - #warn "[fs_signup_server] Billing customer...\n" if $Debug; + #warn "$me Billing customer...\n" if $Debug; my $bill_error = $cust_main->bill; - #warn "[fs_signup_server] error billing new customer: $bill_error" + #warn "$me error billing new customer: $bill_error" # if $bill_error; $bill_error = $cust_main->apply_payments_and_credits; - #warn "[fs_signup_server] error applying payments and credits for". + #warn "$me error applying payments and credits for". # " new customer: $bill_error" # if $bill_error; @@ -656,7 +656,7 @@ sub new_customer { method => FS::payby->payby2bop( $packet->{payby} ), depend_jobnum => $placeholder->jobnum, ); - #warn "[fs_signup_server] error collecting from new customer: $bill_error" + #warn "$me error collecting from new customer: $bill_error" # if $bill_error; if ($bill_error && ref($bill_error) eq 'HASH') { @@ -668,6 +668,11 @@ sub new_customer { }; } + $bill_error = $cust_main->apply_payments_and_credits; + #warn "$me error applying payments and credits for". + # " new customer: $bill_error" + # if $bill_error; + if ( $cust_main->balance > 0 ) { #this makes sense. credit is "un-doing" the invoice -- cgit v1.2.1 From 7d1b8dab48a9396cf0a066545750f69598f66bc8 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 24 Aug 2010 20:14:25 +0000 Subject: insurance against prepaid double-billing, RT#9689 --- FS/bin/freeside-prepaidd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/bin/freeside-prepaidd b/FS/bin/freeside-prepaidd index 2d64221de..05b068b02 100644 --- a/FS/bin/freeside-prepaidd +++ b/FS/bin/freeside-prepaidd @@ -50,7 +50,7 @@ while (1) { } if ( $cust_main->total_unapplied_payments > 0 - || $cust_main->total_credited > 0 + || $cust_main->total_unapplied_credits > 0 ) { -- cgit v1.2.1 From 52339a89155fd6cb5734188119214e2b9c4a0f9b Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 24 Aug 2010 22:04:41 +0000 Subject: blank payinfo instead of "deleted" --- FS/bin/freeside-wipe-cvv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/bin/freeside-wipe-cvv b/FS/bin/freeside-wipe-cvv index 611e841ae..70f0df98f 100755 --- a/FS/bin/freeside-wipe-cvv +++ b/FS/bin/freeside-wipe-cvv @@ -38,7 +38,7 @@ foreach my $cust_main ( qsearch({ print $cust_main->name, "\n"; } if($opt{'n'}) { - $cust_main->payinfo('deleted'); + $cust_main->payinfo(''); $cust_main->paydate(''); $cust_main->payby('BILL'); # can't have a CARD or CHEK without a valid payinfo -- cgit v1.2.1 From 8b69219cd572abe6c2f2873d3a5306f8ae513fc9 Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 24 Aug 2010 23:41:42 +0000 Subject: typo? --- FS/FS/ClientAPI/Signup.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm index 2aca88c98..26d3612cb 100644 --- a/FS/FS/ClientAPI/Signup.pm +++ b/FS/FS/ClientAPI/Signup.pm @@ -90,7 +90,7 @@ sub signup_info { ], 'agent' => [ map { my $agent = $_; - +{ map { $_ => $agent->get($_) } @agent_fields } + { map { $_ => $agent->get($_) } @agent_fields } } qsearch('agent', { 'disabled' => '' } ) ], -- cgit v1.2.1 From 678a99d01de1bd98dd49110d3862d173918fc9b8 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 25 Aug 2010 09:25:56 +0000 Subject: roll back the import transaction on fatal parsing errors on CDR import, so the cdr_batch record gets removed and db doesn't throw a dup key error, RT#9135 --- FS/FS/Record.pm | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'FS') diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index 71cc69ca9..6b05d2dac 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -1871,7 +1871,13 @@ sub batch_import { while ( scalar(@later) ) { my $sub = shift @later; my $data = shift @later; - &{$sub}($record, $data, $conf, $param); # $record->&{$sub}($data, $conf); + eval { + &{$sub}($record, $data, $conf, $param); # $record->&{$sub}($data, $conf) + }; + if ( $@ ) { + $dbh->rollback if $oldAutoCommit; + return "can't insert record". ( $line ? " for $line" : '' ). ": $@"; + } last if exists( $param->{skiprow} ); } next if exists( $param->{skiprow} ); @@ -1902,9 +1908,12 @@ sub batch_import { } - $dbh->commit or die $dbh->errstr if $oldAutoCommit;; + unless ( $imported || $param->{empty_ok} ) { + $dbh->rollback if $oldAutoCommit; + return "Empty file!"; + } - return "Empty file!" unless $imported || $param->{empty_ok}; + $dbh->commit or die $dbh->errstr if $oldAutoCommit;; ''; #no error -- cgit v1.2.1 From 01618f9ed8c0f96d9d17b355cc9db2e54b004397 Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 25 Aug 2010 09:42:04 +0000 Subject: clear signup_info cache when starting xmlrpcd, RT#9380 --- FS/FS/ClientAPI/Signup.pm | 11 ++++++++++- FS/FS/ClientAPI_XMLRPC.pm | 1 + FS/bin/freeside-selfservice-xmlrpcd | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/ClientAPI/Signup.pm b/FS/FS/ClientAPI/Signup.pm index 26d3612cb..2eefcf8d9 100644 --- a/FS/FS/ClientAPI/Signup.pm +++ b/FS/FS/ClientAPI/Signup.pm @@ -26,6 +26,15 @@ use FS::payby; $DEBUG = 0; $me = '[FS::ClientAPI::Signup]'; +sub clear_cache { + warn "$me clear_cache called\n" if $DEBUG; + my $cache = new FS::ClientAPI_SessionCache( { + 'namespace' => 'FS::ClientAPI::Signup', + } ); + $cache->clear(); + return {}; +} + sub signup_info { my $packet = shift; @@ -90,7 +99,7 @@ sub signup_info { ], 'agent' => [ map { my $agent = $_; - { map { $_ => $agent->get($_) } @agent_fields } + +{ map { $_ => $agent->get($_) } @agent_fields } } qsearch('agent', { 'disabled' => '' } ) ], diff --git a/FS/FS/ClientAPI_XMLRPC.pm b/FS/FS/ClientAPI_XMLRPC.pm index cfaf009c7..32e96b8e2 100644 --- a/FS/FS/ClientAPI_XMLRPC.pm +++ b/FS/FS/ClientAPI_XMLRPC.pm @@ -104,6 +104,7 @@ sub ss2clientapi { 'domain_select_hash' => 'Signup/domain_select_hash', # expose? 'new_customer' => 'Signup/new_customer', 'capture_payment' => 'Signup/capture_payment', + 'clear_signup_cache' => 'Signup/clear_cache', 'agent_login' => 'Agent/agent_login', 'agent_logout' => 'Agent/agent_logout', 'agent_info' => 'Agent/agent_info', diff --git a/FS/bin/freeside-selfservice-xmlrpcd b/FS/bin/freeside-selfservice-xmlrpcd index fa745ecf2..e50d51605 100755 --- a/FS/bin/freeside-selfservice-xmlrpcd +++ b/FS/bin/freeside-selfservice-xmlrpcd @@ -63,6 +63,8 @@ logfile("$FREESIDE_LOG/selfservice-xmlrpcd.log"); daemonize2(); +FS::ClientAPI::Signup::clear_cache(); + my $conf = new FS::Conf; die "not running; selfservice-xmlrpc conf option is off\n" -- cgit v1.2.1 From 837bf36d5c126a10f0ceba381526fa35d0716b02 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 25 Aug 2010 22:02:02 +0000 Subject: slightly better description? --- FS/FS/part_event/Condition/balance_age.pm | 2 -- FS/FS/part_event/Condition/once_every.pm | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_event/Condition/balance_age.pm b/FS/FS/part_event/Condition/balance_age.pm index fc3461210..84806596a 100644 --- a/FS/FS/part_event/Condition/balance_age.pm +++ b/FS/FS/part_event/Condition/balance_age.pm @@ -45,8 +45,6 @@ sub order_sql { shift->condition_sql_option_age('age'); } -use FS::UID qw( driver_name ); - sub order_sql_weight { 10; } diff --git a/FS/FS/part_event/Condition/once_every.pm b/FS/FS/part_event/Condition/once_every.pm index a0d9d6802..2921b3a22 100644 --- a/FS/FS/part_event/Condition/once_every.pm +++ b/FS/FS/part_event/Condition/once_every.pm @@ -7,7 +7,7 @@ use FS::cust_event; use base qw( FS::part_event::Condition ); -sub description { "Don't run this event more than once in interval"; } +sub description { "Don't run this event more than once in the specified interval"; } # Runs the event at most "once every X". -- cgit v1.2.1 From 45f5d02ebfeaef2a955b377436dcf3b7e326c915 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 25 Aug 2010 22:11:08 +0000 Subject: slightly better description? --- FS/FS/part_event/Condition/pkg_next_bill_within.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/part_event/Condition/pkg_next_bill_within.pm b/FS/FS/part_event/Condition/pkg_next_bill_within.pm index dc16ce843..90c4c6acc 100644 --- a/FS/FS/part_event/Condition/pkg_next_bill_within.pm +++ b/FS/FS/part_event/Condition/pkg_next_bill_within.pm @@ -5,7 +5,7 @@ use base qw( FS::part_event::Condition ); use FS::Record qw( qsearch ); sub description { - 'Next bill date within interval'; + 'Next bill date within upcoming interval'; } # Run the event when the next bill date is within X days. -- cgit v1.2.1 From 96c38c27a3c07728fbb116acd16bfed026771376 Mon Sep 17 00:00:00 2001 From: mark Date: Thu, 26 Aug 2010 00:08:59 +0000 Subject: avoid breaking recur_Common dependency --- FS/FS/part_pkg/recur_Common.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_pkg/recur_Common.pm b/FS/FS/part_pkg/recur_Common.pm index 21a78c00a..ec17c1662 100644 --- a/FS/FS/part_pkg/recur_Common.pm +++ b/FS/FS/part_pkg/recur_Common.pm @@ -4,9 +4,9 @@ use strict; use vars qw( @ISA %info %recur_method ); use Tie::IxHash; use Time::Local; -use FS::part_pkg::prorate_Mixin; +use FS::part_pkg::prorate; -@ISA = qw(FS::part_pkg::prorate_Mixin); +@ISA = qw(FS::part_pkg::prorate); %info = ( 'disabled' => 1 ); #recur_Common not a usable price plan directly -- cgit v1.2.1 From b2d209dec4f00bd444a68c201940ef90f2af050f Mon Sep 17 00:00:00 2001 From: mark Date: Thu, 26 Aug 2010 21:10:25 +0000 Subject: fix bug affecting single_price calculation --- FS/FS/part_pkg/voip_cdr.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm index 984a65068..5fc1fb8cd 100644 --- a/FS/FS/part_pkg/voip_cdr.pm +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -699,6 +699,8 @@ sub calc_usage { $classnum = $rate_detail->classnum; $charge = sprintf('%.2f', $charge); + warn "Incrementing \$charges by $charge. Now $charges\n" if $DEBUG; + $charges += $charge; @call_details = ( $cdr->downstream_csv( 'format' => $output_format, @@ -718,8 +720,6 @@ sub calc_usage { if ( $charge > 0 ) { #just use FS::cust_bill_pkg_detail objects? - warn "Incrementing \$charges by $charge. Now $charges\n" if $DEBUG; - $charges += $charge; my $call_details; my $phonenum = $cust_svc->svc_x->phonenum; -- cgit v1.2.1 From fff0c848e4c9933934b8da41d23e45b204cac11b Mon Sep 17 00:00:00 2001 From: mark Date: Fri, 27 Aug 2010 02:10:14 +0000 Subject: per-package option to adjust bill date on unsuspend, RT#8434 --- FS/FS/cust_pkg.pm | 14 ++++++++++---- FS/FS/part_pkg/flat.pm | 6 ++++++ 2 files changed, 16 insertions(+), 4 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index f367fd34e..c27d4587d 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -1024,10 +1024,16 @@ sub unsuspend { my $conf = new FS::Conf; - $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive - if ( $opt{'adjust_next_bill'} - || $conf->exists('unsuspend-always_adjust_next_bill_date') ) - && $inactive > 0 && ( $hash{'bill'} || $hash{'setup'} ); + if ( $inactive > 0 && + ( $hash{'bill'} || $hash{'setup'} ) && + ( $opt{'adjust_next_bill'} || + $conf->exists('unsuspend-always_adjust_next_bill_date') || + $self->part_pkg->option('unsuspend_adjust_bill', 1) ) + ) { + + $hash{'bill'} = ( $hash{'bill'} || $hash{'setup'} ) + $inactive; + + } $hash{'susp'} = ''; $hash{'adjourn'} = '' if $hash{'adjourn'} < time; diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index 537cdcf56..d539e8d1e 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -123,6 +123,11 @@ tie my %temporalities, 'Tie::IxHash', 'with the customer\'s other packages', 'type' => 'checkbox', }, + 'unsuspend_adjust_bill' => + { 'name' => 'Adjust next bill date forward when '. + 'unsuspending', + 'type' => 'checkbox', + }, %usage_fields, %usage_recharge_fields, @@ -134,6 +139,7 @@ tie my %temporalities, 'Tie::IxHash', 'fieldorder' => [ qw( setup_fee recur_fee recur_temporality unused_credit expire_months start_1st sync_bill_date + unsuspend_adjust_bill ), @usage_fieldorder, @usage_recharge_fieldorder, qw( externalid ), -- cgit v1.2.1 From 51f402605a8742284f81d1097f3e6df29ee6e2dc Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 1 Sep 2010 17:50:54 +0000 Subject: make expdate available in new alerter templates, RT#9786 --- FS/FS/Cron/alert_expiration.pm | 1 + FS/FS/msg_template.pm | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/Cron/alert_expiration.pm b/FS/FS/Cron/alert_expiration.pm index 364fc60c7..eb53ea880 100644 --- a/FS/FS/Cron/alert_expiration.pm +++ b/FS/FS/Cron/alert_expiration.pm @@ -99,6 +99,7 @@ sub alert_expiration { my $msgnum = $conf->config('alerter_msgnum', $agentnum); if ( $msgnum ) { # new hotness my $msg_template = qsearchs('msg_template', { msgnum => $msgnum } ); + $customer->setfield('expdate', $expire_time); $error = $msg_template->send('cust_main' => $customer); } else { #!$msgnum, the hard way diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm index 121742129..071a3c465 100644 --- a/FS/FS/msg_template.pm +++ b/FS/FS/msg_template.pm @@ -226,7 +226,7 @@ sub prepare { $_ } @$guts; - $body = ''; + $body = '{ use Date::Format qw(time2str); "" }'; while(@$skin || @$guts) { $body .= shift(@$skin) || ''; $body .= shift(@$guts) || ''; @@ -315,7 +315,9 @@ sub substitutions { cust_status ucfirst_cust_status cust_statuscolor signupdate dundate + expdate ), + # expdate is a special case [ signupdate_ymd => sub { time2str('%Y-%m-%d', shift->signupdate) } ], [ dundate_ymd => sub { time2str('%Y-%m-%d', shift->dundate) } ], [ paydate_my => sub { sprintf('%02d/%04d', shift->paydate_monthyear) } ], -- cgit v1.2.1 From 01978afbd19eeebf30398df3d61052f14824d794 Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 1 Sep 2010 18:44:15 +0000 Subject: packages and recurdates for impending_recur templates --- FS/FS/Cron/notify.pm | 4 +++- FS/FS/msg_template.pm | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/Cron/notify.pm b/FS/FS/Cron/notify.pm index ece96fcfd..dcc7b30fe 100644 --- a/FS/FS/Cron/notify.pm +++ b/FS/FS/Cron/notify.pm @@ -4,7 +4,7 @@ use strict; use vars qw( @ISA @EXPORT_OK $DEBUG ); use Exporter; use FS::UID qw( dbh driver_name ); -use FS::Record qw(qsearch); +use FS::Record qw(qsearch qsearchs); use FS::cust_main; use FS::cust_pkg; @@ -106,6 +106,8 @@ END my $msgnum = $conf->config('impending_recur_msgnum',$cust_main->agentnum); if ( $msgnum ) { my $msg_template = qsearchs('msg_template', { msgnum => $msgnum }); + $cust_main->setfield('packages', \\@packages); + $cust_main->setfield('recurdates', \\@recurdates); $error = $msg_template->send('cust_main' => $cust_main); } else { diff --git a/FS/FS/msg_template.pm b/FS/FS/msg_template.pm index 071a3c465..50298d278 100644 --- a/FS/FS/msg_template.pm +++ b/FS/FS/msg_template.pm @@ -316,6 +316,7 @@ sub substitutions { signupdate dundate expdate + packages recurdates ), # expdate is a special case [ signupdate_ymd => sub { time2str('%Y-%m-%d', shift->signupdate) } ], -- cgit v1.2.1 From f2240900f54abdc9fae831d3414989a6385e0e54 Mon Sep 17 00:00:00 2001 From: mark Date: Thu, 2 Sep 2010 22:53:58 +0000 Subject: agent_custid in shellcommands export, RT#9826 --- FS/FS/part_export/shellcommands.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index ec861d3b2..3d5e67277 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -193,7 +193,7 @@ old_ for replace operations):
  • $pkgnum
  • $custnum
  • All other fields in svc_acct are also available. -
  • The following fields from cust_main are also available (except during replace): company, address1, address2, city, state, zip, county, daytime, night, fax, otaker. When used on the command line (rather than STDIN), they will be quoted for the shell already (do not add additional quotes). +
  • The following fields from cust_main are also available (except during replace): company, address1, address2, city, state, zip, county, daytime, night, fax, otaker, agent_custid. When used on the command line (rather than STDIN), they will be quoted for the shell already (do not add additional quotes). END ); @@ -263,7 +263,7 @@ sub _export_command { { no strict 'refs'; foreach my $custf (qw( company address1 address2 city state zip country - daytime night fax otaker + daytime night fax otaker agent_custid )) { ${$custf} = $cust_pkg->cust_main->$custf(); -- cgit v1.2.1 From ed8ba09ae208a58242d16b36c6dfa04d9cd75414 Mon Sep 17 00:00:00 2001 From: mark Date: Tue, 7 Sep 2010 20:25:20 +0000 Subject: agent_custid available on replace, RT#9826 --- FS/FS/part_export/shellcommands.pm | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index 3d5e67277..2066db418 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -342,6 +342,7 @@ sub _export_command { $night = shell_quote $night; $fax = shell_quote $fax; $otaker = shell_quote $otaker; + $acct_custid = shell_quote $acct_custid; my $command_string = eval(qq("$command")); my @ssh_cmd_args = ( @@ -375,6 +376,8 @@ sub _export_replace { } my $old_cust_pkg = $old->cust_svc->cust_pkg; my $new_cust_pkg = $new->cust_svc->cust_pkg; + my $new_cust_main = $new_cust_pkg ? $new_cust_pkg->cust_main : ''; + $new_finger =~ /^(.*)\s+(\S+)$/ or $new_finger =~ /^((.*))$/; ($new_first, $new_last ) = ( $1, $2 ); $quoted_new__password = shell_quote $new__password; #old, wrong? @@ -415,6 +418,12 @@ sub _export_replace { return $error. ' ('. $self->exporttype. ' to '. $self->machine. ')' if $error; + $new_agent_custid = $new_cust_main ? $new_cust_main->agent_custid : ''; + $old_pkgnum = $old_cust_pkg ? $old_cust_pkg->pkgnum : ''; + $old_custnum = $old_cust_pkg ? $old_cust_pkg->custnum : ''; + $new_pkgnum = $new_cust_pkg ? $new_cust_pkg->pkgnum : ''; + $new_custnum = $new_cust_pkg ? $new_cust_pkg->custnum : ''; + my $stdin_string = eval(qq("$stdin")); $new_first = shell_quote $new_first; @@ -422,10 +431,7 @@ sub _export_replace { $new_finger = shell_quote $new_finger; $new_crypt_password = shell_quote $new_crypt_password; $new_ldap_password = shell_quote $new_ldap_password; - $old_pkgnum = $old_cust_pkg ? $old_cust_pkg->pkgnum : ''; - $old_custnum = $old_cust_pkg ? $old_cust_pkg->custnum : ''; - $new_pkgnum = $new_cust_pkg ? $new_cust_pkg->pkgnum : ''; - $new_custnum = $new_cust_pkg ? $new_cust_pkg->custnum : ''; + $new_agent_custid = shell_quote $new_agent_custid; my $command_string = eval(qq("$command")); -- cgit v1.2.1 From 972250d54b5a8b3b071c878de27d2bc87e3222f1 Mon Sep 17 00:00:00 2001 From: mark Date: Thu, 9 Sep 2010 00:35:29 +0000 Subject: auto-adjourn option in flat packages, RT#9516 --- FS/FS/cust_pkg.pm | 17 ++++++----------- FS/FS/part_pkg.pm | 24 +++++++++++++----------- FS/FS/part_pkg/flat.pm | 5 ++++- 3 files changed, 23 insertions(+), 23 deletions(-) (limited to 'FS') diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index c27d4587d..e93476dce 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -259,17 +259,12 @@ sub insert { $self->start_date( timelocal_nocheck(0,0,0,1,$mon,$year) ); } - my $expire_months = $self->part_pkg->option('expire_months', 1); - if ( $expire_months && !$self->expire ) { - my $start = $self->start_date || $self->setup || time; - - #false laziness w/part_pkg::add_freq - my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($start) )[0,1,2,3,4,5]; - $mon += $expire_months; - until ( $mon < 12 ) { $mon -= 12; $year++; } - - #$self->expire( timelocal_nocheck($sec,$min,$hour,$mday,$mon,$year) ); - $self->expire( timelocal_nocheck(0,0,0,$mday,$mon,$year) ); + foreach my $action ( qw(expire adjourn) ) { + my $months = $self->part_pkg->option("${action}_months",1); + if($months and !$self->$action) { + my $start = $self->start_date || $self->setup || time; + $self->$action( $self->part_pkg->add_freq($start, $months) ); + } } local $SIG{HUP} = 'IGNORE'; diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index f278d5ebd..21ab97568 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -809,32 +809,34 @@ sub freq_pretty { } } -=item add_freq TIMESTAMP +=item add_freq TIMESTAMP [ FREQ ] -Adds the frequency of this package to the provided timestamp and returns -the resulting timestamp, or -1 if the frequency of this package could not be -parsed (shouldn't happen). +Adds a billing period of some frequency to the provided timestamp and +returns the resulting timestamp, or -1 if the frequency could not be +parsed (shouldn't happen). By default, the frequency of this package +will be used; to override this, pass a different frequency as a second +argument. =cut sub add_freq { - my( $self, $date ) = @_; - my $freq = $self->freq; + my( $self, $date, $freq ) = @_; + $freq = $self->freq if !defined($freq); #change this bit to use Date::Manip? CAREFUL with timezones (see # mailing list archive) my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($date) )[0,1,2,3,4,5]; - if ( $self->freq =~ /^\d+$/ ) { - $mon += $self->freq; + if ( $freq =~ /^\d+$/ ) { + $mon += $freq; until ( $mon < 12 ) { $mon -= 12; $year++; } - } elsif ( $self->freq =~ /^(\d+)w$/ ) { + } elsif ( $freq =~ /^(\d+)w$/ ) { my $weeks = $1; $mday += $weeks * 7; - } elsif ( $self->freq =~ /^(\d+)d$/ ) { + } elsif ( $freq =~ /^(\d+)d$/ ) { my $days = $1; $mday += $days; - } elsif ( $self->freq =~ /^(\d+)h$/ ) { + } elsif ( $freq =~ /^(\d+)h$/ ) { my $hours = $1; $hour += $hours; } else { diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm index d539e8d1e..18388d4c8 100644 --- a/FS/FS/part_pkg/flat.pm +++ b/FS/FS/part_pkg/flat.pm @@ -115,6 +115,8 @@ tie my %temporalities, 'Tie::IxHash', #used in cust_pkg.pm so could add to any price plan 'expire_months' => { 'name' => 'Auto-add an expiration date this number of months out', }, + 'adjourn_months'=> { 'name' => 'Auto-add a suspension date this number of months out', + }, #used in cust_pkg.pm so could add to any price plan where it made sense 'start_1st' => { 'name' => 'Auto-add a start date to the 1st, ignoring the current month.', 'type' => 'checkbox', @@ -138,7 +140,8 @@ tie my %temporalities, 'Tie::IxHash', }, 'fieldorder' => [ qw( setup_fee recur_fee recur_temporality unused_credit - expire_months start_1st sync_bill_date + expire_months adjourn_months + start_1st sync_bill_date unsuspend_adjust_bill ), @usage_fieldorder, @usage_recharge_fieldorder, -- cgit v1.2.1 From 0afabbd646c01ed4c88826edc9d290698b220418 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 10 Sep 2010 19:31:15 +0000 Subject: fix shell quoting for agent_custid... --- FS/FS/part_export/shellcommands.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm index 2066db418..50af45d7d 100644 --- a/FS/FS/part_export/shellcommands.pm +++ b/FS/FS/part_export/shellcommands.pm @@ -342,7 +342,7 @@ sub _export_command { $night = shell_quote $night; $fax = shell_quote $fax; $otaker = shell_quote $otaker; - $acct_custid = shell_quote $acct_custid; + $agent_custid = shell_quote $agent_custid; my $command_string = eval(qq("$command")); my @ssh_cmd_args = ( -- cgit v1.2.1 From d583a5d3c0647488bac7b7a33d319fd1a85c05b3 Mon Sep 17 00:00:00 2001 From: ivan Date: Sat, 11 Sep 2010 17:02:59 +0000 Subject: dns updates from Erik L: add ttl support, add check for SRV and finish allowing additional rectypes, allow forward slashes for RFC2317 classless in-arpa delegation, RT#8933 --- FS/FS/Schema.pm | 1 + FS/FS/domain_record.pm | 27 +++++++++++++++++++++------ FS/FS/part_export/domain_sql.pm | 3 +++ FS/FS/svc_domain.pm | 6 ++---- 4 files changed, 27 insertions(+), 10 deletions(-) (limited to 'FS') diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index cc6438ab6..459dcabd6 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -1795,6 +1795,7 @@ sub tables_hashref { 'recaf', 'char', '', 2, '', '', 'rectype', 'varchar', '', 5, '', '', 'recdata', 'varchar', '', 255, '', '', + 'ttl', 'int', 'NULL', '', '', '', ], 'primary_key' => 'recnum', 'unique' => [], diff --git a/FS/FS/domain_record.pm b/FS/FS/domain_record.pm index e7e9f70b7..9f1eb5318 100644 --- a/FS/FS/domain_record.pm +++ b/FS/FS/domain_record.pm @@ -1,7 +1,7 @@ package FS::domain_record; use strict; -use vars qw( @ISA $noserial_hack $DEBUG ); +use vars qw( @ISA $noserial_hack $DEBUG $me ); use FS::Conf; #use FS::Record qw( qsearch qsearchs ); use FS::Record qw( qsearchs dbh ); @@ -11,6 +11,7 @@ use FS::svc_www; @ISA = qw(FS::Record); $DEBUG = 0; +$me = '[FS::domain_record]'; =head1 NAME @@ -51,6 +52,8 @@ supported: =item recdata - data for this entry +=item ttl - time to live + =back =head1 METHODS @@ -265,10 +268,12 @@ sub check { $self->recaf =~ /^(IN)$/ or return "Illegal recaf: ". $self->recaf; $self->recaf($1); - $self->rectype =~ /^(SOA|NS|MX|A|PTR|CNAME|TXT|_mstr)$/ - or return "Illegal rectype (only SOA NS MX A PTR CNAME TXT recognized): ". - $self->rectype; - $self->rectype($1); + $self->ttl =~ /^([0-9]{0,6})$/ or return "Illegal ttl: ". $self->ttl; + $self->ttl($1); + + my %rectypes = map { $_=>1 } ( @{ $self->rectypes }, '_mstr' ); + return 'Illegal rectype: '. $self->rectype + unless exists $rectypes{$self->rectype} && $rectypes{$self->rectype}; return "Illegal reczone for ". $self->rectype. ": ". $self->reczone if $self->rectype !~ /^MX$/i && $self->reczone =~ /\*/; @@ -291,6 +296,10 @@ sub check { $self->recdata =~ /^((\d{1,3}\.){3}\d{1,3})$/ or return "Illegal data for A record: ". $self->recdata; $self->recdata($1); + } elsif ( $self->rectype eq 'AAAA' ) { + $self->recdata =~ /^([\da-z:]+)$/ + or return "Illegal data for AAAA record: ". $self->recdata; + $self->recdata($1); } elsif ( $self->rectype eq 'PTR' ) { if ( $conf->exists('zone-underscore') ) { $self->recdata =~ /^([a-z0-9_\.\-]+)$/i @@ -312,11 +321,17 @@ sub check { $self->recdata('"'. $self->recdata. '"'); #? } # or return "Illegal data for TXT record: ". $self->recdata; + } elsif ( $self->rectype eq 'SRV' ) { + $self->recdata =~ /^(\d+)\s+(\d+)\s+(\d+)\s+([a-z0-9\.\-]+)$/i + or return "Illegal data for SRV record: ". $self->recdata; + $self->recdata("$1 $2 $3 $4"); } elsif ( $self->rectype eq '_mstr' ) { $self->recdata =~ /^((\d{1,3}\.){3}\d{1,3})$/ or return "Illegal data for _master pseudo-record: ". $self->recdata; } else { - die "ack!"; + warn "$me no specific check for ". $self->rectype. " records yet"; + $error = $self->ut_text('recdata'); + return $error if $error; } $self->SUPER::check; diff --git a/FS/FS/part_export/domain_sql.pm b/FS/FS/part_export/domain_sql.pm index 0ce1b16e3..30103385b 100644 --- a/FS/FS/part_export/domain_sql.pm +++ b/FS/FS/part_export/domain_sql.pm @@ -99,6 +99,7 @@ sub _export_replace { my %schema = $self->_schema_map; my %static = $self->_static_map; + #my %map = (%schema, %static); my @primary_key = (); if ( $self->option('primary_key') =~ /,/ ) { @@ -107,6 +108,7 @@ sub _export_replace { push @primary_key, $old->$keymap(); } } else { + my %map = (%schema, %static); my $keymap = $map{$self->option('primary_key')}; push @primary_key, $old->$keymap(); } @@ -135,6 +137,7 @@ sub _export_delete { my %schema = $self->_schema_map; my %static = $self->_static_map; + my %map = (%schema, %static); my %primary_key = (); if ( $self->option('primary_key') =~ /,/ ) { diff --git a/FS/FS/svc_domain.pm b/FS/FS/svc_domain.pm index 7d527e5be..dde6d3c2c 100644 --- a/FS/FS/svc_domain.pm +++ b/FS/FS/svc_domain.pm @@ -303,9 +303,6 @@ defined. An FS::cust_svc record will be created and inserted. The additional field I should be set to I for new domains, I for transfers, or I for no action (registered elsewhere). -A registration or transfer email will be submitted unless -$FS::svc_domain::whois_hack is true. - The additional field I can be used to manually set the admin contact email address on this email. Otherwise, the svc_acct records for this package (see L) are searched. If there is exactly one svc_acct record @@ -565,7 +562,7 @@ sub check { $recref->{domain} = "$1.$2"; $recref->{suffix} ||= $2; # hmmmmmmmm. - } elsif ( $whois_hack && $recref->{domain} =~ /^([\w\-\.]+)\.(\w+)$/ ) { + } elsif ( $whois_hack && $recref->{domain} =~ /^([\w\-\.\/]+)\.(\w+)$/ ) { $recref->{domain} = "$1.$2"; # need to match a list of suffixes - no guarantee they're top-level.. # http://wiki.mozilla.org/TLD_List @@ -623,6 +620,7 @@ sub domain_record { 'A' => 5, 'TXT' => 6, 'PTR' => 7, + 'SRV' => 8, ); my %sort = ( -- cgit v1.2.1 From 592c1771d2b332127113e30094bf8fd6b026e046 Mon Sep 17 00:00:00 2001 From: ivan Date: Sun, 12 Sep 2010 00:47:39 +0000 Subject: add agent and agentnum to maestro customer_status, RT#9905 --- FS/FS/Maestro.pm | 2 ++ 1 file changed, 2 insertions(+) (limited to 'FS') diff --git a/FS/FS/Maestro.pm b/FS/FS/Maestro.pm index 05693681d..c1d047032 100644 --- a/FS/FS/Maestro.pm +++ b/FS/FS/Maestro.pm @@ -127,6 +127,8 @@ sub customer_status { return { 'name' => $cust_main->name, 'email' => $cust_main->invoicing_list_emailonly_scalar, + 'agentnum' => $cust_main->agentnum, + 'agent' => $cust_main->agent->agent, 'max_lines' => $svc_pbx ? $svc_pbx->max_extensions : '', 'max_simultaneous' => $svc_pbx ? $svc_pbx->max_simultaneous : '', 'outbound_service' => $outbound_service, -- cgit v1.2.1 From ca8cd9af30c2273891d4e489404b61fbac439fcc Mon Sep 17 00:00:00 2001 From: jeff Date: Thu, 16 Sep 2010 04:17:32 +0000 Subject: default registrations to 1 year --- FS/FS/part_export/domreg_opensrs.pm | 2 ++ 1 file changed, 2 insertions(+) (limited to 'FS') diff --git a/FS/FS/part_export/domreg_opensrs.pm b/FS/FS/part_export/domreg_opensrs.pm index 6554991d3..76f0059aa 100644 --- a/FS/FS/part_export/domreg_opensrs.pm +++ b/FS/FS/part_export/domreg_opensrs.pm @@ -379,6 +379,8 @@ Like most export functions, returns an error message on failure or undef on succ sub register { my ( $self, $svc_domain, $years ) = @_; + $years = 1 unless $years; #default to 1 year since we don't seem to pass it + return "Net::OpenSRS does not support period other than 1 year" if $years != 1; eval "use Net::OpenSRS;"; -- cgit v1.2.1 From 206169a12bd35a0733234f7292cc44fef2ff0ad8 Mon Sep 17 00:00:00 2001 From: ivan Date: Thu, 16 Sep 2010 16:07:36 +0000 Subject: blast from the past: exclamation mark vs. cistron radius with textfiles. RT#9958 --- FS/FS/svc_acct.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm index c301bcd87..d401e1dee 100644 --- a/FS/FS/svc_acct.pm +++ b/FS/FS/svc_acct.pm @@ -101,7 +101,7 @@ FS::UID->install_callback( sub { ); @saltset = ( 'a'..'z' , 'A'..'Z' , '0'..'9' , '.' , '/' ); -@pw_set = ( 'a'..'z', 'A'..'Z', '0'..'9', '(', ')', '#', '!', '.', ',' ); +@pw_set = ( 'a'..'z', 'A'..'Z', '0'..'9', '(', ')', '#', '.', ',' ); sub _cache { my $self = shift; -- cgit v1.2.1 From 9bddb219a4ea74d65a5cf43359f2bb0471c98807 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 17 Sep 2010 05:45:48 +0000 Subject: return svcnum from order_pkg, RT#9906 --- FS/FS/ClientAPI/MyAccount.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'FS') diff --git a/FS/FS/ClientAPI/MyAccount.pm b/FS/FS/ClientAPI/MyAccount.pm index 8003613e7..5ecb71b75 100644 --- a/FS/FS/ClientAPI/MyAccount.pm +++ b/FS/FS/ClientAPI/MyAccount.pm @@ -1181,6 +1181,7 @@ sub order_pkg { 'svc_domain' => [ qw( domain ) ], 'svc_phone' => [ qw( phonenum pin sip_password phone_name ) ], 'svc_external' => [ qw( id title ) ], + 'svc_pbx' => [ qw( id name ) ], ); my $svc_x = "FS::$svcdb"->new( { @@ -1237,7 +1238,9 @@ sub order_pkg { $cust_pkg->reexport; } - return { error => '', pkgnum => $cust_pkg->pkgnum }; + my $svcnum = $svc[0] ? $svc[0]->svcnum : ''; + + return { error=>'', pkgnum=>$cust_pkg->pkgnum, svcnum=>$svcnum }; } -- cgit v1.2.1 From 853fca259ec006d4a5f3ce046e5334210412baac Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 17 Sep 2010 17:28:47 +0000 Subject: when using src_dst_length_less, add option to charge for CDRs where accountcode is toll free anyway, RT#9683 --- FS/FS/part_pkg/voip_cdr.pm | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'FS') diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm index 5fc1fb8cd..c66a3fd5b 100644 --- a/FS/FS/part_pkg/voip_cdr.pm +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -161,7 +161,7 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); 'skip_src_length_more' => { 'name' => 'Do not charge for CDRs where the source is more than this many digits:', }, - 'noskip_src_length_accountcode_tollfree' => { 'name' => 'Do charge for CDRs where source is equal or greater than the specified digits and accountcode is toll free', + 'noskip_src_length_accountcode_tollfree' => { 'name' => 'Do charge for CDRs where source is equal or greater than the specified digits, when accountcode is toll free', 'type' => 'checkbox', }, @@ -178,6 +178,10 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); 'skip_dst_length_less' => { 'name' => 'Do not charge for CDRs where the destination is less than this many digits:', }, + 'noskip_dst_length_accountcode_tollfree' => { 'name' => 'Do charge for CDRs where dst is less than the specified digits, when accountcode is toll free', + 'type' => 'checkbox', + }, + 'skip_lastapp' => { 'name' => 'Do not charge for CDRs where the lastapp matches this value', }, @@ -255,7 +259,9 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); skip_dstchannel_prefix skip_src_length_more noskip_src_length_accountcode_tollfree accountcode_tollfree_ratenum - skip_dst_length_less skip_lastapp + skip_dst_length_less + noskip_dst_length_accountcode_tollfree + skip_lastapp use_duration 411_rewrite output_format usage_mandate summarize_usage usage_section @@ -827,7 +833,7 @@ sub check_chargable { skip_dcontext skip_dstchannel_prefix skip_src_length_more noskip_src_length_accountcode_tollfree - skip_dst_length_less + skip_dst_length_less noskip_dst_length_accountcode_tollfree skip_lastapp ); foreach my $opt (grep !exists($flags{option_cache}->{$_}), @opt ) { @@ -869,7 +875,10 @@ sub check_chargable { my $dst_length = $opt{'skip_dst_length_less'}; return "destination less than $dst_length digits" - if $dst_length && length($cdr->dst) < $dst_length; + if $dst_length && length($cdr->dst) < $dst_length + && ! ( $opt{'noskip_dst_length_accountcode_tollfree'} + && $cdr->is_tollfree + ); return "lastapp is $opt{'skip_lastapp'}" if length($opt{'skip_lastapp'}) && $cdr->lastapp eq $opt{'skip_lastapp'}; -- cgit v1.2.1 From 6c9cd1c36adbb9fc950fcf0a0b269fa6f16838a1 Mon Sep 17 00:00:00 2001 From: mark Date: Fri, 17 Sep 2010 18:12:08 +0000 Subject: email_search_result for cust_pkg and svc_broadband, RT#8736 --- FS/FS/Mason.pm | 1 + FS/FS/cust_main.pm | 162 ++------------------------------------- FS/FS/cust_main_Mixin.pm | 196 +++++++++++++++++++++++++++++++++++++++++++++++ FS/FS/svc_broadband.pm | 120 +++++++++++++++++++++++++++++ 4 files changed, 323 insertions(+), 156 deletions(-) (limited to 'FS') diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 7be78aa03..d769d8514 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -113,6 +113,7 @@ if ( -e $addl_handler_use_file ) { use Locale::Country; use Business::US::USPS::WebTools::AddressStandardization; use LWP::UserAgent; + use Storable qw( nfreeze thaw ); use FS; use FS::UID qw( getotaker dbh datasrc driver_name ); use FS::Record qw( qsearch qsearchs fields dbdef diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index f4b9c5993..007beec92 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2,7 +2,11 @@ package FS::cust_main; require 5.006; use strict; -use base qw( FS::otaker_Mixin FS::payinfo_Mixin FS::Record ); +use base qw( FS::otaker_Mixin + FS::payinfo_Mixin + FS::cust_main_Mixin + FS::Record + ); use vars qw( @EXPORT_OK $DEBUG $me $conf @encrypted_fields $import $ignore_expired_card @@ -8048,7 +8052,7 @@ sub search { ? @{ $params->{'payby'} } : ( $params->{'payby'} ); - @payby = grep /^([A-Z]{4})$/, @{ $params->{'payby'} }; + @payby = grep /^([A-Z]{4})$/, @payby; push @where, '( '. join(' OR ', map "cust_main.payby = '$_'", @payby). ' )' if @payby; @@ -8183,160 +8187,6 @@ sub search { } -=item email_search_result HASHREF - -(Class method) - -Emails a notice to the specified customers. - -Valid parameters are those of the L method, plus the following: - -=over 4 - -=item from - -From: address - -=item subject - -Email Subject: - -=item html_body - -HTML body - -=item text_body - -Text body - -=item job - -Optional job queue job for status updates. - -=back - -Returns an error message, or false for success. - -If an error occurs during any email, stops the enture send and returns that -error. Presumably if you're getting SMTP errors aborting is better than -retrying everything. - -=cut - -sub email_search_result { - my($class, $params) = @_; - - my $from = delete $params->{from}; - my $subject = delete $params->{subject}; - my $html_body = delete $params->{html_body}; - my $text_body = delete $params->{text_body}; - my $error = ''; - - my $job = delete $params->{'job'} - or die "email_search_result must run from the job queue.\n"; - - $params->{'payby'} = [ split(/\0/, $params->{'payby'}) ] - unless ref($params->{'payby'}); - - my $sql_query = $class->search($params); - - my $count_query = delete($sql_query->{'count_query'}); - my $count_sth = dbh->prepare($count_query) - or die "Error preparing $count_query: ". dbh->errstr; - $count_sth->execute - or die "Error executing $count_query: ". $count_sth->errstr; - my $count_arrayref = $count_sth->fetchrow_arrayref; - my $num_cust = $count_arrayref->[0]; - - #my @extra_headers = @{ delete($sql_query->{'extra_headers'}) }; - #my @extra_fields = @{ delete($sql_query->{'extra_fields'}) }; - - - my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo - my @retry_jobs = (); - my $success = 0; - - #eventually order+limit magic to reduce memory use? - foreach my $cust_main ( qsearch($sql_query) ) { - - #progressbar first, so that the count is right - $num++; - if ( time - $min_sec > $last ) { - my $error = $job->update_statustext( - int( 100 * $num / $num_cust ) - ); - die $error if $error; - $last = time; - } - - my $to = $cust_main->invoicing_list_emailonly_scalar; - - if( $to ) { - my @message = ( - 'from' => $from, - 'to' => $to, - 'subject' => $subject, - 'html_body' => $html_body, - 'text_body' => $text_body, - ); - - $error = send_email( generate_email( @message ) ); - - if($error) { - # queue the sending of this message so that the user can see what we - # tried to do, and retry if desired - my $queue = new FS::queue { - 'job' => 'FS::Misc::process_send_email', - 'custnum' => $cust_main->custnum, - 'status' => 'failed', - 'statustext' => $error, - }; - $queue->insert(@message); - push @retry_jobs, $queue; - } - else { - $success++; - } - } - - if($success == 0 and - (scalar(@retry_jobs) > 10 or $num == $num_cust) - ) { - # 10 is arbitrary, but if we have enough failures, that's - # probably a configuration or network problem, and we - # abort the batch and run away screaming. - # We NEVER do this if anything was successfully sent. - $_->delete foreach (@retry_jobs); - return "multiple failures: '$error'\n"; - } - } - - if(@retry_jobs) { - # fail the job, but with a status message that makes it clear - # something was sent. - return "Sent $success, failed ".scalar(@retry_jobs).". Failed attempts placed in job queue.\n"; - } - - return ''; -} - -sub process_email_search_result { - my $job = shift; - #warn "$me process_re_X $method for job $job\n" if $DEBUG; - - my $param = thaw(decode_base64(shift)); - warn Dumper($param) if $DEBUG; - - $param->{'job'} = $job; - - $param->{'payby'} = [ split(/\0/, $param->{'payby'}) ] - unless ref($param->{'payby'}); - - my $error = FS::cust_main->email_search_result( $param ); - die $error if $error; - -} - =item fuzzy_search FUZZY_HASHREF [ HASHREF, SELECT, EXTRA_SQL, CACHE_OBJ ] Performs a fuzzy (approximate) search and returns the matching FS::cust_main diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm index 3dde95f2e..b446d6965 100644 --- a/FS/FS/cust_main_Mixin.pm +++ b/FS/FS/cust_main_Mixin.pm @@ -5,6 +5,8 @@ use vars qw( $DEBUG $me ); use Carp qw( confess ); use FS::UID qw(dbh); use FS::cust_main; +use FS::Record qw( qsearch qsearchs ); +use FS::Misc qw( send_email generate_email ); $DEBUG = 0; $me = '[FS::cust_main_Mixin]'; @@ -33,6 +35,11 @@ for example, from a JOINed search. See httemplate/search/ for examples. sub cust_unlinked_msg { '(unlinked)'; } sub cust_linked { $_[0]->custnum; } +sub cust_main { + my $self = shift; + $self->cust_linked ? qsearchs('cust_main', {custnum => $self->custnum}) : ''; +} + =item display_custnum Given an object that contains fields from cust_main (say, from a JOINed @@ -330,6 +337,195 @@ sub cust_search_sql { } +=item email_search_result HASHREF + +Emails a notice to the specified customers. Customers without +invoice email destinations will be skipped. + +Parameters: + +=over 4 + +=item job + +Queue job for status updates. Required. + +=item search + +Hashref of params to the L method. Required. + +=item msgnum + +Message template number (see L). Overrides all +of the following options. + +=item from + +From: address + +=item subject + +Email Subject: + +=item html_body + +HTML body + +=item text_body + +Text body + +=back + +Returns an error message, or false for success. + +If any messages fail to send, they will be queued as individual +jobs which can be manually retried. If the first ten messages +in the job fail, the entire job will abort and return an error. + +=cut + +use Storable qw(thaw); +use MIME::Base64; +use Data::Dumper qw(Dumper); + +sub email_search_result { + my($class, $param) = @_; + + my $msgnum = $param->{msgnum}; + my $from = delete $param->{from}; + my $subject = delete $param->{subject}; + my $html_body = delete $param->{html_body}; + my $text_body = delete $param->{text_body}; + my $error = ''; + + my $job = delete $param->{'job'} + or die "email_search_result must run from the job queue.\n"; + + my $msg_template; + if ( $msgnum ) { + $msg_template = qsearchs('msg_template', { msgnum => $msgnum } ) + or die "msgnum $msgnum not found\n"; + } + + $param->{'payby'} = [ split(/\0/, $param->{'payby'}) ] + unless ref($param->{'payby'}); + + my $sql_query = $class->search($param->{'search'}); + + my $count_query = delete($sql_query->{'count_query'}); + my $count_sth = dbh->prepare($count_query) + or die "Error preparing $count_query: ". dbh->errstr; + $count_sth->execute + or die "Error executing $count_query: ". $count_sth->errstr; + my $count_arrayref = $count_sth->fetchrow_arrayref; + my $num_cust = $count_arrayref->[0]; + + my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo + my @retry_jobs = (); + my $success = 0; + + #eventually order+limit magic to reduce memory use? + foreach my $obj ( qsearch($sql_query) ) { + + #progressbar first, so that the count is right + $num++; + if ( time - $min_sec > $last ) { + my $error = $job->update_statustext( + int( 100 * $num / $num_cust ) + ); + die $error if $error; + $last = time; + } + + my $cust_main = $obj->cust_main; + my @message; + if ( !$cust_main ) { + next; # unlinked object; nothing else we can do + } + + if ( $msg_template ) { + # XXX add support for other context objects? + @message = $msg_template->prepare( 'cust_main' => $cust_main ); + } + else { + my $to = $cust_main->invoicing_list_emailonly_scalar; + next if !$to; + + @message = ( + 'from' => $from, + 'to' => $to, + 'subject' => $subject, + 'html_body' => $html_body, + 'text_body' => $text_body, + ); + } #if $msg_template + + $error = send_email( generate_email( @message ) ); + + if($error) { + # queue the sending of this message so that the user can see what we + # tried to do, and retry if desired + my $queue = new FS::queue { + 'job' => 'FS::Misc::process_send_email', + 'custnum' => $cust_main->custnum, + 'status' => 'failed', + 'statustext' => $error, + }; + $queue->insert(@message); + push @retry_jobs, $queue; + } + else { + $success++; + } + + if($success == 0 and + (scalar(@retry_jobs) > 10 or $num == $num_cust) + ) { + # 10 is arbitrary, but if we have enough failures, that's + # probably a configuration or network problem, and we + # abort the batch and run away screaming. + # We NEVER do this if anything was successfully sent. + $_->delete foreach (@retry_jobs); + return "multiple failures: '$error'\n"; + } + } # foreach $obj + + if(@retry_jobs) { + # fail the job, but with a status message that makes it clear + # something was sent. + return "Sent $success, failed ".scalar(@retry_jobs).". Failed attempts placed in job queue.\n"; + } + + return ''; +} + +sub process_email_search_result { + my $job = shift; + #warn "$me process_re_X $method for job $job\n" if $DEBUG; + + my $param = thaw(decode_base64(shift)); + warn Dumper($param) if $DEBUG; + + $param->{'job'} = $job; + + $param->{'search'} = thaw(decode_base64($param->{'search'})) + or die "process_email_search_result requires search params.\n"; + +# $param->{'payby'} = [ split(/\0/, $param->{'payby'}) ] +# unless ref($param->{'payby'}); + + my $table = $param->{'table'} + or die "process_email_search_result requires table.\n"; + + eval "use FS::$table;"; + die "error loading FS::$table: $@\n" if $@; + + my $error = "FS::$table"->email_search_result( $param ); + die $error if $error; + +} + =back =head1 BUGS diff --git a/FS/FS/svc_broadband.pm b/FS/FS/svc_broadband.pm index 74cedfc77..5ffe0e452 100755 --- a/FS/FS/svc_broadband.pm +++ b/FS/FS/svc_broadband.pm @@ -113,6 +113,126 @@ sub table { 'svc_broadband'; } sub table_dupcheck_fields { ( 'mac_addr' ); } +=item search HASHREF + +Class method which returns a qsearch hash expression to search for parameters +specified in HASHREF. + +Parameters: + +=over 4 + +=item unlinked - set to search for all unlinked services. Overrides all other options. + +=item agentnum + +=item custnum + +=item svcpart + +=item ip_addr + +=item pkgpart - arrayref + +=item routernum - arrayref + +=item order_by + +=back + +=cut + +sub search { + my ($class, $params) = @_; + my @where = (); + my @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 )', + ); + + # based on FS::svc_acct::search, probably the most mature of the bunch + #unlinked + push @where, 'pkgnum IS NULL' if $params->{'unlinked'}; + + #agentnum + if ( $params->{'agentnum'} =~ /^(\d+)$/ and $1 ) { + push @where, "agentnum = $1"; + } + push @where, $FS::CurrentUser::CurrentUser->agentnums_sql( + 'null_right' => 'View/link unlinked services', + 'table' => 'cust_main' + ); + + #custnum + if ( $params->{'custnum'} =~ /^(\d+)$/ and $1 ) { + push @where, "custnum = $1"; + } + + #pkgpart, now properly untainted, can be arrayref + for my $pkgpart ( $params->{'pkgpart'} ) { + if ( ref $pkgpart ) { + my $where = join(',', map { /^(\d+)$/ ? $1 : () } @$pkgpart ); + push @where, "cust_pkg.pkgpart IN ($where)" if $where; + } + elsif ( $pkgpart =~ /^(\d+)$/ ) { + push @where, "cust_pkg.pkgpart = $1"; + } + } + + #routernum, can be arrayref + for my $routernum ( $params->{'routernum'} ) { + push @from, 'LEFT JOIN addr_block USING ( blocknum )'; + if ( ref $routernum and grep { $_ } @$routernum ) { + my $where = join(',', map { /^(\d+)$/ ? $1 : () } @$routernum ); + push @where, "addr_block.routernum IN ($where)" if $where; + } + elsif ( $routernum =~ /^(\d+)$/ ) { + push @where, "addr_block.routernum = $1"; + } + } + + #svcnum + if ( $params->{'svcnum'} =~ /^(\d+)$/ ) { + push @where, "svcnum = $1"; + } + + #svcpart + if ( $params->{'svcpart'} =~ /^(\d+)$/ ) { + push @where, "svcpart = $1"; + } + + #ip_addr + if ( $params->{'ip_addr'} =~ /^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/ ) { + push @where, "ip_addr = '$1'"; + } + + #custnum + if ( $params->{'custnum'} =~ /^(\d+)$/ and $1) { + push @where, "custnum = $1"; + } + + my $addl_from = join(' ', @from); + my $extra_sql = ''; + $extra_sql = 'WHERE '.join(' AND ', @where) if @where; + my $count_query = "SELECT COUNT(*) FROM svc_broadband $addl_from $extra_sql"; + return( { + 'table' => 'svc_broadband', + 'hashref' => {}, + 'select' => join(', ', + 'svc_broadband.*', + 'part_svc.svc', + 'cust_main.custnum', + FS::UI::Web::cust_sql_fields($params->{'cust_fields'}), + ), + 'extra_sql' => $extra_sql, + 'addl_from' => $addl_from, + 'order_by' => "ORDER BY ".($params->{'order_by'} || 'svcnum'), + 'count_query' => $count_query, + } ); +} + =item search_sql STRING Class method which returns an SQL fragment to search for the given string. -- cgit v1.2.1 From 0fb7ffd120c41dabfc34b6c06443a7604d879f8a Mon Sep 17 00:00:00 2001 From: mark Date: Fri, 17 Sep 2010 19:57:50 +0000 Subject: cdr.max_callers field and skip option, RT#9810 --- FS/FS/Schema.pm | 2 ++ FS/FS/part_pkg/voip_cdr.pm | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) (limited to 'FS') diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 459dcabd6..8403ea2d6 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -2495,6 +2495,8 @@ sub tables_hashref { 'uniqueid', 'varchar', '', 32, \"''", '', 'userfield', 'varchar', '', 255, \"''", '', + 'max_callers', 'int', 'NULL', '', '', '', + ### # fields for unitel/RSLCOM/convergent that don't map well to asterisk # defaults diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm index c66a3fd5b..41c0888d3 100644 --- a/FS/FS/part_pkg/voip_cdr.pm +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -149,10 +149,10 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); 'use_cdrtypenum' => { 'name' => 'Do not charge for CDRs where the CDR Type is not set to: ', }, - 'skip_dst_prefix' => { 'name' => 'Do not charge for CDRs where the destination number starts with any of these values:', + 'skip_dst_prefix' => { 'name' => 'Do not charge for CDRs where the destination number starts with any of these values: ', }, - 'skip_dcontext' => { 'name' => 'Do not charge for CDRs where the dcontext is set to any of these (comma-separated) values:', + 'skip_dcontext' => { 'name' => 'Do not charge for CDRs where the dcontext is set to any of these (comma-separated) values: ', }, 'skip_dstchannel_prefix' => { 'name' => 'Do not charge for CDRs where the dstchannel starts with:', @@ -166,7 +166,7 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); }, 'accountcode_tollfree_ratenum' => { - 'name' => 'Optional alternate rate plan when accountcode is toll free', + 'name' => 'Optional alternate rate plan when accountcode is toll free: ', 'type' => 'select', 'select_table' => 'rate', 'select_key' => 'ratenum', @@ -182,9 +182,12 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); 'type' => 'checkbox', }, - 'skip_lastapp' => { 'name' => 'Do not charge for CDRs where the lastapp matches this value', + 'skip_lastapp' => { 'name' => 'Do not charge for CDRs where the lastapp matches this value: ', }, + 'skip_max_callers' => { 'name' => 'Do not charge for CDRs where max_callers is greater than this value: ', + }, + 'use_duration' => { 'name' => 'Calculate usage based on the duration field instead of the billsec field', 'type' => 'checkbox', }, @@ -199,7 +202,7 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); 'default' => 'default', #XXX test }, - 'usage_section' => { 'name' => 'Section in which to place usage charges (whether separated or not)', + 'usage_section' => { 'name' => 'Section in which to place usage charges (whether separated or not): ', }, 'summarize_usage' => { 'name' => 'Include usage summary with recurring charges when usage is in separate section', @@ -262,6 +265,7 @@ tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); skip_dst_length_less noskip_dst_length_accountcode_tollfree skip_lastapp + skip_max_callers use_duration 411_rewrite output_format usage_mandate summarize_usage usage_section @@ -835,6 +839,7 @@ sub check_chargable { skip_src_length_more noskip_src_length_accountcode_tollfree skip_dst_length_less noskip_dst_length_accountcode_tollfree skip_lastapp + skip_max_callers ); foreach my $opt (grep !exists($flags{option_cache}->{$_}), @opt ) { $flags{option_cache}->{$opt} = $self->option($opt, 1); @@ -903,6 +908,11 @@ sub check_chargable { } + return "max_callers > $opt{skip_max_callers}" + if length($opt{'skip_max_callers'}) + and length($cdr->max_callers) + and $cdr->max_callers > $opt{'skip_max_callers'}; + #all right then, rate it ''; } -- cgit v1.2.1 From 5a52da30588e8811338845ce2edaf0631acad479 Mon Sep 17 00:00:00 2001 From: ivan Date: Fri, 17 Sep 2010 20:19:41 +0000 Subject: refactor giant cust_main.pm a little in preparation of adding API methods for maestro, RT#9967 --- FS/FS.pm | 4 + FS/FS/cust_main.pm | 3527 ++++------------------------------- FS/FS/cust_main/Billing.pm | 1549 +++++++++++++++ FS/FS/cust_main/Billing_Realtime.pm | 1439 ++++++++++++++ FS/FS/part_pkg.pm | 1 + FS/MANIFEST | 2 + 6 files changed, 3313 insertions(+), 3209 deletions(-) create mode 100644 FS/FS/cust_main/Billing.pm create mode 100644 FS/FS/cust_main/Billing_Realtime.pm (limited to 'FS') diff --git a/FS/FS.pm b/FS/FS.pm index 07b31b3b5..40914cef9 100644 --- a/FS/FS.pm +++ b/FS/FS.pm @@ -262,6 +262,10 @@ L - Prospect class L - Customer class +L - Customer billing class + +L - Customer real-time billing class + L - Customer location class L - Mixin class for records that contain fields from cust_main diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 007beec92..21f66b92e 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -2,11 +2,10 @@ package FS::cust_main; require 5.006; use strict; -use base qw( FS::otaker_Mixin - FS::payinfo_Mixin - FS::cust_main_Mixin +use base qw( FS::cust_main::Billing FS::cust_main::Billing_Realtime + FS::otaker_Mixin FS::payinfo_Mixin FS::cust_main_Mixin FS::Record - ); + ); use vars qw( @EXPORT_OK $DEBUG $me $conf @encrypted_fields $import $ignore_expired_card @@ -14,7 +13,6 @@ use vars qw( @EXPORT_OK $DEBUG $me $conf @paytypes ); use vars qw( $realtime_bop_decline_quiet ); #ugh -use Safe; use Carp; use Exporter; use Scalar::Util qw( blessed ); @@ -40,10 +38,6 @@ use FS::payby; use FS::cust_pkg; use FS::cust_svc; use FS::cust_bill; -use FS::cust_bill_pkg; -use FS::cust_bill_pkg_display; -use FS::cust_bill_pkg_tax_location; -use FS::cust_bill_pkg_tax_rate_location; use FS::cust_pay; use FS::cust_pay_pending; use FS::cust_pay_void; @@ -56,15 +50,10 @@ use FS::cust_location; use FS::cust_class; use FS::cust_main_exemption; use FS::cust_tax_adjustment; -use FS::tax_rate; -use FS::tax_rate_location; use FS::cust_tax_location; -use FS::part_pkg_taxrate; use FS::agent; use FS::cust_main_invoice; use FS::cust_tag; -use FS::cust_credit_bill; -use FS::cust_bill_pay; use FS::prepay_credit; use FS::queue; use FS::part_pkg; @@ -80,7 +69,7 @@ use FS::TicketSystem; @EXPORT_OK = qw( smart_search ); -$realtime_bop_decline_quiet = 0; +$realtime_bop_decline_quiet = 0; #move to Billing_Realtime # 1 is mostly method/subroutine entry and options # 2 traces progress of some operations @@ -2667,257 +2656,200 @@ sub classname { : ''; } +=item BILLING METHODS -=item bill_and_collect +Documentation on billing methods has been moved to +L. -Cancels and suspends any packages due, generates bills, applies payments and -credits, and applies collection events to run cards, send bills and notices, -etc. - -By default, warns on errors and continues with the next operation (but see the -"fatal" flag below). +=item do_cust_event [ HASHREF | OPTION => VALUE ... ] -Options are passed as name-value pairs. Currently available options are: +Runs billing events; see L and the billing events web +interface. -=over 4 +If there is an error, returns the error, otherwise returns false. -=item time +Options are passed as name-value pairs. -Bills the customer as if it were that time. Specified as a UNIX timestamp; see L). Also see L and L for conversion functions. For example: +Currently available options are: - use Date::Parse; - ... - $cust_main->bill( 'time' => str2time('April 20th, 2001') ); +=over 4 -=item invoice_time +=item time -Used in conjunction with the I