summaryrefslogtreecommitdiff
path: root/FS
diff options
context:
space:
mode:
Diffstat (limited to 'FS')
-rw-r--r--FS/FS/API.pm5
-rw-r--r--FS/FS/Password_Mixin.pm20
-rw-r--r--FS/FS/Schema.pm4
-rw-r--r--FS/FS/cdr/FS/FS/cdr/vvs.pm7
-rw-r--r--FS/FS/cdr/vss.pm33
-rw-r--r--FS/FS/cust_bill_pkg.pm29
-rw-r--r--FS/FS/cust_main/Billing.pm11
-rw-r--r--FS/FS/cust_main/Billing_Realtime.pm2
-rw-r--r--FS/FS/part_pkg/sql_external.pm71
9 files changed, 130 insertions, 52 deletions
diff --git a/FS/FS/API.pm b/FS/FS/API.pm
index 4e6cb6cd7..4ff1a3ab5 100644
--- a/FS/FS/API.pm
+++ b/FS/FS/API.pm
@@ -65,6 +65,10 @@ Amount paid
Option date for payment
+=item order_number
+
+Optional order number
+
=back
Example:
@@ -77,6 +81,7 @@ Example:
#optional
'_date' => 1397977200, #UNIX timestamp
+ 'order_number' => '12345',
);
if ( $result->{'error'} ) {
diff --git a/FS/FS/Password_Mixin.pm b/FS/FS/Password_Mixin.pm
index b80708116..2e400ec9d 100644
--- a/FS/FS/Password_Mixin.pm
+++ b/FS/FS/Password_Mixin.pm
@@ -60,7 +60,7 @@ sub is_password_allowed {
# basic checks using Data::Password;
# options for Data::Password
- $DICTIONARY = 4; # minimum length of disallowed words
+ $DICTIONARY = 0; # minimum length of disallowed words, false value disables dictionary checking
$MINLEN = $conf->config('passwordmin') || 6;
$MAXLEN = $conf->config('passwordmax') || 8;
$GROUPS = 4; # must have all 4 'character groups': numbers, symbols, uppercase, lowercase
@@ -70,9 +70,23 @@ sub is_password_allowed {
# # lists of disallowed words
# @DICTIONARIES = qw( /usr/share/dict/web2 /usr/share/dict/words /usr/share/dict/linux.words );
+ # first, no dictionary checking but require 4 char groups
my $error = IsBadPassword($password);
- $error = 'must contain at least one each of numbers, symbols, and lowercase and uppercase letters'
- if $error eq 'contains less than 4 character groups'; # avoid confusion
+
+ # but they can get away with 3 char groups, so long as they're not using a word
+ if ($error eq 'contains less than 4 character groups') {
+ $DICTIONARY = 4; # default from Data::Password is 5
+ $GROUPS = 3;
+ $error = IsBadPassword($password);
+ # take note--we never actually report dictionary word errors;
+ # 4 char groups is the rule, 3 char groups and no dictionary words is an acceptable exception
+ $error = 'should contain at least one each of numbers, symbols, lowercase and uppercase letters'
+ if $error;
+ }
+
+ # maybe also at some point add an exception for any passwords of sufficient length,
+ # see https://xkcd.com/936/
+
$error = 'Invalid password - ' . $error if $error;
return $error if $error;
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index c40f6c747..c8f8c815e 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1774,7 +1774,7 @@ sub tables_hashref {
'gatewaynum', 'int', 'NULL', '', '', '', # payment_gateway FK
'processor', 'varchar', 'NULL', $char_d, '', '', # module name
'auth', 'varchar','NULL',16, '', '', # CC auth number
- 'order_number','varchar','NULL',$char_d, '', '', # transaction number
+ 'order_number','varchar','NULL',256, '', '', # transaction number
],
'primary_key' => 'paynum',
#i guess not now, with cust_pay_pending, if we actually make it here, we _do_ want to record it# 'unique' => [ [ 'payunique' ] ],
@@ -1809,7 +1809,7 @@ sub tables_hashref {
'gatewaynum', 'int', 'NULL', '', '', '', # payment_gateway FK
'processor', 'varchar', 'NULL', $char_d, '', '', # module name
'auth', 'varchar','NULL',16, '', '', # CC auth number
- 'order_number', 'varchar','NULL',$char_d, '', '', # transaction number
+ 'order_number', 'varchar','NULL',256, '', '', # transaction number
#void fields
'void_date', @date_type, '', '',
diff --git a/FS/FS/cdr/FS/FS/cdr/vvs.pm b/FS/FS/cdr/FS/FS/cdr/vvs.pm
index 63a647ee8..db7e72ac6 100644
--- a/FS/FS/cdr/FS/FS/cdr/vvs.pm
+++ b/FS/FS/cdr/FS/FS/cdr/vvs.pm
@@ -18,12 +18,11 @@ use FS::cdr qw(_cdr_date_parser_maker);
'src', # caller
'dst', # called
skip(2), # reason
- # call id
+ # call id
_cdr_date_parser_maker('startdate'), # time
'billsec', # duration
- skip(3), # ringtime
- # status
- # resller_charge
+ skip(2), # ringtime
+ # reseller_charge
'upstream_price',# customer_charge
],
);
diff --git a/FS/FS/cdr/vss.pm b/FS/FS/cdr/vss.pm
deleted file mode 100644
index a550303df..000000000
--- a/FS/FS/cdr/vss.pm
+++ /dev/null
@@ -1,33 +0,0 @@
-package FS::cdr::vss;
-
-use strict;
-use vars qw( @ISA %info $tmp_mon $tmp_mday $tmp_year );
-use Time::Local;
-use FS::cdr qw(_cdr_date_parser_maker);
-
-@ISA = qw(FS::cdr);
-
-%info = (
- 'name' => 'VSS',
- 'weight' => 120,
- 'header' => 1,
- 'import_fields' => [
-
- skip(1), # i_customer
- 'accountcode', # account_id
- 'src', # caller
- 'dst', # called
- skip(2), # reason
- # call id
- _cdr_date_parser_maker('startdate'), # time
- 'billsec', # duration
- skip(3), # ringtime
- # status
- # resller_charge
- 'upstream_price',# customer_charge
- ],
-);
-
-sub skip { map {''} (1..$_[0]) }
-
-1;
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
index 4448da62e..a15d7a260 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -1102,17 +1102,34 @@ sub cust_bill_pkg_tax_Xlocation {
=item recur_show_zero
-=cut
+Whether to show a zero recurring amount. This is true if the package or its
+definition has the recur_show_zero flag, and the recurring fee is actually
+zero for this period.
-sub recur_show_zero { shift->_X_show_zero('recur'); }
-sub setup_show_zero { shift->_X_show_zero('setup'); }
+=cut
-sub _X_show_zero {
+sub recur_show_zero {
my( $self, $what ) = @_;
- return 0 unless $self->$what() == 0 && $self->pkgnum;
+ return 0 unless $self->get('recur') == 0 && $self->pkgnum;
+
+ $self->cust_pkg->_X_show_zero('recur');
+}
+
+=item setup_show_zero
- $self->cust_pkg->_X_show_zero($what);
+Whether to show a zero setup charge. This requires the package or its
+definition to have the setup_show_zero flag, but it also returns false if
+the package's setup date is before this line item's start date.
+
+=cut
+
+sub setup_show_zero {
+ my $self = shift;
+ return 0 unless $self->get('setup') == 0 && $self->pkgnum;
+ my $cust_pkg = $self->cust_pkg;
+ return 0 if ( $self->sdate || 0 ) > ( $cust_pkg->setup || 0 );
+ return $cust_pkg->_X_show_zero('setup');
}
=item credited [ BEFORE, AFTER, OPTIONS ]
diff --git a/FS/FS/cust_main/Billing.pm b/FS/FS/cust_main/Billing.pm
index 2f9eecdf5..77529562f 100644
--- a/FS/FS/cust_main/Billing.pm
+++ b/FS/FS/cust_main/Billing.pm
@@ -1261,6 +1261,9 @@ sub _make_lines {
my $unitrecur = 0;
my @recur_discounts = ();
my $sdate;
+
+ my $override_quantity;
+
# Conditions for billing the recurring fee:
# - the package doesn't have a future start date
# - and it's not suspended
@@ -1356,6 +1359,10 @@ sub _make_lines {
#base_cancel???
$unitrecur = $cust_pkg->base_recur( \$sdate ) || $recur; #XXX uuh, better
+ if ( $param{'override_quantity'} ) {
+ $override_quantity = $param{'override_quantity'};
+ }
+
if ( $increment_next_bill ) {
my $next_bill;
@@ -1410,7 +1417,7 @@ sub _make_lines {
}
}
- }
+ } # end of recurring fee
warn "\$setup is undefined" unless defined($setup);
warn "\$recur is undefined" unless defined($recur);
@@ -1477,7 +1484,7 @@ sub _make_lines {
'unitsetup' => sprintf('%.2f', $unitsetup),
'recur' => $recur,
'unitrecur' => sprintf('%.2f', $unitrecur),
- 'quantity' => $cust_pkg->quantity,
+ 'quantity' => $override_quantity || $cust_pkg->quantity,
'details' => \@details,
'discounts' => [ @setup_discounts, @recur_discounts ],
'hidden' => $part_pkg->hidden,
diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm
index 6c0b655a2..2a9e86924 100644
--- a/FS/FS/cust_main/Billing_Realtime.pm
+++ b/FS/FS/cust_main/Billing_Realtime.pm
@@ -522,6 +522,8 @@ sub realtime_bop {
? uc($options{'paytype'})
: uc($self->getfield('paytype')) || 'PERSONAL CHECKING';
+ $content{company} = $self->company if $self->company;
+
if ( $content{account_type} =~ /BUSINESS/i && $self->company ) {
$content{account_name} = $self->company;
} else {
diff --git a/FS/FS/part_pkg/sql_external.pm b/FS/FS/part_pkg/sql_external.pm
index 813e8085c..48d89a0e3 100644
--- a/FS/FS/part_pkg/sql_external.pm
+++ b/FS/FS/part_pkg/sql_external.pm
@@ -6,6 +6,14 @@ use vars qw( %info );
use DBI;
#use FS::Record qw(qsearch qsearchs);
+tie our %query_style, 'Tie::IxHash', (
+ 'simple' => 'Simple (a single value for the recurring charge)',
+ 'detailed' => 'Detailed (multiple rows for invoice details)',
+);
+
+our @detail_cols = ( qw(amount format duration phonenum accountcode
+ startdate regionname detail)
+ );
%info = (
'name' => 'Base charge plus additional fees for external services from a configurable SQL query',
'shortname' => 'External SQL query',
@@ -34,10 +42,17 @@ use DBI;
'query' => { 'name' => 'SQL query',
'default' => '',
},
+
+ 'query_style' => {
+ 'name' => 'Query output style',
+ 'type' => 'select',
+ 'select_options' => \%query_style,
+ },
+
},
'fieldorder' => [qw( recur_method cutoff_day ),
FS::part_pkg::prorate_Mixin::fieldorder,
- qw( datasrc db_username db_password query
+ qw( datasrc db_username db_password query query_style
)],
'weight' => '58',
);
@@ -53,6 +68,7 @@ sub calc_recur {
my $self = shift;
my($cust_pkg, $sdate, $details, $param ) = @_;
my $price = 0;
+ my $quantity; # can be overridden; if not we use the default
my $dbh = DBI->connect( map { $self->option($_) }
qw( datasrc db_username db_password )
@@ -67,9 +83,60 @@ sub calc_recur {
) {
my $id = $cust_svc->svc_x->id;
$sth->execute($id) or die $sth->errstr;
- $price += $sth->fetchrow_arrayref->[0];
+
+ if ( $self->option('query_style') eq 'detailed' ) {
+
+ while (my $row = $sth->fetchrow_hashref) {
+ if (exists $row->{amount}) {
+ if ( $row->{amount} eq '' ) {
+ # treat as zero
+ } elsif ( $row->{amount} =~ /^\d+(?:\.\d+)?$/ ) {
+ $price += $row->{amount};
+ } else {
+ die "sql_external query returned non-numeric amount: $row->{amount}";
+ }
+ }
+ if (exists $row->{quantity}) {
+ $quantity ||= 0;
+ if ( $row->{quantity} eq '' ) {
+ # treat as zero
+ } elsif ( $row->{quantity} =~ /^\d+$/ ) {
+ $quantity += $row->{quantity};
+ } else {
+ die "sql_external query returned non-integer quantity: $row->{quantity}";
+ }
+ }
+
+ my $detail = FS::cust_bill_pkg_detail->new;
+ foreach my $field (@detail_cols) {
+ if (exists $row->{$field}) {
+ $detail->set($field, $row->{$field});
+ }
+ }
+ if (!$detail->get('detail')) {
+ die "sql_external query did not return detail description";
+ # or make something up?
+ # or just don't insert the detail?
+ }
+
+ push @$details, $detail;
+ } # while $row
+
+ } else {
+
+ # simple style: returns only a single value, which is the price
+ $price += $sth->fetchrow_arrayref->[0];
+
+ }
+ }
+ $price = sprintf('%.2f', $price);
+
+ # XXX probably shouldn't allow package quantity > 1 on these packages.
+ if ($cust_pkg->quantity > 1) {
+ warn "sql_external package #".$cust_pkg->pkgnum." has quantity > 1\n";
}
+ $param->{'override_quantity'} = $quantity;
$param->{'override_charges'} = $price;
($cust_pkg->quantity || 1) * $self->calc_recur_Common($cust_pkg,$sdate,$details,$param);
}