summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Burger <burgerc@freeside.biz>2018-02-13 09:04:28 -0500
committerChristopher Burger <burgerc@freeside.biz>2018-02-13 09:04:28 -0500
commit908b5627cc5899b00d3b9e83602403eb956dd038 (patch)
tree3acd7abd582b6c68b3d1612623b2702ba640cc88
parentbd115bbb7ee8b1f8568d026caceb95ae18c80e87 (diff)
parent1f19ac8ae3fda009fede1e3c6f3e1c75b93adfca (diff)
Merge branch 'master' of ssh://git.freeside.biz/home/git/freeside
-rw-r--r--FS/FS/Schema.pm3
-rw-r--r--FS/FS/Template_Mixin.pm7
-rw-r--r--FS/FS/cdr.pm45
-rw-r--r--FS/FS/cust_bill.pm22
-rw-r--r--FS/FS/cust_bill_pkg.pm10
-rw-r--r--FS/FS/cust_bill_pkg_detail.pm20
-rw-r--r--FS/FS/cust_pkg.pm282
-rw-r--r--FS/FS/part_pkg.pm13
-rw-r--r--FS/FS/part_pkg/prorate_Mixin.pm2
-rw-r--r--FS/FS/quotation.pm20
-rwxr-xr-xFS/bin/freeside-passwd19
-rw-r--r--bin/update-rates42
-rw-r--r--debian/control2
-rw-r--r--debian/freeside-webui.postinst10
-rw-r--r--debian/freeside.postinst (renamed from debian/postinst)5
-rwxr-xr-xdebian/rules5
-rw-r--r--httemplate/docs/about.html4
-rw-r--r--httemplate/edit/access_user.html7
-rw-r--r--httemplate/edit/process/access_user.html22
-rw-r--r--httemplate/edit/process/change-cust_pkg.html47
-rw-r--r--httemplate/elements/menu.html3
-rwxr-xr-xhttemplate/misc/change_pkg.cgi18
-rwxr-xr-xhttemplate/misc/process/void-cust_bill.html6
-rwxr-xr-xhttemplate/misc/void-cust_bill.html8
-rw-r--r--httemplate/view/prospect_main.html3
25 files changed, 532 insertions, 93 deletions
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 65eadad21..edecb7f38 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -5661,9 +5661,6 @@ sub tables_hashref {
'sessionnum', 'int', 'NULL', '', '', '',
'subscriber', 'varchar', 'NULL', $char_d, '', '',
- #old
- 'cdrbatch', 'varchar', 'NULL', 255, '', '',
- #new
'cdrbatchnum', 'int', 'NULL', '', '', '',
# FK to cust_bill_pkg_detail; having a value here absolutely means
diff --git a/FS/FS/Template_Mixin.pm b/FS/FS/Template_Mixin.pm
index b9f3e9274..88fd4e87f 100644
--- a/FS/FS/Template_Mixin.pm
+++ b/FS/FS/Template_Mixin.pm
@@ -657,10 +657,11 @@ sub print_generic {
$invoice_data{'cid'} = $params{'cid'}
if $params{'cid'};
- if ( $cust_main->country eq $countrydefault ) {
- $invoice_data{'country'} = '';
- } else {
+ if ( $cust_main->bill_locationnum
+ && $cust_main->bill_location->country ne $countrydefault ) {
$invoice_data{'country'} = &$escape_function($cust_main->bill_country_full);
+ } else {
+ $invoice_data{'country'} = '';
}
my @address = ();
diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm
index 331ac0f2f..3de022466 100644
--- a/FS/FS/cdr.pm
+++ b/FS/FS/cdr.pm
@@ -170,7 +170,7 @@ following fields are currently supported:
=item freesiderewritestatus - NULL, done, skipped
-=item cdrbatch
+=item cdrbatchnum
=item detailnum - Link to invoice detail (L<FS::cust_bill_pkg_detail>)
@@ -240,7 +240,6 @@ sub table_info {
'svcnum' => 'Freeside service',
'freesidestatus' => 'Freeside status',
'freesiderewritestatus' => 'Freeside rewrite status',
- 'cdrbatch' => 'Legacy batch',
'cdrbatchnum' => 'Batch',
'detailnum' => 'Freeside invoice detail line',
},
@@ -1659,7 +1658,12 @@ foreach my $INC ( @INC ) {
tie my %import_formats, 'Tie::IxHash',
map { $_ => $cdr_info{$_}->{'name'} }
- sort { $cdr_info{$a}->{'weight'} <=> $cdr_info{$b}->{'weight'} }
+
+ #this is not doing anything useful anymore
+ #sort { $cdr_info{$a}->{'weight'} <=> $cdr_info{$b}->{'weight'} }
+ #so just sort alpha
+ sort { lc($cdr_info{$a}->{'name'}) cmp lc($cdr_info{$b}->{'name'}) }
+
grep { exists($cdr_info{$_}->{'import_fields'}) }
keys %cdr_info;
@@ -1868,41 +1872,6 @@ sub process_batch_import {
# @columns = map { s/^ +//; $_; } @columns;
# }
-# _ upgrade_data
-#
-# Used by FS::Upgrade to migrate to a new database.
-
-sub _upgrade_data {
- my ($class, %opts) = @_;
-
- warn "$me upgrading $class\n" if $DEBUG;
-
- my $sth = dbh->prepare(
- 'SELECT DISTINCT(cdrbatch) FROM cdr WHERE cdrbatch IS NOT NULL'
- ) or die dbh->errstr;
-
- $sth->execute or die $sth->errstr;
-
- my %cdrbatchnum = ();
- while (my $row = $sth->fetchrow_arrayref) {
-
- my $cdr_batch = qsearchs( 'cdr_batch', { 'cdrbatch' => $row->[0] } );
- unless ( $cdr_batch ) {
- $cdr_batch = new FS::cdr_batch { 'cdrbatch' => $row->[0] };
- my $error = $cdr_batch->insert;
- die $error if $error;
- }
-
- $cdrbatchnum{$row->[0]} = $cdr_batch->cdrbatchnum;
- }
-
- $sth = dbh->prepare('UPDATE cdr SET cdrbatch = NULL, cdrbatchnum = ? WHERE cdrbatch IS NOT NULL AND cdrbatch = ?') or die dbh->errstr;
-
- foreach my $cdrbatch (keys %cdrbatchnum) {
- $sth->execute($cdrbatchnum{$cdrbatch}, $cdrbatch) or die $sth->errstr;
- }
-
-}
=item ip_addr_sql FIELD RANGE
diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm
index 8b94dcc87..bd1b8bbec 100644
--- a/FS/FS/cust_bill.pm
+++ b/FS/FS/cust_bill.pm
@@ -215,7 +215,7 @@ sub insert {
}
-=item void [ REASON ]
+=item void [ REASON [ , REPROCESS_CDRS ] ]
Voids this invoice: deletes the invoice and adds a record of the voided invoice
to the FS::cust_bill_void table (and related tables starting from
@@ -226,6 +226,7 @@ FS::cust_bill_pkg_void).
sub void {
my $self = shift;
my $reason = scalar(@_) ? shift : '';
+ my $reprocess_cdrs = scalar(@_) ? shift : '';
unless (ref($reason) || !$reason) {
$reason = FS::reason->new_or_existing(
@@ -257,7 +258,7 @@ sub void {
}
foreach my $cust_bill_pkg ( $self->cust_bill_pkg ) {
- my $error = $cust_bill_pkg->void($reason);
+ my $error = $cust_bill_pkg->void($reason, $reprocess_cdrs);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
@@ -3541,6 +3542,23 @@ sub _items_aging_balances {
return map{ sprintf('%.2f',$_) } @aging_balances;
}
+=item has_call_details
+
+Returns true if this invoice has call details.
+
+=cut
+
+sub has_call_details {
+ my $self = shift;
+ $self->scalar_sql("
+ SELECT 1 FROM cust_bill_pkg_detail
+ LEFT JOIN cust_bill_pkg USING (billpkgnum)
+ WHERE cust_bill_pkg_detail.format = 'C'
+ AND cust_bill_pkg.invnum = ?
+ LIMIT 1
+ ", $self->invnum);
+}
+
=item call_details [ OPTION => VALUE ... ]
Returns an array of CSV strings representing the call details for this invoice
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
index e44a84709..77dce2476 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -324,7 +324,7 @@ sub insert {
}
-=item void [ REASON ]
+=item void [ REASON [ , REPROCESS_CDRS ] ]
Voids this line item: deletes the line item and adds a record of the voided
line item to the FS::cust_bill_pkg_void table (and related tables).
@@ -334,6 +334,7 @@ line item to the FS::cust_bill_pkg_void table (and related tables).
sub void {
my $self = shift;
my $reason = scalar(@_) ? shift : '';
+ my $reprocess_cdrs = scalar(@_) ? shift : '';
unless (ref($reason) || !$reason) {
$reason = FS::reason->new_or_existing(
@@ -373,6 +374,9 @@ sub void {
cust_tax_exempt_pkg
cust_bill_pkg_fee
)) {
+ my %delete_args = ();
+ $delete_args{'reprocess_cdrs'} = $reprocess_cdrs
+ if $table eq 'cust_bill_pkg_detail';
foreach my $linked ( qsearch($table, { billpkgnum=>$self->billpkgnum }) ) {
@@ -380,7 +384,7 @@ sub void {
my $void = $vclass->new( {
map { $_ => $linked->get($_) } $linked->fields
});
- my $error = $void->insert || $linked->delete;
+ my $error = $void->insert || $linked->delete(%delete_args);
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
@@ -863,7 +867,7 @@ sub _item_discount {
# show introductory rate as a pseudo-discount
if (!$d) { # this will conflict with showing real discounts
my $part_pkg = $self->part_pkg;
- if ( $part_pkg and $part_pkg->option('show_as_discount') ) {
+ if ( $part_pkg and $part_pkg->option('show_as_discount',1) ) {
my $cust_pkg = $self->cust_pkg;
my $intro_end = $part_pkg->intro_end($cust_pkg);
my $_date = $self->cust_bill->_date;
diff --git a/FS/FS/cust_bill_pkg_detail.pm b/FS/FS/cust_bill_pkg_detail.pm
index dd118c1b2..19b15f7dd 100644
--- a/FS/FS/cust_bill_pkg_detail.pm
+++ b/FS/FS/cust_bill_pkg_detail.pm
@@ -106,21 +106,37 @@ sub insert {
'';
}
-=item delete
+=item delete [ ARG => VALUE ... ]
Delete this record from the database.
+If the "reprocess_cdrs" argument is set to true, resets the status of any
+related CDRs (and deletes their associated cdr_termination records, if any).
+
=cut
sub delete {
- my $self = shift;
+ my( $self, %args ) = @_;
+
my $error = $self->SUPER::delete;
return $error if $error;
+
foreach my $cdr (qsearch('cdr', { detailnum => $self->detailnum })) {
+
$cdr->set('detailnum', '');
+ $cdr->set('freesidestatus', '') if $args{'reprocess_cdrs'};
$error = $cdr->replace;
return "error unlinking CDR #" . $cdr->acctid . ": $error" if $error;
+
+ #well, technically this could have been on other invoices / termination
+ # partners... separate flag?
+ $self->scalar_sql( 'DELETE FROM cdr_termination WHERE acctid = ?',
+ $cdr->acctid )
+ if $args{'reprocess_cdrs'};
+
}
+
+ '';
}
=item replace OLD_RECORD
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index 7d683235b..f11beec7d 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -6,7 +6,7 @@ use base qw( FS::cust_pkg::Search FS::cust_pkg::API
);
use strict;
-use Carp qw(cluck);
+use Carp qw(cluck croak);
use Scalar::Util qw( blessed );
use List::Util qw(min max sum);
use Tie::IxHash;
@@ -2372,8 +2372,20 @@ sub change {
$same_pkgpart = 0;
}
- if ($opt->{'waive_setup'}) { $self->set('waive_setup', $opt->{'waive_setup'}) }
- else { $self->set('waive_setup', ''); }
+ # Discounts:
+ # When a new discount level is specified in $opt:
+ # If new discountnum matches old discountnum, months_used/end_date are
+ # carried over as the discount is applied to the new cust_pkg
+ #
+ # Legacy behavior:
+ # Unless discount-related fields have been set within $opt, change()
+ # sets no discounts on the changed packages unless the new pkgpart is the
+ # same as the old pkgpart. In that case, discounts from the old cust_pkg
+ # are copied onto the new cust_pkg
+
+ # Read discount fields from $opt
+ my %new_discount = $self->_parse_new_discounts($opt);
+ $self->set(waive_setup => $opt->{waive_setup} ? $opt->{waive_setup} : '');
# Before going any further here: if the package is still in the pre-setup
# state, it's safe to modify it in place. No need to charge/credit for
@@ -2429,6 +2441,22 @@ sub change {
} # done transferring services
+ # Set waive_setup as directed
+ if ( !$error && exists $opt->{waive_setup} ) {
+ $self->set(waive_setup => $opt->{waive_setup});
+ $error = $self->replace;
+ }
+
+ # Set discounts if explicitly specified in $opt
+ if ( !$error && %new_discount ) {
+ $error = $self->change_discount(%new_discount);
+ }
+
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
$dbh->commit if $oldAutoCommit;
return $self;
@@ -2618,8 +2646,18 @@ sub change {
}
}
- # transfer discounts, if we're not changing pkgpart
- if ( $same_pkgpart ) {
+ if (%new_discount && !$error) {
+
+ # If discounts were explicitly specified in $opt
+ $error = $cust_pkg->change_discount(%new_discount);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "applying discounts: $error";
+ }
+
+ } elsif ( $same_pkgpart ) {
+
+ # transfer discounts, if we're not changing pkgpart
foreach my $old_discount ($self->cust_pkg_discount_active) {
# don't remove the old discount, we may still need to bill that package.
my $new_discount = new FS::cust_pkg_discount {
@@ -2836,6 +2874,20 @@ sub change_later {
$opt->{'locationnum'} = $opt->{'cust_location'}->locationnum;
}
+ # Discounts:
+ # Applies discounts to the newly created future_change package
+ #
+ # If a new discount is the same as the old discount, carry over the
+ # old discount's months_used/end_date fields too
+ #
+ # Legacy behavior:
+ # Legacy behavior was to create the next package with no discount.
+ # This behavior is preserved. Without the discount fields in $opt,
+ # the new package will be created with no discounts.
+
+ # parse discount information from $opt
+ my %new_discount = $self->_parse_new_discounts($opt);
+
if ( $self->change_to_pkgnum ) {
my $change_to = FS::cust_pkg->by_key($self->change_to_pkgnum);
my $new_pkgpart = $opt->{'pkgpart'}
@@ -2873,6 +2925,16 @@ sub change_later {
$change_to->set('start_date', $date);
$error = $self->replace || $change_to->replace;
}
+
+ if ( !$error && exists $opt->{waive_setup} ) {
+ $change_to->set(waive_setup => $opt->{waive_setup} );
+ $error = $change_to->insert();
+ }
+
+ if ( !$error && %new_discount ) {
+ $error = $change_to->change_discount(%new_discount);
+ }
+
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
return $error;
@@ -2906,11 +2968,17 @@ sub change_later {
} );
$error = $new->insert('change' => 1,
'allow_pkgpart' => ($new_pkgpart ? 0 : 1));
+
+ if ( !$error && %new_discount ) {
+ $error = $new->change_discount(%new_discount);
+ }
+
if ( !$error ) {
$self->set('change_to_pkgnum', $new->pkgnum);
$self->set('expire', $date);
$error = $self->replace;
}
+
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
} else {
@@ -2920,6 +2988,66 @@ sub change_later {
$error;
}
+# Helper method reads $opt hashref from change() and change_later()
+# Returns a hash of %new_discount suitable for passing to change_discount()
+sub _parse_new_discounts {
+ my ($self, $opt) = @_;
+
+ croak "Bad parameter list" unless ref $opt;
+
+ my %old_discount =
+ map { $_->setuprecur => $_ }
+ qsearch('cust_pkg_discount', {
+ pkgnum => $self->pkgnum,
+ disabled => '',
+ });
+
+ my %new_discount;
+ for my $type(qw|setup recur|) {
+
+ if (exists $opt->{"${type}_discountnum"}) {
+ $new_discount{$type} = {
+ discountnum => $opt->{"${type}_discountnum"},
+ amount => $opt->{"${type}_discountnum_amount"},
+ percent => $opt->{"${type}_discountnum_percent"},
+ };
+ }
+
+ # Specified discountnum same as old discountnum, carry over addl fields
+ if (
+ exists $opt->{"${type}_discountnum"}
+ && exists $old_discount{$type}
+ && $opt->{"${type}_discountnum"} eq $old_discount{$type}->discountnum
+ ){
+ $new_discount{$type}->{months} = $old_discount{$type}->months;
+ $new_discount{$type}->{end_date} = $old_discount{$type}->end_date;
+ }
+
+ # No new discount specified, carryover old discount
+ # If we wanted to abandon legacy behavior, and always carry old discounts
+ # uncomment this:
+
+ # if (!exists $new_discount{$type} && $old_discount{$type}) {
+ # $new_discount{$type} = {
+ # discountnum => $old_discount{$type}->discountnum,
+ # amount => $old_discount{$type}->amount,
+ # percent => $old_discount{$type}->percent,
+ # months => $old_discount{$type}->months,
+ # end_date => $old_discount{$type}->end_date,
+ # };
+ # }
+ }
+
+ if ($DEBUG) {
+ warn "_parse_new_discounts(), pkgnum: ".$self->pkgnum." \n";
+ warn "Determine \%old_discount, \%new_discount: \n";
+ warn Dumper(\%old_discount);
+ warn Dumper(\%new_discount);
+ }
+
+ %new_discount;
+}
+
=item abort_change
Cancels a future package change scheduled by C<change_later>.
@@ -4731,6 +4859,149 @@ sub insert_discount {
'';
}
+=item change_discount %opt
+
+Method checks if the given values represent a change in either setup or
+discount level. If so, the existing discounts are revoked, the new
+discounts are recorded.
+
+Usage:
+
+$error = change_discount(
+ setup => {
+
+ # -1: Indicates a "custom discount"
+ # 0: Indicates to remove any discount
+ # >0: discountnum to apply
+ discountnum => [-1, 0, discountnum],
+
+ # When discountnum is "-1" to indicate custom discount, include
+ # the additional fields:
+ amount => AMOUNT_DISCOUNT
+ percent => PERCENTAGE_DISCOUNT
+ months => -1,
+ },
+
+ recur => {...}
+);
+
+
+=cut
+
+sub change_discount {
+ my ($self, %opt) = @_;
+ return "change_discount() called with bad \%opt"
+ unless %opt;
+
+ for (keys %opt) {
+ return "change_discount() called with unknown bad key $_"
+ unless $_ eq 'setup' || $_ eq 'recur';
+ }
+
+ my @old_discount =
+ qsearch('cust_pkg_discount',{
+ pkgnum => $self->pkgnum,
+ disabled => '',
+ });
+
+ if ($DEBUG) {
+ warn "change_discount() pkgnum: ".$self->pkgnum." \n";
+ warn "change_discount() \%opt: \n";
+ warn Dumper(\%opt);
+ }
+
+ my @to_be_disabled;
+
+ for my $type (qw|setup recur|) {
+ next unless ref $opt{$type};
+ my %change = %{$opt{$type}};
+
+ return "change_discount() called with bad \$opt($type)"
+ unless $change{discountnum} =~ /^-?\d+$/;
+
+ if ($change{discountnum} eq 0) {
+ # Removing old discount
+
+ delete $opt{$type};
+ push @to_be_disabled, grep {$_->setuprecur eq $type} @old_discount;
+ } else {
+
+ if (
+ grep {
+ $_->discountnum eq $change{discountnum}
+ && $_->setuprecur eq $type
+ } @old_discount
+ ){
+ # Duplicate, disregard this entry
+ delete $opt{$type};
+ next;
+ } else {
+ # Mark any discounts we're replacing
+ push @to_be_disabled, grep{ $_->setuprecur eq $type} @old_discount;
+ }
+
+ }
+ }
+
+
+ # If we still have changes queued, pass them to insert_discount()
+ # by setting values into object fields
+ for my $type (keys %opt) {
+ $self->set("${type}_discountnum", $opt{$type}->{discountnum});
+
+ if ($opt{$type}->{discountnum} eq '-1') {
+ $self->set("${type}_discountnum_${_}", $opt{$type}->{$_})
+ for qw(amount percent months);
+ }
+
+ }
+
+ if ($DEBUG) {
+ warn "change_discount() \% opt before insert \n";
+ warn Dumper \%opt;
+ warn "\@to_be_disabled \n";
+ warn Dumper \@to_be_disabled;
+ }
+
+ # Roll these updates into a transaction
+ my $oldAutoCommit = $FS::UID::AutoCommit;
+ local $FS::UID::AutoCommit = 0;
+ my $dbh = dbh;
+
+ my $error;
+
+ # The "waive setup fee" flag has traditionally been handled by setting
+ # $cust_pkg->waive_setup_fee = Y. This has been appropriately, and separately
+ # handled, and it operates on a differetnt table than cust_pkg_discount,
+ # so the "-2 for waive setup fee" option is not being reimplemented
+ # here. Perhaps this may change later.
+ #
+ # When a setup discount is entered, we still need unset waive_setup
+ if ( $opt{setup} && $opt{setup} > -2 && $self->waive_setup ) {
+ $self->set(waive_setup => '');
+ $error = $self->replace();
+ }
+
+ # Create new discounts
+ $error ||= $self->insert_discount();
+
+ # Disabling old discounts
+ for my $tbd (@to_be_disabled) {
+ unless ($error) {
+ $tbd->set(disabled => 'Y');
+ $error = $tbd->replace();
+ }
+ }
+
+ if ($error) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+
+ $dbh->commit if $oldAutoCommit;
+ return undef;
+}
+
=item set_usage USAGE_VALUE_HASHREF
USAGE_VALUE_HASHREF is a hashref of svc_acct usage columns and the amounts
@@ -5700,4 +5971,3 @@ L<FS::pkg_svc>, schema.html from the base documentation
=cut
1;
-
diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm
index 956cf797c..da53715c6 100644
--- a/FS/FS/part_pkg.pm
+++ b/FS/FS/part_pkg.pm
@@ -232,6 +232,19 @@ sub insert {
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ if ( length($self->classnum) && $self->classnum !~ /^(\d+)$/ ) {
+ my $pkg_class = qsearchs('pkg_class', { 'classname' => $self->classnum } )
+ || new FS::pkg_class { classname => $self->classnum };
+ unless ( $pkg_class->classnum ) {
+ my $error = $pkg_class->insert;
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return $error;
+ }
+ }
+ $self->classnum( $pkg_class->classnum );
+ }
+
warn " inserting part_pkg record" if $DEBUG;
my $error = $self->SUPER::insert( $options{options} );
if ( $error ) {
diff --git a/FS/FS/part_pkg/prorate_Mixin.pm b/FS/FS/part_pkg/prorate_Mixin.pm
index 1a7b1efc0..9e97cc593 100644
--- a/FS/FS/part_pkg/prorate_Mixin.pm
+++ b/FS/FS/part_pkg/prorate_Mixin.pm
@@ -205,7 +205,7 @@ sub prorate_setup {
# For some reason (probably user override), the bill date has been set even
# though the package isn't billing yet. Start billing as though that was the
# start date.
- $sdate = $cust_pkg->bill;
+ $$sdate = $cust_pkg->bill;
$cust_pkg->setup($cust_pkg->bill);
}
# Now figure the start and end of the period that contains the start date.
diff --git a/FS/FS/quotation.pm b/FS/FS/quotation.pm
index 6b0c9145f..a3f061291 100644
--- a/FS/FS/quotation.pm
+++ b/FS/FS/quotation.pm
@@ -716,7 +716,10 @@ sub estimate {
my $cust_main;
if ( $cust_or_prospect->isa('FS::prospect_main') ) {
$cust_main = $cust_or_prospect->convert_cust_main;
- die "$cust_main (simulating customer signup)\n" unless ref $cust_main;
+ unless ( ref($cust_main) ) {
+ $temp_dbh->rollback;
+ die "$cust_main (simulating customer signup)\n";
+ }
$fake_self->set('prospectnum', '');
$fake_self->set('custnum', $cust_main->custnum);
} else {
@@ -726,7 +729,10 @@ sub estimate {
# order packages
local($FS::cust_pkg::disable_start_on_hold) = 1;
$error = $fake_self->order(\%pkgnum_of);
- die "$error (simulating package order)\n" if $error;
+ if ( $error ) {
+ $temp_dbh->rollback;
+ die "$error (simulating package order)\n";
+ }
my @new_pkgs = map { FS::cust_pkg->by_key($_) } values(%pkgnum_of);
@@ -739,7 +745,10 @@ sub estimate {
'no_usage_reset' => 1,
);
$error = $cust_main->bill(%bill_opt);
- die "$error (simulating initial billing)\n" if $error;
+ if ( $error ) {
+ $temp_dbh->rollback;
+ die "$error (simulating initial billing)\n" if $error;
+ }
# pick dates for future bills
my %next_bill_pkgs;
@@ -755,7 +764,10 @@ sub estimate {
$bill_opt{'return_bill'} = $return_bill[$i] = [];
$bill_opt{'pkg_list'} = $next_bill_pkgs{$next_bill};
$error = $cust_main->bill(%bill_opt);
- die "$error (simulating recurring billing cycle $i)\n" if $error;
+ if ( $error ) {
+ $temp_dbh->rollback;
+ die "$error (simulating recurring billing cycle $i)\n";
+ }
$i++;
}
diff --git a/FS/bin/freeside-passwd b/FS/bin/freeside-passwd
new file mode 100755
index 000000000..dbd566e84
--- /dev/null
+++ b/FS/bin/freeside-passwd
@@ -0,0 +1,19 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+my $user = shift or die &usage;
+my $password = shift or die &usage;
+
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw( qsearchs );
+use FS::access_user;
+
+adminsuidsetup $user;
+
+my $access_user = qsearchs('access_user', {'username'=>$user})
+ or die "unknown username $user\n";
+my $error = $access_user->change_password($password);
+die $error if $error;
+
+1;
diff --git a/bin/update-rates b/bin/update-rates
new file mode 100644
index 000000000..b16fc7f78
--- /dev/null
+++ b/bin/update-rates
@@ -0,0 +1,42 @@
+#!/usr/bin/perl
+
+use FS::UID 'adminsuidsetup';
+use FS::Record qw(dbh qsearch qsearchs);
+use FS::tax_class;
+use FS::tax_rate;
+use strict;
+
+adminsuidsetup('ivan');
+$FS::UID::AutoCommit = 0;
+my @location = ( geocode => { op => 'like', value => '24%' } );
+
+# convert TELECOMM RELAY SYSTEMS SURCHARGE:CENTREX LINES
+# to TELECOMM RELAY SYSTEMS SURCHARGE:TELECOMMUNICATIONS
+my $old_taxclassnum = qsearchs('tax_class', { 'taxclass' => '09:35' })->taxclassnum;
+my $new_taxclassnum = qsearchs('tax_class', { 'taxclass' => '09:00' })->taxclassnum;
+my $error;
+
+my @bindings = qsearch('part_pkg_taxrate', {
+ 'taxclassnum' => $old_taxclassnum,
+ @location
+});
+print "remapping ".scalar(@bindings)." tax rate bindings.\n";
+foreach my $part_pkg_taxrate (@bindings) {
+ $part_pkg_taxrate->set('taxclassnum', $new_taxclassnum);
+ $error = $part_pkg_taxrate->replace;
+ die $part_pkg_taxrate->pkgtaxratenum .": $error" if $error;
+}
+
+# change the fee to 0.05.
+my @tax_rates = qsearch('tax_rate', {
+ taxclassnum => $new_taxclassnum,
+ @location
+});
+print "changing rate on ".scalar(@tax_rates)." tax rate definitions.\n";
+foreach my $tax_rate (@tax_rates) {
+ $tax_rate->set('fee', 0.05);
+ my $error = $tax_rate->replace;
+ die $tax_rate->taxnum . ": $error\n" if $error;
+}
+
+dbh->commit;
diff --git a/debian/control b/debian/control
index fc3bae1ce..2391f73f7 100644
--- a/debian/control
+++ b/debian/control
@@ -175,7 +175,7 @@ Description: Self-service portal html/cgi filesfor Freeside billing and trouble
Package: freeside-ng-selfservice
Architecture: all
-Depends: libapache2-mod-php5,php5-xmlrpc,apache2
+Depends: libapache2-mod-php|libapache2-mod-php5,php5-xmlrpc,apache2
Recommends:
Description: Next Generation Self-service portal for Freeside billing and trouble ticketing
Freeside is a web-based billing and trouble ticketing application.
diff --git a/debian/freeside-webui.postinst b/debian/freeside-webui.postinst
new file mode 100644
index 000000000..8dd2baaf5
--- /dev/null
+++ b/debian/freeside-webui.postinst
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+chown -R freeside /usr/local/etc/freeside
+rm -fr /usr/local/etc/freeside/masondata/*
+
+#XXX systemd equivalent (start apache after postgres)
+/sbin/insserv -d
+
+exit 0
+
diff --git a/debian/postinst b/debian/freeside.postinst
index 09f9daefd..3e7dc2502 100644
--- a/debian/postinst
+++ b/debian/freeside.postinst
@@ -1,8 +1,9 @@
#!/bin/sh
-chown -R freeside /usr/local/etc/freeside
+#probably not needed with systemd
/usr/sbin/update-rc.d freeside defaults 23 01
-/sbin/insserv -d
+
+chown -R freeside /usr/local/etc/freeside
rm -fr /usr/local/etc/freeside/masondata/*
exit 0
diff --git a/debian/rules b/debian/rules
index 1eb312ba4..041c895a4 100755
--- a/debian/rules
+++ b/debian/rules
@@ -228,8 +228,9 @@ install-stamp: build-stamp
# Torrus
- install -d ${TORRUS_CONF}
- install -o root -m 755 htetc/freeside-torrus.conf $(TORRUS_CONF)/
+ #in freeside-webui package
+ #install -d ${TORRUS_CONF}
+ #install -o root -m 755 htetc/freeside-torrus.conf $(TORRUS_CONF)/
( cd torrus; \
torrus_user=freeside var_user=freeside var_group=freeside ./configure; \
diff --git a/httemplate/docs/about.html b/httemplate/docs/about.html
index bc3c1b3b9..29a9fe0c4 100644
--- a/httemplate/docs/about.html
+++ b/httemplate/docs/about.html
@@ -28,7 +28,7 @@
% } else {
<FONT SIZE="-1">
% }
-&copy; 2017 Freeside Internet Services, Inc.<BR>
+&copy; 2018 Freeside Internet Services, Inc.<BR>
All rights reserved.<BR>
Licensed under the terms of the<BR>
GNU <b>Affero</b> General Public License.<BR>
@@ -56,7 +56,7 @@ GNU <b>Affero</b> General Public License.<BR>
% unless ( $agentnum ) {
<CENTER>
- <FONT SIZE="-3">"I've heard it too many times to ignore it / Its's something that I'm supposed to be" - K. Frog</FONT>
+ <FONT SIZE="-3">"Then, when all seemed to be lost, a small glimmer of light appeared in the distance. Could it be... back from the great beyond..." - D. 3030</FONT>
</CENTER>
% }
diff --git a/httemplate/edit/access_user.html b/httemplate/edit/access_user.html
index 9d2585366..f40575c0b 100644
--- a/httemplate/edit/access_user.html
+++ b/httemplate/edit/access_user.html
@@ -33,8 +33,10 @@
sub {
my $access_user = shift;
- '<BR>Employee Groups<BR>'.
- ntable("#cccccc",2).
+ '<BR>'.
+ '<FONT CLASS="fsinnerbox-title">Employee Groups</FONT>'.
+ '<BR>'.
+ '<TABLE CLASS="fsinnerbox">'.
'<TR><TD>'.
include( '/elements/checkboxes-table.html',
'source_obj' => $access_user,
@@ -49,6 +51,7 @@
},
'onsubmit' => 'check_user_custnum_search',
'html_foot' => $check_user_custnum_search,
+ 'html_table_class' => 'fsinnerbox',
)
%>
<%init>
diff --git a/httemplate/edit/process/access_user.html b/httemplate/edit/process/access_user.html
index 54d2b0348..fcd210f83 100644
--- a/httemplate/edit/process/access_user.html
+++ b/httemplate/edit/process/access_user.html
@@ -11,7 +11,7 @@
'target_table' => 'access_group',
},
'precheck_callback' => \&precheck_callback,
- 'post_new_object_callback' => \&post_new_object_callback,
+ #'post_new_object_callback' => \&post_new_object_callback,
'noerror_callback' => \&noerror_callback,
)
%>
@@ -38,20 +38,24 @@ sub precheck_callback {
return '';
}
-sub post_new_object_callback {
+#sub post_new_object_callback {
+# my( $cgi, $access_user ) = @_;
+#
+# if ( length($cgi->param('_password')) ) {
+# my $password = scalar($cgi->param('_password'));
+# my $error = $access_user->is_password_allowed($password);
+# #XXX and then bubble the error back up to the UI
+# }
+#}
+
+sub noerror_callback {
my( $cgi, $access_user ) = @_;
if ( length($cgi->param('_password')) ) {
my $password = scalar($cgi->param('_password'));
- my $error = $access_user->is_password_allowed($password)
- || $access_user->change_password($password);
+ $access_user->change_password($password);
}
-}
-
-sub noerror_callback {
- my( $cgi, $access_user ) = @_;
-
#handle installer checkbox
my @sched_item = $access_user->sched_item;
my $sched_item = $sched_item[0];
diff --git a/httemplate/edit/process/change-cust_pkg.html b/httemplate/edit/process/change-cust_pkg.html
index 7fcc1da07..02b01f8de 100644
--- a/httemplate/edit/process/change-cust_pkg.html
+++ b/httemplate/edit/process/change-cust_pkg.html
@@ -40,19 +40,54 @@ if ( $cgi->param('locationnum') == -1 ) {
$change{'cust_location'} = $cust_location;
}
+my $error;
+
+# Discounts:
+# setup_discountnum and change_discountnum may contain one of the following:
+# - "-1" Represents the 'other' option. Results in a new entry to the
+# discount table.
+# - "-2" Represents the "waive setup fee" option. Sets cust_pkg.waive_setup = Y
+# - int Represents the id for a discount row: discount.discountnum
+# my %discount;
+# $change{waive_setup} = '';
+# for my $type (qw|setup recur|) {
+# my $dnum = $cgi->param("${type}_discountnum");
+
+# if ($dnum eq '-2' && $type eq 'setup') {
+# $change{waive_setup} = 'Y';
+# } elsif ($val =~ /^-?\d+$/) {
+# $discount{$type} = {discountnum => $dnum};
+# if ($dnum eq '-1') {
+# $discount{$type}->{amount} = $cgi->param("${type}_discountnum_amount");
+# $discount{$type}->{percent} = $cgi->param("${type}_discountnum_percent");
+# }
+# } else {
+# # Shouldn't happen without funny business
+# $error = "Bad value ${type}_discountnum ($val)";
+# }
+# }
+
+
$change{waive_setup} = '';
+for my $type (qw|setup_discountnum recur_discountnum|) {
+ my $dnum = $cgi->param($type);
-if ( $cgi->param('setup_discountnum') =~ /^(-?\d+)$/ ) {
- if ( $1 == -2 ) {
+ if ($dnum eq '-2' && $type eq 'setup_discountnum') {
+ # Waive Discount
$change{waive_setup} = 'Y';
+ } elsif ($dnum =~ /^-?\d+$/) {
+ # Set discountnum
+ $change{$type} = $dnum;
+ $change{"${type}_amount"} = $cgi->param("${type}_amount");
+ $change{"${type}_percent"} = $cgi->param("${type}_percent");
+ } elsif ($dnum eq '') {
+ # Set discount as no discount
+ $change{"${type}"} = 0;
} else {
- $change{setup_discountnum} = $1;
- $change{setup_discountnum_amount} = $cgi->param('setup_discountnum_amount');
- $change{setup_discountnum_percent} = $cgi->param('setup_discountnum_percent');
+ $error = "Bad value ${type}_discountnum ($dnum)";
}
}
-my $error;
my $now = time;
if (defined($cgi->param('contract_end'))) {
$change{'contract_end'} = parse_datetime($cgi->param('contract_end'));
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 9b8b2cd1e..eb065b668 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -518,8 +518,9 @@ tie my %tools_importing, 'Tie::IxHash',
'Customers' => [ $fsurl.'misc/cust_main-import.cgi', '' ],
'Package definitions' => [ $fsurl.'misc/part_pkg-import.html', '' ],
'Customer packages' => [ $fsurl.'misc/cust_pkg-import.html', '' ],
+# 'Customer broadband services' => [ $fsurl.'misc/svc_broadband-import.html', '' ],
'Customer notes' => [ $fsurl.'misc/cust_main_note-import.html', '' ],
- 'Customer Contacts' => [ $fsurl.'misc/contact-import.cgi', '' ],
+ 'Customer contacts' => [ $fsurl.'misc/contact-import.cgi', '' ],
'One-time charges' => [ $fsurl.'misc/cust_main-import_charges.cgi', '' ],
'Payments' => [ $fsurl.'misc/cust_pay-import.cgi', '' ],
'Credits' => [ $fsurl.'misc/cust_credit-import.html', '' ],
diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi
index 243da9308..2470ee135 100755
--- a/httemplate/misc/change_pkg.cgi
+++ b/httemplate/misc/change_pkg.cgi
@@ -90,7 +90,13 @@
% if ( $discount_cust_pkg || $waive_setup_fee ) {
<FONT CLASS="fsinnerbox-title"><% mt('Discounting') |h %></FONT>
<TABLE CLASS="fsinnerbox">
- <& /elements/tr-select-pkg-discount.html, disable_recur => 1, &>
+ <& /elements/tr-select-pkg-discount.html,
+ curr_value_setup => $discount{setup},
+ curr_value_recur => $discount{recur},
+ disable_setup => 0,
+ disable_recur => 0,
+ disable_waive_setup => 0
+ &>
</TABLE><BR>
% }
@@ -168,4 +174,14 @@ if ( $cust_pkg->change_to_pkgnum ) {
}
$title = "Edit Scheduled Package Change";
}
+
+# Get current values of discounts for selectboxes
+my %discount = (setup => undef, recur => undef);
+$discount{$_->setuprecur} = $_->discountnum
+ for qsearch('cust_pkg_discount', {
+ pkgnum => $cust_pkg->pkgnum,
+ disabled => '',
+ });
+$discount{setup} = '-2' if $cust_pkg->waive_setup;
+
</%init>
diff --git a/httemplate/misc/process/void-cust_bill.html b/httemplate/misc/process/void-cust_bill.html
index 32a2fc591..49dda185e 100755
--- a/httemplate/misc/process/void-cust_bill.html
+++ b/httemplate/misc/process/void-cust_bill.html
@@ -19,8 +19,8 @@ my $invnum = $1;
my $cust_bill = qsearchs('cust_bill',{'invnum'=>$invnum});
-my $custnum = $cust_bill->custnum;
-
-my $error = $cust_bill->void( scalar($cgi->param('reason')) );
+my $error = $cust_bill->void( scalar($cgi->param('reason')),
+ scalar($cgi->param('reprocess_cdrs')),
+ );
</%init>
diff --git a/httemplate/misc/void-cust_bill.html b/httemplate/misc/void-cust_bill.html
index e4e4705d7..e5fbdc6d5 100755
--- a/httemplate/misc/void-cust_bill.html
+++ b/httemplate/misc/void-cust_bill.html
@@ -18,6 +18,14 @@
'cgi' => $cgi
&>
+% if ( $cust_bill->has_call_details ) {
+ <& /elements/tr-checkbox.html,
+ label => 'Reprocess CDRs',
+ field => 'reprocess_cdrs',
+ value => '1',
+ &>
+% }
+
</TABLE>
<BR>
diff --git a/httemplate/view/prospect_main.html b/httemplate/view/prospect_main.html
index 2fde797c6..f4dd4146f 100644
--- a/httemplate/view/prospect_main.html
+++ b/httemplate/view/prospect_main.html
@@ -108,8 +108,7 @@ my $prospect_main = qsearchs( {
});
die "Prospect not found!" unless $prospect_main;
-my $title = encode_entities($prospect_main->name);
-$title = mt("Prospect"). ": $title";
+my $title = mt("Prospect"). ': '. $prospect_main->name;
$title .= ' ('.mt('DISABLED').')'
if $prospect_main->disabled;