[freeside-commits] branch FREESIDE_4_BRANCH updated. dd9dd7a913cd8da4d97b1c72522e016562a98459
Mark Wells
mark at 420.am
Thu Nov 5 16:08:45 PST 2015
The branch, FREESIDE_4_BRANCH has been updated
via dd9dd7a913cd8da4d97b1c72522e016562a98459 (commit)
from 28816258c25afc6c64101b2ac31cb9dec7cb42d4 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit dd9dd7a913cd8da4d97b1c72522e016562a98459
Author: Mark Wells <mark at freeside.biz>
Date: Thu Nov 5 16:06:56 2015 -0800
Add proper reasons and reason types for payment and invoice voids.
Contributed by Irina Todeva <itodeva at hostgator.com>
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 4fda5eb..0e5efe9 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -735,8 +735,9 @@ sub tables_hashref {
#void fields
'void_date', @date_type, '', '',
- 'reason', 'varchar', 'NULL', $char_d, '', '',
- 'void_usernum', 'int', 'NULL', '', '', '',
+ 'reason', 'varchar', 'NULL', $char_d, '', '',
+ 'reasonnum', 'int', 'NULL', '', '', '',
+ 'void_usernum', 'int', 'NULL', '', '', '',
],
'primary_key' => 'invnum',
'unique' => [ [ 'custnum', 'agent_invid' ] ], #agentnum? huh
@@ -750,6 +751,9 @@ sub tables_hashref {
{ columns => [ 'statementnum' ],
table => 'cust_statement', #_void? both?
},
+ { columns => [ 'reasonnum' ],
+ table => 'reason',
+ },
{ columns => [ 'void_usernum' ],
table => 'access_user',
references => [ 'usernum' ],
@@ -1197,8 +1201,9 @@ sub tables_hashref {
'feepart', 'int', 'NULL', '', '', '',
#void fields
'void_date', @date_type, '', '',
- 'reason', 'varchar', 'NULL', $char_d, '', '',
- 'void_usernum', 'int', 'NULL', '', '', '',
+ 'reason', 'varchar', 'NULL', $char_d, '', '',
+ 'reasonnum', 'int', 'NULL', '', '', '',
+ 'void_usernum', 'int', 'NULL', '', '', '',
],
'primary_key' => 'billpkgnum',
'unique' => [],
@@ -1209,6 +1214,9 @@ sub tables_hashref {
{ columns => [ 'invnum' ],
table => 'cust_bill_void',
},
+ { columns => [ 'reasonnum' ],
+ table => 'reason',
+ },
#pkgnum 0 and -1 are used for special things
#{ columns => [ 'pkgnum' ],
# table => 'cust_pkg',
@@ -2505,6 +2513,7 @@ sub tables_hashref {
#void fields
'void_date', @date_type, '', '',
'reason', 'varchar', 'NULL', $char_d, '', '',
+ 'reasonnum', 'int', 'NULL', '', '', '',
'void_usernum', 'int', 'NULL', '', '', '',
],
'primary_key' => 'paynum',
@@ -2526,6 +2535,9 @@ sub tables_hashref {
{ columns => [ 'gatewaynum' ],
table => 'payment_gateway',
},
+ { columns => [ 'reasonnum' ],
+ table => 'reason',
+ },
{ columns => [ 'void_usernum' ],
table => 'access_user',
references => [ 'usernum' ],
diff --git a/FS/FS/Upgrade.pm b/FS/FS/Upgrade.pm
index ffc04ba..342f7bf 100644
--- a/FS/FS/Upgrade.pm
+++ b/FS/FS/Upgrade.pm
@@ -344,6 +344,11 @@ sub upgrade_data {
#customer credits
'cust_credit' => [],
+ # reason / void_reason migration to reasonnum / void_reasonnum
+ 'cust_credit_void' => [],
+ 'cust_bill_void' => [],
+ 'cust_bill_pkg_void' => [],
+
#duplicate history records
'h_cust_svc' => [],
diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm
index 085a9f1..ccf141b 100644
--- a/FS/FS/cust_bill.pm
+++ b/FS/FS/cust_bill.pm
@@ -37,6 +37,8 @@ use FS::cust_bill_pay_pkg;
use FS::cust_credit_bill_pkg;
use FS::discount_plan;
use FS::cust_bill_void;
+use FS::reason;
+use FS::reason_type;
use FS::L10N;
$DEBUG = 0;
@@ -212,7 +214,7 @@ sub insert {
}
-=item void
+=item void [ REASON ]
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
@@ -224,6 +226,14 @@ sub void {
my $self = shift;
my $reason = scalar(@_) ? shift : '';
+ unless (ref($reason) || !$reason) {
+ $reason = FS::reason->new_or_existing(
+ 'class' => 'X',
+ 'type' => 'Void invoice',
+ 'reason' => $reason
+ );
+ }
+
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
@@ -238,7 +248,7 @@ sub void {
my $cust_bill_void = new FS::cust_bill_void ( {
map { $_ => $self->get($_) } $self->fields
} );
- $cust_bill_void->reason($reason);
+ $cust_bill_void->reasonnum($reason->reasonnum) if $reason;
my $error = $cust_bill_void->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
index 5861ee4..aea776a 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -26,6 +26,8 @@ use FS::cust_bill_pkg_tax_location_void;
use FS::cust_bill_pkg_tax_rate_location_void;
use FS::cust_tax_exempt_pkg_void;
use FS::cust_bill_pkg_fee_void;
+use FS::reason;
+use FS::reason_type;
use FS::Cursor;
@@ -322,7 +324,7 @@ sub insert {
}
-=item void
+=item void [ REASON ]
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).
@@ -333,6 +335,14 @@ sub void {
my $self = shift;
my $reason = scalar(@_) ? shift : '';
+ unless (ref($reason) || !$reason) {
+ $reason = FS::reason->new_or_existing(
+ 'class' => 'X',
+ 'type' => 'Void invoice',
+ 'reason' => $reason
+ );
+ }
+
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
local $SIG{QUIT} = 'IGNORE';
@@ -347,7 +357,7 @@ sub void {
my $cust_bill_pkg_void = new FS::cust_bill_pkg_void ( {
map { $_ => $self->get($_) } $self->fields
} );
- $cust_bill_pkg_void->reason($reason);
+ $cust_bill_pkg_void->reasonnum($reason->reasonnum) if $reason;
my $error = $cust_bill_pkg_void->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
diff --git a/FS/FS/cust_bill_pkg_void.pm b/FS/FS/cust_bill_pkg_void.pm
index 080452e..991dd37 100644
--- a/FS/FS/cust_bill_pkg_void.pm
+++ b/FS/FS/cust_bill_pkg_void.pm
@@ -1,7 +1,8 @@
package FS::cust_bill_pkg_void;
-use base qw( FS::TemplateItem_Mixin FS::Record );
+use base qw( FS::TemplateItem_Mixin FS::reason_Mixin FS::Record );
use strict;
+use vars qw( $me $DEBUG );
use FS::Record qw( qsearch qsearchs dbh fields );
use FS::cust_bill_void;
use FS::cust_bill_pkg_detail;
@@ -13,6 +14,9 @@ use FS::cust_bill_pkg_tax_location;
use FS::cust_bill_pkg_tax_rate_location;
use FS::cust_tax_exempt_pkg;
+$me = '[ FS::cust_bill_pkg_void ]';
+$DEBUG = 0;
+
=head1 NAME
FS::cust_bill_pkg_void - Object methods for cust_bill_pkg_void records
@@ -104,6 +108,13 @@ unitrecur
hidden
+=item reason
+
+freeform string (deprecated)
+
+=item reasonnum
+
+reason for voiding the payment (see L<FS::reson>)
=back
@@ -134,6 +145,10 @@ sub discount_table { 'cust_bill_pkg_discount_void'; }
Adds this record to the database. If there is an error, returns the error,
otherwise returns false.
+=item reason
+
+Returns the text of the associated void reason (see L<FS::reason>) for this.
+
=item unvoid
"Un-void"s this line item: Deletes the voided line item from the database and
@@ -242,6 +257,8 @@ sub check {
|| $self->ut_moneyn('unitrecur')
|| $self->ut_enum('hidden', [ '', 'Y' ])
|| $self->ut_numbern('feepart')
+ || $self->ut_textn('reason')
+ || $self->ut_foreign_keyn('reasonnum', 'reason', 'reasonnum')
;
return $error if $error;
@@ -266,6 +283,18 @@ sub cust_bill_pkg_fee {
qsearch( 'cust_bill_pkg_fee_void', { 'billpkgnum' => $self->billpkgnum } );
}
+
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+
+ warn "$me upgrading $class\n" if $DEBUG;
+
+ $class->_upgrade_reasonnum(%opts);
+}
+
=back
=head1 BUGS
diff --git a/FS/FS/cust_bill_void.pm b/FS/FS/cust_bill_void.pm
index f3dba90..50f69c9 100644
--- a/FS/FS/cust_bill_void.pm
+++ b/FS/FS/cust_bill_void.pm
@@ -1,13 +1,18 @@
package FS::cust_bill_void;
-use base qw( FS::Template_Mixin FS::cust_main_Mixin FS::otaker_Mixin FS::Record );
+use base qw( FS::Template_Mixin FS::cust_main_Mixin FS::otaker_Mixin
+ FS::reason_Mixin FS::Record );
use strict;
+use vars qw( $me $DEBUG );
use FS::Record qw( qsearch qsearchs dbh fields );
use FS::cust_statement;
use FS::access_user;
use FS::cust_bill_pkg_void;
use FS::cust_bill;
+$me = '[ FS::cust_bill_void ]';
+$DEBUG = 0;
+
=head1 NAME
FS::cust_bill_void - Object methods for cust_bill_void records
@@ -82,9 +87,13 @@ promised_date
void_date
-=item reason
+=item reason
+
+freeform string (deprecated)
-reason
+=item reasonnum
+
+reason for voiding the payment (see L<FS::reson>)
=item void_usernum
@@ -216,6 +225,7 @@ sub check {
|| $self->ut_numbern('void_date')
|| $self->ut_textn('reason')
|| $self->ut_numbern('void_usernum')
+ || $self->ut_foreign_keyn('reasonnum', 'reason', 'reasonnum')
;
return $error if $error;
@@ -259,6 +269,10 @@ sub void_access_user {
=item cust_bill_pkg
+=item reason
+
+Returns the text of the associated void reason (see L<FS::reason>) for this.
+
=cut
sub cust_bill_pkg { #actually cust_bill_pkg_void objects
@@ -339,6 +353,17 @@ sub search_sql_where {
sub enable_previous { 0 }
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data { # class method
+ my ($class, %opts) = @_;
+
+ warn "$me upgrading $class\n" if $DEBUG;
+
+ $class->_upgrade_reasonnum(%opts);
+}
+
=back
=head1 BUGS
diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm
index e442bdd..836cf36 100644
--- a/FS/FS/cust_credit.pm
+++ b/FS/FS/cust_credit.pm
@@ -407,7 +407,7 @@ sub void {
my $cust_credit_void = new FS::cust_credit_void ( {
map { $_ => $self->get($_) } $self->fields
} );
- $cust_credit_void->set('void_reasonnum', $reason->reasonnum);
+ $cust_credit_void->set('void_reasonnum', $reason->reasonnum) if $reason;
my $error = $cust_credit_void->insert;
if ( $error ) {
$dbh->rollback if $oldAutoCommit;
diff --git a/FS/FS/cust_credit_void.pm b/FS/FS/cust_credit_void.pm
index 9c92068..60beaa6 100644
--- a/FS/FS/cust_credit_void.pm
+++ b/FS/FS/cust_credit_void.pm
@@ -2,12 +2,16 @@ package FS::cust_credit_void;
use base qw( FS::otaker_Mixin FS::cust_main_Mixin FS::reason_Mixin FS::Record );
use strict;
+use vars qw( $me $DEBUG );
use FS::Record qw(qsearchs); # qsearch qsearchs);
use FS::CurrentUser;
use FS::access_user;
use FS::cust_credit;
use FS::UID qw( dbh );
+$me = '[ FS::cust_credit_void ]';
+$DEBUG = 0;
+
=head1 NAME
FS::cust_credit_void - Object methods for cust_credit_void objects
@@ -190,6 +194,17 @@ sub void_reason {
return $reason_text;
}
+# _upgrade_data
+#
+# Used by FS::Upgrade to migrate to a new database.
+sub _upgrade_data { # class method
+ my ( $class, %opts ) = @_;
+
+ warn "$me upgrading $class\n" if $DEBUG;
+
+ $class->_upgrade_reasonnum(%opts);
+}
+
=back
=head1 BUGS
diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
index e8aa3c7..e34e3b2 100644
--- a/FS/FS/cust_pay.pm
+++ b/FS/FS/cust_pay.pm
@@ -2,9 +2,9 @@ package FS::cust_pay;
use strict;
use base qw( FS::otaker_Mixin FS::payinfo_transaction_Mixin FS::cust_main_Mixin
- FS::Record );
+ FS::reason_Mixin FS::Record);
use vars qw( $DEBUG $me $conf @encrypted_fields
- $unsuspendauto $ignore_noapply
+ $unsuspendauto $ignore_noapply
);
use Date::Format;
use Business::CreditCard;
@@ -24,6 +24,8 @@ use FS::cust_pkg;
use FS::cust_pay_void;
use FS::upgrade_journal;
use FS::Cursor;
+use FS::reason;
+use FS::reason_type;
$DEBUG = 0;
@@ -438,6 +440,15 @@ adds a record of the voided payment to the FS::cust_pay_void table.
sub void {
my $self = shift;
+ my $reason = shift;
+
+ unless (ref($reason) || !$reason) {
+ $reason = FS::reason->new_or_existing(
+ 'class' => 'X',
+ 'type' => 'Void payment',
+ 'reason' => $reason
+ );
+ }
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
@@ -453,7 +464,7 @@ sub void {
my $cust_pay_void = new FS::cust_pay_void ( {
map { $_ => $self->get($_) } $self->fields
} );
- $cust_pay_void->reason(shift) if scalar(@_);
+ $cust_pay_void->reasonnum($reason->reasonnum) if $reason;
my $error = $cust_pay_void->insert;
my $cust_pay_pending =
@@ -1064,6 +1075,8 @@ sub _upgrade_data { #class method
warn "$me upgrading $class\n" if $DEBUG;
+ $class->_upgrade_reasonnum(%opt);
+
local $FS::payinfo_Mixin::ignore_masked_payinfo = 1;
##
diff --git a/FS/FS/cust_pay_void.pm b/FS/FS/cust_pay_void.pm
index b2f777b..72ada25 100644
--- a/FS/FS/cust_pay_void.pm
+++ b/FS/FS/cust_pay_void.pm
@@ -1,6 +1,6 @@
package FS::cust_pay_void;
use base qw( FS::otaker_Mixin FS::payinfo_transaction_Mixin FS::cust_main_Mixin
- FS::Record );
+ FS::reason_Mixin FS::Record );
use strict;
use vars qw( @encrypted_fields $otaker_upgrade_kludge );
@@ -88,7 +88,9 @@ Desired pkgnum when using experimental package balances.
=item void_date
-=item reason
+=item reason - a freeform string (deprecated)
+
+=item reasonnum - Reason for voiding the payment (see L<FS::reson>)
=back
@@ -189,6 +191,7 @@ sub check {
|| $self->ut_numbern('void_date')
|| $self->ut_textn('reason')
|| $self->payinfo_check
+ || $self->ut_foreign_keyn('reasonnum', 'reason', 'reasonnum')
;
return $error if $error;
@@ -221,10 +224,20 @@ sub void_access_user {
qsearchs('access_user', { 'usernum' => $self->void_usernum } );
}
+=item reason
+
+Returns the text of the associated void reason (see L<FS::reason>) for this.
+
+=cut
+
# Used by FS::Upgrade to migrate to a new database.
sub _upgrade_data { # class method
my ($class, %opts) = @_;
+ local $FS::payinfo_Mixin::ignore_masked_payinfo = 1;
+
+ $class->_upgrade_reasonnum(%opts);
+
my $sql = "SELECT usernum FROM access_user WHERE username = ( SELECT history_user FROM h_cust_pay_void WHERE paynum = ? AND history_action = 'insert' ORDER BY history_date LIMIT 1 ) ";
my $sth = dbh->prepare($sql) or die dbh->errstr;
diff --git a/FS/FS/reason_Mixin.pm b/FS/FS/reason_Mixin.pm
index a397541..9c436ab 100644
--- a/FS/FS/reason_Mixin.pm
+++ b/FS/FS/reason_Mixin.pm
@@ -6,18 +6,22 @@ use FS::Record qw( qsearch qsearchs dbdef );
use FS::access_user;
use FS::UID qw( dbh );
use FS::reason;
+use FS::reason_type;
our $DEBUG = 0;
our $me = '[FS::reason_Mixin]';
=item reason
-Returns the text of the associated reason (see L<FS::reason>) for this credit.
+Returns the text of the associated reason (see L<FS::reason>) for this credit /
+voided payment / voided invoice. This can no longer be used to set the
+(deprecated) free-text "reason" field; see L<FS::reason/new_or_existing>.
=cut
sub reason {
- my ($self, $value, %options) = @_;
+ my $self = shift;
+
my $reason_text;
if ( $self->reasonnum ) {
my $reason = FS::reason->by_key($self->reasonnum);
@@ -32,65 +36,136 @@ sub reason {
return $reason_text;
}
-# it was a mistake to allow setting the reason this way; use
-# FS::reason->new_or_existing
-
# Used by FS::Upgrade to migrate reason text fields to reasonnum.
-sub _upgrade_reasonnum { # class method
- my $class = shift;
- my $table = $class->table;
-
- if (defined dbdef->table($table)->column('reason')) {
-
- warn "$me Checking for unmigrated reasons\n" if $DEBUG;
-
- my @cust_refunds = qsearch({ 'table' => $table,
- 'hashref' => {},
- 'extra_sql' => 'WHERE reason IS NOT NULL',
- });
-
- if (scalar(grep { $_->getfield('reason') =~ /\S/ } @cust_refunds)) {
- warn "$me Found unmigrated reasons\n" if $DEBUG;
- my $hashref = { 'class' => 'F', 'type' => 'Legacy' };
- my $reason_type = qsearchs( 'reason_type', $hashref );
- unless ($reason_type) {
- $reason_type = new FS::reason_type( $hashref );
- my $error = $reason_type->insert();
- die "$class had error inserting FS::reason_type into database: $error\n"
- if $error;
- }
-
- $hashref = { 'reason_type' => $reason_type->typenum,
- 'reason' => '(none)'
- };
- my $noreason = qsearchs( 'reason', $hashref );
- unless ($noreason) {
- $hashref->{'disabled'} = 'Y';
- $noreason = new FS::reason( $hashref );
- my $error = $noreason->insert();
- die "can't insert legacy reason '(none)' into database: $error\n"
- if $error;
- }
-
- foreach my $cust_refund ( @cust_refunds ) {
- my $reason = $cust_refund->getfield('reason');
- warn "Contemplating reason $reason\n" if $DEBUG > 1;
- if ($reason =~ /\S/) {
- $cust_refund->reason($reason, 'reason_type' => $reason_type->typenum)
- or die "can't insert legacy reason $reason into database\n";
- }else{
- $cust_refund->reasonnum($noreason->reasonnum);
+# Note that any new tables that get reasonnum fields do NOT need to be
+# added here unless they have previously had a free-text "reason" field.
+
+sub _upgrade_reasonnum { # class method
+ my $class = shift;
+ my $table = $class->table;
+
+ my $reason_class;
+ if ( $table =~ /^cust_bill/ ) { # also includes cust_bill_pkg
+ $reason_class = 'I';
+ } elsif ( $table =~ /^cust_pay/ ) {
+ $reason_class = 'P';
+ } elsif ( $table eq 'cust_refund' ) {
+ $reason_class = 'F';
+ } elsif ( $table =~ /^cust_credit/ ) {
+ $reason_class = 'R';
+ } else {
+ die "don't know the reason class to use for upgrading $table";
+ }
+
+ for my $fieldname (qw(reason void_reason)) {
+
+ if ( $table =~ /^cust_credit/ and $fieldname eq 'void_reason' ) {
+ $reason_class = 'X';
+ }
+
+ if ( defined dbdef->table($table)->column($fieldname)
+ && defined dbdef->table($table)->column( $fieldname . 'num' ) )
+ {
+
+ warn "$me Checking for unmigrated reasons\n" if $DEBUG;
+
+ my @legacy_reason_records = qsearch(
+ {
+ 'table' => $table,
+ 'hashref' => {},
+ 'extra_sql' => 'WHERE ' . $fieldname . ' IS NOT NULL',
+ }
+ );
+
+ if ( @legacy_reason_records ) {
+
+ warn "$me Found unmigrated reasons\n" if $DEBUG;
+
+ my $reason_type =
+ $class->_upgrade_get_legacy_reason_type( $reason_class );
+ # XXX "noreason" does not actually work, because we limited to
+ # "reason is not null" above. Records where the reason string
+ # is null will end up with a reasonnum of null also.
+ my $noreason = $class->_upgrade_get_no_reason( $reason_type );
+
+ foreach my $record_to_upgrade (@legacy_reason_records) {
+ my $reason = $record_to_upgrade->getfield($fieldname);
+ warn "Contemplating reason $reason\n" if $DEBUG > 1;
+ if ( $reason =~ /\S/ ) {
+ my $reason =
+ $class->_upgrade_get_reason( $reason, $reason_type );
+ $record_to_upgrade->set( $fieldname . 'num',
+ $reason->reasonnum );
+ }
+ else {
+ $record_to_upgrade->set( $fieldname . 'num',
+ $noreason->reasonnum );
+ }
+
+ $record_to_upgrade->setfield( $fieldname, '' );
+ my $error = $record_to_upgrade->replace;
+
+ my $primary_key = $record_to_upgrade->primary_key;
+ warn "*** WARNING: error replacing $fieldname in $class "
+ . $record_to_upgrade->get($primary_key)
+ . ": $error ***\n"
+ if $error;
+ }
+ }
}
+ }
+}
- $cust_refund->setfield('reason', '');
- my $error = $cust_refund->replace;
+# internal methods for upgrade
- warn "*** WARNING: error replacing reason in $class ".
- $cust_refund->refundnum. ": $error ***\n"
- if $error;
- }
+# _upgrade_get_legacy_reason_type is class method supposed to be used only
+# within the reason_Mixin class which will either find or create a reason_type
+sub _upgrade_get_legacy_reason_type {
+
+ my $class = shift;
+ my $reason_class = shift;
+ my $reason_type_params = { 'class' => $reason_class, 'type' => 'Legacy' };
+ my $reason_type = qsearchs( 'reason_type', $reason_type_params );
+ unless ($reason_type) {
+ $reason_type = new FS::reason_type($reason_type_params);
+ my $error = $reason_type->insert();
+ die "$class had error inserting FS::reason_type into database: $error\n"
+ if $error;
}
- }
+ return $reason_type;
+}
+
+# _upgrade_get_no_reason is class method supposed to be used only within the
+# reason_Mixin class which will either find or create a default (no reason)
+# reason
+sub _upgrade_get_no_reason {
+
+ my $class = shift;
+ my $reason_type = shift;
+ return $class->_upgrade_get_reason( '(none)', $reason_type );
+}
+
+# _upgrade_get_reason is class method supposed to be used only within the
+# reason_Mixin class which will either find or create a reason
+sub _upgrade_get_reason {
+
+ my $class = shift;
+ my $reason_text = shift;
+ my $reason_type = shift;
+
+ my $reason_params = {
+ 'reason_type' => $reason_type->typenum,
+ 'reason' => $reason_text
+ };
+ my $reason = qsearchs( 'reason', $reason_params );
+ unless ($reason) {
+ $reason_params->{'disabled'} = 'Y';
+ $reason = new FS::reason($reason_params);
+ my $error = $reason->insert();
+ die "can't insert legacy reason '$reason_text' into database: $error\n"
+ if $error;
+ }
+ return $reason;
}
1;
diff --git a/FS/FS/reason_type.pm b/FS/FS/reason_type.pm
index 17a7167..1d04986 100644
--- a/FS/FS/reason_type.pm
+++ b/FS/FS/reason_type.pm
@@ -3,15 +3,18 @@ package FS::reason_type;
use strict;
use vars qw( @ISA );
use FS::Record qw( qsearch qsearchs );
+use Tie::IxHash;
@ISA = qw(FS::Record);
-our %class_name = (
+tie our %class_name, 'Tie::IxHash', (
'C' => 'cancel',
'R' => 'credit',
'S' => 'suspend',
'F' => 'refund',
- 'X' => 'void credit',
+ 'X' => 'credit void',
+ 'I' => 'invoice void',
+ 'P' => 'payment void',
);
our %class_purpose = (
@@ -20,6 +23,18 @@ our %class_purpose = (
'S' => 'explain why a customer package was suspended',
'F' => 'explain why a customer was refunded',
'X' => 'explain why a credit was voided',
+ 'I' => 'explain why an invoice was voided',
+ 'P' => 'explain why a payment was voided',
+);
+
+our %class_add_access_right = (
+ 'C' => 'Add on-the-fly cancel reason',
+ 'R' => 'Add on-the-fly credit reason',
+ 'S' => 'Add on-the-fly suspend reason',
+ 'F' => 'Add on-the-fly refund reason',
+ 'X' => 'Add on-the-fly void reason',
+ 'I' => 'Add on-the-fly void reason',
+ 'P' => 'Add on-the-fly void reason',
);
=head1 NAME
@@ -50,7 +65,7 @@ inherits from FS::Record. The following fields are currently supported:
=item typenum - primary key
-=item class - currently 'C', 'R', 'S', 'F' or 'X' for cancel, credit, suspend, refund or void credit
+=item class - one of the keys of %class_name
=item type - name of the type of reason
diff --git a/httemplate/browse/reason_type.html b/httemplate/browse/reason_type.html
index 6b444ba..0cb6e7a 100644
--- a/httemplate/browse/reason_type.html
+++ b/httemplate/browse/reason_type.html
@@ -1,49 +1,62 @@
-<% include( 'elements/browse.html',
- 'title' => ucfirst($classname) . " Reason Types",
- 'menubar' => [ ucfirst($classname) . " reasons" =>
- $p.'browse/reason.html?class=' . $class,
- ],
- 'html_init' => $html_init,
- 'name' => $classname . " reason types",
- 'query' => { 'table' => 'reason_type',
- 'hashref' => {},
- 'extra_sql' => $where_clause .
- 'ORDER BY typenum',
- },
- 'count_query' => $count_query,
- 'header' => [ '#',
- ucfirst($classname) . ' Reason Type',
- ucfirst($classname) . ' Reasons',
- ],
- 'fields' => [ 'typenum',
- 'type',
- $reasons_sub,
- ],
- 'links' => [ $link,
- $link,
- '',
- ],
- )
-%>
+<& elements/browse.html,
+ 'title' => ucwords($classname) . " Reasons",
+ 'html_init' => $html_init,
+ 'name' => $classname . " reason types",
+ 'query' => { 'table' => 'reason_type',
+ 'hashref' => {},
+ 'extra_sql' => $where_clause .
+ 'ORDER BY typenum',
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ ucwords($classname) . ' Reason Type',
+ ucwords($classname) . ' Reasons',
+ ],
+ 'fields' => [ 'typenum',
+ 'type',
+ $reasons_sub,
+ ],
+ 'links' => [ $link,
+ $link,
+ '',
+ ],
+ 'disable_total' => 1,
+&>
<%init>
+sub ucwords {
+ join(' ', map ucfirst($_), split(/ /, shift));
+}
+
die "access denied"
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
$cgi->param('class') =~ /^(\w)$/ or die "illegal class";
my $class=$1;
-my $classname = $FS::reason_type::class_name{$class};
+my $classname = ucfirst($FS::reason_type::class_name{$class});
+
+my $html_init = 'Reasons: ' .
+ include('/elements/menubar.html',
+ map {
+ ucfirst($FS::reason_type::class_name{$_}),
+ $p.'browse/reason_type.html?class=' . $_
+ } keys (%FS::reason_type::class_name)
+ );
-my $html_init = ucfirst($classname) .
- " reason types allow groups of $classname reasons for reporting purposes." .
- qq!<BR><BR><A HREF="${p}edit/reason_type.html?class=$class"><I>Add a ! .
- $classname . " reason type</I></A><BR><BR>";
+$html_init .= '<BR><P>' .
+ $classname . ' reasons ' .
+ $FS::reason_type::class_purpose{$class} .
+ '. Reason types allow reasons to be grouped for reporting purposes.' .
+ qq!<BR><BR><A HREF="${p}edit/reason_type.html?class=$class"><I>! .
+ ($classname =~ /^[aeiou]/i ? 'Add an ' : 'Add a ') .
+ lc($classname) . ' reason type</I></A>'.
+ '</P>';
my $reasons_sub = sub {
my $reason_type = shift;
- [ map {
+ [ ( map {
[
{
'data' => $_->reason,
@@ -53,7 +66,15 @@ my $reasons_sub = sub {
},
];
}
- $reason_type->enabled_reasons,
+ $reason_type->enabled_reasons ),
+ [
+ {
+ 'data' => '(add)',
+ 'align' => 'left',
+ 'link' => $p. "edit/reason.html?class=$class",
+ 'data_style' => 'i',
+ }
+ ]
];
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index ea69331..1af6f97 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -621,10 +621,8 @@ $config_export_svc{'Hardware types'} = [ $fsurl.'browse/hardware_class.html', 'S
if $curuser->access_right('Configuration');
tie my %config_pkg_reason, 'Tie::IxHash',
- 'Cancel reasons' => [ $fsurl.'browse/reason.html?class=C', 'Cancel reasons explain why a service was cancelled.' ],
- 'Cancel reason types' => [ $fsurl.'browse/reason_type.html?class=C', 'Cancel reason types define groups of reasons.' ],
- 'Suspend reasons' => [ $fsurl.'browse/reason.html?class=S', 'Suspend reasons explain why a service was suspended.' ],
- 'Suspend reason types' => [ $fsurl.'browse/reason_type.html?class=S', 'Suspend reason types define groups of reasons.' ],
+ 'Cancel reasons' => [ $fsurl.'browse/reason_type.html?class=C', 'Cancel reasons explain why a service was cancelled.' ],
+ 'Suspend reasons' => [ $fsurl.'browse/reason_type.html?class=S', 'Suspend reasons explain why a service was suspended.' ],
;
tie my %config_pkg, 'Tie::IxHash', ();
@@ -715,12 +713,13 @@ if ( $curuser->access_right('Configuration') ) {
}
$config_billing{'separator4'} = ''; #its a separator!
- $config_billing{'Credit reasons'} = [ $fsurl.'browse/reason.html?class=R', 'Credit reasons explain why a credit was issued.' ];
- $config_billing{'Credit reason types'} = [ $fsurl.'browse/reason_type.html?class=R', 'Credit reason types define groups of reasons.' ];
+ $config_billing{'Credit reasons'} = [ $fsurl.'browse/reason_type.html?class=R', 'Credit reasons explain why a credit was issued.' ];
- $config_billing{'separator5'} = ''; #its a separator!
- $config_billing{'Refund reasons'} = [ $fsurl.'browse/reason.html?class=F', 'Refund reasons explain why a refund was issued.' ];
- $config_billing{'Refund reason types'} = [ $fsurl.'browse/reason_type.html?class=F', 'Refund reason types define groups of reasons.' ];
+ $config_billing{'Refund reasons'} = [ $fsurl.'browse/reason_type.html?class=F', 'Refund reasons explain why a refund was issued.' ];
+
+ $config_billing{'Invoice void reasons'} = [ $fsurl.'browse/reason_type.html?class=I', 'Invoice void reasons explain why an invoice was voided.' ];
+ $config_billing{'Payment void reasons'} = [ $fsurl.'browse/reason_type.html?class=P', 'Payment void reasons explain why a payment was voided.' ];
+ $config_billing{'Credit void reasons'} = [ $fsurl.'browse/reason_type.html?class=X', 'Credit void reasons explain why a credit was voided.' ];
}
#XXX also to be unified
diff --git a/httemplate/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html
index 1258746..93949ba 100755
--- a/httemplate/elements/tr-select-reason.html
+++ b/httemplate/elements/tr-select-reason.html
@@ -6,8 +6,7 @@ Example:
#required
'field' => 'reasonnum',
- 'reason_class' => 'C', # currently 'C', 'R', 'F', 'S' or 'X'
- # for cancel, credit, refund, suspend or void credit
+ 'reason_class' => 'C', # one of those in %FS::reason_type::class_name
#recommended
'cgi' => $cgi, #easiest way for things to be properly "sticky" on errors
@@ -189,20 +188,8 @@ if ( $opt{'cgi'} ) {
my $id = $opt{'id'} || $name;
$id =~ s/\./_/g; # for edit/part_event
-my $add_access_right;
-if ($class eq 'C') {
- $add_access_right = 'Add on-the-fly cancel reason';
-} elsif ($class eq 'S') {
- $add_access_right = 'Add on-the-fly suspend reason';
-} elsif ($class eq 'R') {
- $add_access_right = 'Add on-the-fly credit reason';
-} elsif ($class eq 'F') {
- $add_access_right = 'Add on-the-fly refund reason';
-} elsif ($class eq 'X') {
- $add_access_right = 'Add on-the-fly void credit reason';
-} else {
- die "illegal class: $class";
-}
+my $add_access_right = $FS::reason_type::class_add_access_right{$class}
+ or die "unknown class: $class";
my @reasons = qsearch({
'table' => 'reason',
diff --git a/httemplate/misc/unapply-cust_credit.cgi b/httemplate/misc/unapply-cust_credit.cgi
index ed739ac..aa1a3a9 100755
--- a/httemplate/misc/unapply-cust_credit.cgi
+++ b/httemplate/misc/unapply-cust_credit.cgi
@@ -1,4 +1,4 @@
-<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+<% $cgi->redirect($p. "view/cust_main.cgi?custnum=". $custnum. ";show=payment_history") %>
<%init>
die "access denied"
diff --git a/httemplate/misc/unapply-cust_pay.cgi b/httemplate/misc/unapply-cust_pay.cgi
index b0343d0..34c1ecf 100755
--- a/httemplate/misc/unapply-cust_pay.cgi
+++ b/httemplate/misc/unapply-cust_pay.cgi
@@ -1,4 +1,4 @@
-<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+<% $cgi->redirect($p. "view/cust_main.cgi?custnum=". $custnum. ";show=payment_history") %>
<%init>
die "access denied"
diff --git a/httemplate/misc/unvoid-cust_pay_void.cgi b/httemplate/misc/unvoid-cust_pay_void.cgi
index 4726ee5..84b7879 100755
--- a/httemplate/misc/unvoid-cust_pay_void.cgi
+++ b/httemplate/misc/unvoid-cust_pay_void.cgi
@@ -1,7 +1,7 @@
%if ( $error ) {
% errorpage($error);
%} else {
-<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
+<% $cgi->redirect($p. "view/cust_main.cgi?custnum=". $custnum. ";show=payment_history") %>
%}
<%init>
diff --git a/httemplate/misc/void-cust_bill.html b/httemplate/misc/void-cust_bill.html
old mode 100644
new mode 100755
index 39b0712..e4e4705
--- a/httemplate/misc/void-cust_bill.html
+++ b/httemplate/misc/void-cust_bill.html
@@ -12,10 +12,11 @@
<INPUT TYPE="hidden" NAME="invnum" VALUE="<% $invnum %>">
<% ntable("#cccccc", 2) %>
-<TR>
- <TD ALIGN="right">Reason</TD>
- <TD><INPUT TYPE="text" NAME="reason" VALUE="<% scalar($cgi->param('reason')) |h %>"></TD>
-</TR>
+<& /elements/tr-select-reason.html,
+ 'field' => 'reasonnum',
+ 'reason_class' => 'I',
+ 'cgi' => $cgi
+&>
</TABLE>
diff --git a/httemplate/misc/void-cust_pay.cgi b/httemplate/misc/void-cust_pay.cgi
deleted file mode 100755
index 31b7a62..0000000
--- a/httemplate/misc/void-cust_pay.cgi
+++ /dev/null
@@ -1,26 +0,0 @@
-%if ( $error ) {
-% errorpage($error);
-%} else {
-<% $cgi->redirect($p. "view/cust_main.cgi?". $custnum) %>
-%}
-<%init>
-
-#untaint paynum
-my($query) = $cgi->keywords;
-$query =~ /^(\d+)$/ || die "Illegal paynum";
-my $paynum = $1;
-
-my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum});
-
-my $right = 'Void payments';
-$right = 'Credit card void' if $cust_pay->payby eq 'CARD';
-$right = 'Echeck void' if $cust_pay->payby eq 'CHEK';
-
-die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right($right);
-
-my $custnum = $cust_pay->custnum;
-
-my $error = $cust_pay->void;
-
-</%init>
diff --git a/httemplate/misc/void-cust_pay.html b/httemplate/misc/void-cust_pay.html
new file mode 100755
index 0000000..205d93a
--- /dev/null
+++ b/httemplate/misc/void-cust_pay.html
@@ -0,0 +1,78 @@
+%if ( $success ) {
+<& /elements/header-popup.html, mt("Payment voided") &>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY>
+</HTML>
+%} else {
+<& /elements/header-popup.html, mt('Void payment') &>
+
+<& /elements/error.html &>
+
+<P ALIGN="center"><B><% mt('Void this payment?') |h %></B>
+
+<FORM action="<% ${p} %>misc/void-cust_pay.html">
+<INPUT TYPE="hidden" NAME="paynum" VALUE="<% $paynum %>">
+
+<TABLE BGCOLOR="#cccccc" BORDER="0" CELLSPACING="2" STYLE="margin-left:auto; margin-right:auto">
+<& /elements/tr-select-reason.html,
+ 'field' => 'reasonnum',
+ 'reason_class' => 'P',
+ 'cgi' => $cgi
+&>
+</TABLE>
+
+<BR>
+<P ALIGN="CENTER">
+<INPUT TYPE="submit" NAME="confirm_void_payment" VALUE="<% mt('Void payment') |h %>">
+
+<INPUT TYPE="BUTTON" VALUE="<% mt("Don't void payment") |h %>" onClick="parent.cClick();">
+
+</FORM>
+</BODY>
+</HTML>
+
+%}
+<%init>
+
+#untaint paynum
+my $paynum = $cgi->param('paynum');
+if ($paynum) {
+ $paynum =~ /^(\d+)$/ || die "Illegal paynum";
+} else {
+ my($query) = $cgi->keywords;
+ $query =~ /^(\d+)/ || die "Illegal paynum";
+ $paynum = $1;
+}
+
+my $cust_pay = qsearchs('cust_pay',{'paynum'=>$paynum}) || die "Payment not found";
+
+my $right = 'Void payments';
+$right = 'Credit card void' if $cust_pay->payby eq 'CARD';
+$right = 'Echeck void' if $cust_pay->payby eq 'CHEK';
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right($right);
+
+my $success = 0;
+if ($cgi->param('confirm_void_payment')) {
+
+ #untaint reasonnum / create new reason
+ my ($reasonnum, $error) = $m->comp('process/elements/reason');
+ if (!$reasonnum) {
+ $error = 'Reason required';
+ } else {
+ my $reason = qsearchs('reason', { 'reasonnum' => $reasonnum })
+ || die "Reason num $reasonnum not found in database";
+ $error = $cust_pay->void($reason) unless $error;
+ }
+
+ if ($error) {
+ $cgi->param('error',$error);
+ } else {
+ $success = 1;
+ }
+}
+
+</%init>
diff --git a/httemplate/view/cust_bill.cgi b/httemplate/view/cust_bill.cgi
index 8884dde..d73edbd 100755
--- a/httemplate/view/cust_bill.cgi
+++ b/httemplate/view/cust_bill.cgi
@@ -9,13 +9,30 @@ function areyousure(href, message) {
}
</SCRIPT>
-% if ( !$cust_bill->closed && $curuser->access_right('Void invoices') ) {
+% if ( !$cust_bill->closed ) { # otherwise allow no changes
+% my $can_delete = $conf->exists('deleteinvoices')
+% && $curuser->access_right('Delete invoices');
+% my $can_void = $curuser->access_right('Void invoices');
+% if ( $can_void ) {
<& /elements/popup_link.html,
'label' => emt('Void this invoice'),
'actionlabel' => emt('Void this invoice'),
'action' => $p.'misc/void-cust_bill.html?invnum='.$invnum,
&>
- <BR><BR>
+% }
+% if ( $can_void and $can_delete ) {
+ |
+% }
+% if ( $can_delete ) {
+ <A href="" onclick="areyousure(\
+ '<%$p%>misc/delete-cust_bill.html?<% $invnum %>',\
+ <% mt('Are you sure you want to delete this invoice?') |js_string %>)"\
+ TITLE = "<% mt('Delete this invoice from the database completely') |h %>">\
+ <% emt('Delete this invoice') |h %></A>
+% }
+% if ( $can_void or $can_delete ) {
+ <BR><BR>
+% }
% }
% if ( $cust_bill->owed > 0
diff --git a/httemplate/view/cust_main/payment_history/payment.html b/httemplate/view/cust_main/payment_history/payment.html
index d72e34b..6c93f7b 100644
--- a/httemplate/view/cust_main/payment_history/payment.html
+++ b/httemplate/view/cust_main/payment_history/payment.html
@@ -169,8 +169,9 @@ if ( $cust_pay->closed !~ /^Y/i
&& scalar(@refund_right)
) {
my $refundtitle = ($cust_pay->payby =~ /^(CARD|CHEK)$/)
- ? emt('Send a refund for this payment to the payment gateway')
- : emt('Record a refund for this payment');
+ ? emt('Send a refund for this payment to the payment gateway')
+ : emt('Record a refund for this payment');
+
$refund = qq! (<A HREF="${p}edit/cust_refund.cgi?payby=$1;!.
qq!paynum=!. $cust_pay->paynum. '"'.
qq! TITLE="! . $refundtitle
@@ -178,14 +179,17 @@ if ( $cust_pay->closed !~ /^Y/i
}
my $void = '';
-my $voidmsg = $cust_pay->payby =~ /^(CARD|CHEK)$/
+# note: "TOKN" is not yet supported in stock freeside
+my $voidmsg = $cust_pay->payby =~ /^(CARD|CHEK|TOKN)$/
? ' (' . emt('do not send anything to the payment gateway').')'
: '';
-$void = areyousure_link("${p}misc/void-cust_pay.cgi?".$cust_pay->paynum,
- emt('Are you sure you want to void this payment?'),
- emt('Void this payment from the database') . $voidmsg,
- emt('void')
- )
+$void = ' ('.
+ include( '/elements/popup_link.html',
+ 'label' => emt('void'),
+ 'action' => "${p}misc/void-cust_pay.html?".$cust_pay->paynum,
+ 'actionlabel' => emt('Void payment'),
+ ).
+ ')'
if $cust_pay->closed !~ /^Y/i
&& ( ( $cust_pay->payby eq 'CARD' && $opt{'Credit card void'} )
|| ( $cust_pay->payby eq 'CHEK' && $opt{'Echeck void'} )
diff --git a/httemplate/view/cust_main/payment_history/voided_invoice.html b/httemplate/view/cust_main/payment_history/voided_invoice.html
index ea61f84..ff4d12f 100644
--- a/httemplate/view/cust_main/payment_history/voided_invoice.html
+++ b/httemplate/view/cust_main/payment_history/voided_invoice.html
@@ -6,7 +6,7 @@
% }
% my $reason = $cust_bill_void->reason;
% if ($reason) {
- (<% $reason %>)
+ (<% $reason |h %>)
% }
<% mt("on [_1]", time2str($date_format, $cust_bill_void->void_date) ) |h %>
</I>
diff --git a/httemplate/view/cust_main/payment_history/voided_payment.html b/httemplate/view/cust_main/payment_history/voided_payment.html
index 5c43c91..e295f9b 100644
--- a/httemplate/view/cust_main/payment_history/voided_payment.html
+++ b/httemplate/view/cust_main/payment_history/voided_payment.html
@@ -6,7 +6,7 @@
% }
% my $reason = $cust_pay_void->reason;
% if ($reason) {
- (<% $reason %>)
+ (<% $reason |h %>)
% }
<% mt("on [_1]", time2str($date_format, $cust_pay_void->void_date) ) |h %>
</I>
-----------------------------------------------------------------------
Summary of changes:
FS/FS/Schema.pm | 20 ++-
FS/FS/Upgrade.pm | 5 +
FS/FS/cust_bill.pm | 14 +-
FS/FS/cust_bill_pkg.pm | 14 +-
FS/FS/cust_bill_pkg_void.pm | 31 +++-
FS/FS/cust_bill_void.pm | 31 +++-
FS/FS/cust_credit.pm | 2 +-
FS/FS/cust_credit_void.pm | 15 ++
FS/FS/cust_pay.pm | 19 +-
FS/FS/cust_pay_void.pm | 17 +-
FS/FS/reason_Mixin.pm | 187 ++++++++++++++------
FS/FS/reason_type.pm | 21 ++-
httemplate/browse/reason_type.html | 89 ++++++----
httemplate/elements/menu.html | 17 +-
httemplate/elements/tr-select-reason.html | 19 +-
httemplate/misc/unapply-cust_credit.cgi | 2 +-
httemplate/misc/unapply-cust_pay.cgi | 2 +-
httemplate/misc/unvoid-cust_pay_void.cgi | 2 +-
httemplate/misc/void-cust_bill.html | 9 +-
httemplate/misc/void-cust_pay.cgi | 26 ---
httemplate/misc/void-cust_pay.html | 78 ++++++++
httemplate/view/cust_bill.cgi | 21 ++-
.../view/cust_main/payment_history/payment.html | 20 ++-
.../cust_main/payment_history/voided_invoice.html | 2 +-
.../cust_main/payment_history/voided_payment.html | 2 +-
25 files changed, 484 insertions(+), 181 deletions(-)
mode change 100644 => 100755 httemplate/misc/void-cust_bill.html
delete mode 100755 httemplate/misc/void-cust_pay.cgi
create mode 100755 httemplate/misc/void-cust_pay.html
More information about the freeside-commits
mailing list