summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjeff <jeff>2006-10-19 14:29:27 +0000
committerjeff <jeff>2006-10-19 14:29:27 +0000
commitce98306f315a53f2ac4b8c010341c4f84bf728a8 (patch)
tree284731e6ad5daf4580b3afdfa3812a864e136b89
parentcdffa6e5fe5c8cf0755a3f3497ae71170bbe4cea (diff)
suspension and cancellation reasons
-rw-r--r--FS/FS/cust_pkg_reason.pm122
-rw-r--r--FS/FS/reason.pm (renamed from FS/FS/cancel_reason.pm)51
-rw-r--r--FS/FS/reason_type.pm135
-rw-r--r--FS/t/cust_pkg_reason.t5
-rw-r--r--FS/t/reason.t (renamed from FS/t/cancel_reason.t)2
-rw-r--r--FS/t/reason_type.t5
-rw-r--r--httemplate/browse/reason.html64
-rw-r--r--httemplate/browse/reason_type.html68
-rw-r--r--httemplate/edit/process/reason.html6
-rw-r--r--httemplate/edit/process/reason_type.html6
-rw-r--r--httemplate/edit/reason.html42
-rw-r--r--httemplate/edit/reason_type.html28
-rwxr-xr-xhttemplate/elements/tr-select-reason.html94
-rwxr-xr-xhttemplate/misc/cancel_pkg.cgi16
-rwxr-xr-xhttemplate/misc/cancel_pkg.html95
-rwxr-xr-xhttemplate/misc/expire_pkg.cgi56
-rwxr-xr-xhttemplate/misc/process/cancel_pkg.html94
-rwxr-xr-xhttemplate/misc/process/expire_pkg.cgi26
-rwxr-xr-xhttemplate/misc/susp_pkg.cgi16
19 files changed, 791 insertions, 140 deletions
diff --git a/FS/FS/cust_pkg_reason.pm b/FS/FS/cust_pkg_reason.pm
new file mode 100644
index 0000000..2f92740
--- /dev/null
+++ b/FS/FS/cust_pkg_reason.pm
@@ -0,0 +1,122 @@
+package FS::cust_pkg_reason;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_pkg_reason - Object methods for cust_pkg_reason records
+
+=head1 SYNOPSIS
+
+ use FS::cust_pkg_reason;
+
+ $record = new FS::cust_pkg_reason \%hash;
+ $record = new FS::cust_pkg_reason { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_pkg_reason object represents a relationship between a cust_pkg
+and a reason, for example cancellation or suspension reasons.
+FS::cust_pkg_reason inherits from FS::Record. The following fields are
+currently supported:
+
+=over 4
+
+=item num - primary key
+
+=item pkgnum -
+
+=item reasonnum -
+
+=item otaker -
+
+=item date -
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new cust_pkg_reason. To add the example to the database, see
+L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'cust_pkg_reason'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+=item check
+
+Checks all fields to make sure this is a valid cust_pkg_reason. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('num')
+ || $self->ut_number('pkgnum')
+ || $self->ut_number('reasonnum')
+ || $self->ut_text('otaker')
+ || $self->ut_numbern('date')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+Here be termites. Don't use on wooden computers.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/cancel_reason.pm b/FS/FS/reason.pm
index 19cc721..b2d2e9d 100644
--- a/FS/FS/cancel_reason.pm
+++ b/FS/FS/reason.pm
@@ -1,4 +1,4 @@
-package FS::cancel_reason;
+package FS::reason;
use strict;
use vars qw( @ISA );
@@ -8,14 +8,14 @@ use FS::Record qw( qsearch qsearchs );
=head1 NAME
-FS::cancel_reason - Object methods for cancel_reason records
+FS::reason - Object methods for reason records
=head1 SYNOPSIS
- use FS::cancel_reason;
+ use FS::reason;
- $record = new FS::cancel_reason \%hash;
- $record = new FS::cancel_reason { 'column' => 'value' };
+ $record = new FS::reason \%hash;
+ $record = new FS::reason { 'column' => 'value' };
$error = $record->insert;
@@ -27,17 +27,19 @@ FS::cancel_reason - Object methods for cancel_reason records
=head1 DESCRIPTION
-An FS::cancel_reason object represents an cancellation reason.
-FS::cancel_reason inherits from FS::Record. The following fields are
-currently supported:
+An FS::reason object represents a reason message. FS::reason inherits from
+FS::Record. The following fields are currently supported:
=over 4
=item reasonnum - primary key
-=item reason -
+=item reason_type - index into FS::reason_type
+
+=item reason - text of the reason
+
+=item disabled - 'Y' or ''
-=item disabled - empty or "Y"
=back
@@ -47,17 +49,14 @@ currently supported:
=item new HASHREF
-Creates a new cancellation reason. To add the reason to the database, see
-L<"insert">.
+Creates a new reason. To add the example to the database, see L<"insert">.
Note that this stores the hash reference, not a distinct copy of the hash it
points to. You can ask the object for a copy with the I<hash> method.
=cut
-# the new method can be inherited from FS::Record, if a table method is defined
-
-sub table { 'cancel_reason'; }
+sub table { 'reason'; }
=item insert
@@ -66,16 +65,12 @@ otherwise returns false.
=cut
-# the insert method can be inherited from FS::Record
-
=item delete
Delete this record from the database.
=cut
-# the delete method can be inherited from FS::Record
-
=item replace OLD_RECORD
Replaces the OLD_RECORD with this one in the database. If there is an error,
@@ -83,8 +78,6 @@ returns the error, otherwise returns false.
=cut
-# the replace method can be inherited from FS::Record
-
=item check
Checks all fields to make sure this is a valid reason. If there is
@@ -93,26 +86,34 @@ and replace methods.
=cut
-# the check method should currently be supplied - FS::Record contains some
-# data checking routines
-
sub check {
my $self = shift;
my $error =
$self->ut_numbern('reasonnum')
|| $self->ut_text('reason')
- || $self->ut_enum('disabled', [ '', 'Y' ] )
;
return $error if $error;
$self->SUPER::check;
}
+=item reasontype
+
+Returns the reason_type (see <I>FS::reason_type</I>) associated with this reason.
+
+=cut
+
+sub reasontype {
+ qsearchs( 'reason_type', { 'typenum' => shift->reason_type } );
+}
+
=back
=head1 BUGS
+Here be termintes. Don't use on wooden computers.
+
=head1 SEE ALSO
L<FS::Record>, schema.html from the base documentation.
diff --git a/FS/FS/reason_type.pm b/FS/FS/reason_type.pm
new file mode 100644
index 0000000..89278d0
--- /dev/null
+++ b/FS/FS/reason_type.pm
@@ -0,0 +1,135 @@
+package FS::reason_type;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::reason_type - Object methods for reason_type records
+
+=head1 SYNOPSIS
+
+ use FS::reason_type;
+
+ $record = new FS::reason_type \%hash;
+ $record = new FS::reason_type { 'column' => 'value' };
+
+ $error = $record->insert;
+
+ $error = $new_record->replace($old_record);
+
+ $error = $record->delete;
+
+ $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::reason_type object represents a grouping of reasons. FS::reason_type
+inherits from FS::Record. The following fields are currently supported:
+
+=over 4
+
+=item typenum - primary key
+
+=item class - currently 'C' or 'S' for cancel or suspend
+
+=item type - name of the type of reason
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new reason_type. To add the example to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to. You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'reason_type'; }
+
+=item insert
+
+Adds this record to the database. If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database. If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+=item check
+
+Checks all fields to make sure this is a valid reason_type. If there is
+an error, returns the error, otherwise returns false. Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+ my $self = shift;
+
+ my $error =
+ $self->ut_numbern('typenum')
+ || $self->ut_enum('class', [ 'C', 'S' ] )
+ || $self->ut_text('type')
+ ;
+ return $error if $error;
+
+ $self->SUPER::check;
+}
+
+=item reasons
+
+Returns a list of all reasons associated with this type.
+
+=cut
+
+sub reasons {
+ qsearch( 'reason', { 'reason_type' => shift->typenum } );
+}
+
+=item enabled_reasons
+
+Returns a list of enabled reasons associated with this type.
+
+=cut
+
+sub enabled_reasons {
+ qsearch( 'reason', { 'reason_type' => shift->typenum,
+ 'enabled' => '',
+ } );
+}
+
+=back
+
+=head1 BUGS
+
+Here be termintes. Don't use on wooden computers.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/t/cust_pkg_reason.t b/FS/t/cust_pkg_reason.t
new file mode 100644
index 0000000..2f0a4fa
--- /dev/null
+++ b/FS/t/cust_pkg_reason.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_pkg_reason;
+$loaded=1;
+print "ok 1\n";
diff --git a/FS/t/cancel_reason.t b/FS/t/reason.t
index a5948f6..d5e4dc9 100644
--- a/FS/t/cancel_reason.t
+++ b/FS/t/reason.t
@@ -1,5 +1,5 @@
BEGIN { $| = 1; print "1..1\n" }
END {print "not ok 1\n" unless $loaded;}
-use FS::cancel_reason;
+use FS::reason;
$loaded=1;
print "ok 1\n";
diff --git a/FS/t/reason_type.t b/FS/t/reason_type.t
new file mode 100644
index 0000000..279d5b9
--- /dev/null
+++ b/FS/t/reason_type.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::reason_type;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/reason.html b/httemplate/browse/reason.html
new file mode 100644
index 0000000..e666142
--- /dev/null
+++ b/httemplate/browse/reason.html
@@ -0,0 +1,64 @@
+%
+%$cgi->param('class') =~ /^(\w)$/ or die "illegal class";
+%my $class = $1;
+%
+%my %classmap = ( 'C' => 'cancel',
+% 'S' => 'suspend',
+% );
+%
+%my $classname = $classmap{$class};
+%
+%my $html_init = ucfirst($classname) .
+% " reasons explain why we $classname a package.<BR><BR>".
+% qq!<A HREF="${p}edit/reason.html?class=$class">!.
+% "<I>Add a $classname reason</I></A><BR><BR>";
+%
+%my $where_clause = "WHERE class='$class'";
+%$where_clause .= " AND (disabled = '' OR disabled IS NULL)"
+% unless $cgi->param('showdisabled');
+
+%my $disabledurl = $cgi->param('showdisabled')
+% ? do { $cgi->param('showdisabled', 0);
+% '( <a href="'. $cgi->self_url. '">hide disabled reasons</a> )'; }
+% : do { $cgi->param('showdisabled', 1);
+% '( <a href="'. $cgi->self_url. '">show disabled reasons</a> )'; }
+% ;
+%
+%$html_init .= $disabledurl;
+%
+%my $count_query = 'SELECT COUNT(*) FROM reason LEFT JOIN reason_type on ' .
+% 'reason_type.typenum = reason.reason_type ' . $where_clause;
+%
+%my $link = [ $p."edit/reason.html?class=$class&reasonnum=", 'reasonnum' ];
+%
+%
+<% include( 'elements/browse.html',
+ 'title' => ucfirst($classname) . ' Reasons',
+ 'menubar' => [ # 'Main menu' => $p,
+ ucfirst($classname) . ' Reason Types' =>
+ $p.'browse/reason_type.html?class='.
+ $class,
+ ],
+ 'html_init' => $html_init,
+ 'name' => $classname . ' reasons',
+ 'query' => { 'table' => 'reason',
+ 'hashref' => {},
+ 'extra_sql' => $where_clause .
+ 'ORDER BY reason_type',
+ 'addl_from' => 'LEFT JOIN reason_type ON reason_type.typenum = reason.reason_type',
+ },
+ 'count_query' => $count_query,
+ 'header' => [ '#',
+ ucfirst($classname) . ' Reason Type',
+ ucfirst($classname) . ' Reason',
+ ],
+ 'fields' => [ 'reasonnum',
+ sub { shift->reasontype->type },
+ 'reason',
+ ],
+ 'links' => [ $link,
+ $link,
+ '',
+ ],
+ )
+%>
diff --git a/httemplate/browse/reason_type.html b/httemplate/browse/reason_type.html
new file mode 100644
index 0000000..a8ccbdc
--- /dev/null
+++ b/httemplate/browse/reason_type.html
@@ -0,0 +1,68 @@
+%
+%$cgi->param('class') =~ /^(\w)$/ or die "illegal class";
+%my $class=$1;
+%
+%my %classmap = ( 'C' => 'cancel',
+% 'S' => 'suspend',
+% );
+%
+%my $classname = $classmap{$class};
+%
+%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>";
+%
+%my $reasons_sub = sub {
+% my $reason_type = shift;
+%
+% [ map {
+% [
+% {
+% 'data' => $_->reason,
+% 'align' => 'left',
+% 'link' => $p. "edit/reason.html?class=$class&reasonnum=".
+% $_->reasonnum,
+% },
+% ];
+% }
+% $reason_type->enabled_reasons,
+%
+% ];
+%
+%};
+%
+%my $where_clause = "WHERE class='$class'";
+%my $count_query = 'SELECT COUNT(*) FROM reason_type ';
+%$count_query .= $where_clause;
+%
+%my $link = [ $p.'edit/reason_type.html?class='.$class.'&typenum=', 'typenum' ];
+%
+%
+<% 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,
+ '',
+ ],
+ )
+%>
diff --git a/httemplate/edit/process/reason.html b/httemplate/edit/process/reason.html
new file mode 100644
index 0000000..55c1ea9
--- /dev/null
+++ b/httemplate/edit/process/reason.html
@@ -0,0 +1,6 @@
+<% include( 'elements/process.html',
+ 'table' => 'reason',
+ 'redirect' => popurl(3) . 'browse/reason.html?class=' .
+ $cgi->param('class') . '&',
+ )
+%>
diff --git a/httemplate/edit/process/reason_type.html b/httemplate/edit/process/reason_type.html
new file mode 100644
index 0000000..4ccccad
--- /dev/null
+++ b/httemplate/edit/process/reason_type.html
@@ -0,0 +1,6 @@
+<% include( 'elements/process.html',
+ 'table' => 'reason_type',
+ 'redirect' => popurl(3) . 'browse/reason_type.html?class=' .
+ $cgi->param('class') . '&',
+ )
+%>
diff --git a/httemplate/edit/reason.html b/httemplate/edit/reason.html
new file mode 100644
index 0000000..2f59328
--- /dev/null
+++ b/httemplate/edit/reason.html
@@ -0,0 +1,42 @@
+%
+% $cgi->param('class') =~ /^(\w)$/ or die "illegal class";
+% my $class=$1;
+%
+% my %classmap = ('C' => 'cancel',
+% 'S' => 'suspend',
+% );
+% my $classname = $classmap{$class};
+%
+% my (@types) = qsearch( 'reason_type', { 'class' => $class } );
+%
+<% include( 'elements/edit.html',
+ 'name' => ucfirst($classname) . ' Reason',
+ 'table' => 'reason',
+ 'labels' => {
+ 'reasonnum' => ucfirst($classname) . ' Reason',
+ 'reason_type' => ucfirst($classname) . ' Reason type',
+ 'reason' => ucfirst($classname) . ' Reason',
+ 'disabled' => 'Disabled',
+ 'class' => '',
+ },
+ 'fields' => [
+ { 'field' => 'reason_type',
+ 'type' => 'select',
+ 'value' => { 'vcolumn' => 'typenum',
+ 'ccolumn' => 'type',
+ 'values' => \@types,
+ },
+ },
+ 'reason',
+ { 'field' => 'class',
+ 'type' => 'fixedhidden',
+ 'value' => $class,
+ },
+ { 'field' => 'disabled',
+ 'type' => 'checkbox',
+ 'value' => 'Y'
+ },
+ ],
+ 'viewall_url' => $p . "browse/reason.html?class=$class",
+ )
+%>
diff --git a/httemplate/edit/reason_type.html b/httemplate/edit/reason_type.html
new file mode 100644
index 0000000..970529e
--- /dev/null
+++ b/httemplate/edit/reason_type.html
@@ -0,0 +1,28 @@
+%
+%$cgi->param('class') =~ /^(\w)$/;
+%my $class = $1;
+%
+%my %classmap = ( 'C' => 'Cancel',
+% 'S' => 'Suspend',
+% );
+%
+%my $classname = $classmap{$class};
+%
+<% include( 'elements/edit.html',
+ 'name' => $classname . ' Reason Type',
+ 'table' => 'reason_type',
+ 'labels' => {
+ 'typenum' => $classname . ' reason type',
+ 'type' => $classname . ' reason type name',
+ 'class' => '',
+ },
+ 'fields' => [
+ 'type',
+ { 'field' => 'class',
+ 'type' => 'hidden',
+ },
+ ],
+ 'viewall_url' => $p . "browse/reason_type.html?class=$class",
+ 'new_hashref_callback' => sub {{ 'class' => $class }},
+ )
+%>
diff --git a/httemplate/elements/tr-select-reason.html b/httemplate/elements/tr-select-reason.html
new file mode 100755
index 0000000..6c66e81
--- /dev/null
+++ b/httemplate/elements/tr-select-reason.html
@@ -0,0 +1,94 @@
+
+<SCRIPT TYPE="text/javascript">
+ function sh_add<% $name %>()
+ {
+
+%if ($curuser->access_right($access_right)){
+
+ if (document.getElementById('<% $name %>').selectedIndex ==
+ (document.getElementById('<% $name %>').length - 1)) {
+ document.getElementById('new<% $name %>').disabled = false;
+ document.getElementById('new<% $name %>').style.display = 'inline';
+ document.getElementById('new<% $name %>Label').style.display = 'inline';
+ document.getElementById('new<% $name %>T').disabled = false;
+ document.getElementById('new<% $name %>T').style.display = 'inline';
+ document.getElementById('new<% $name %>TLabel').style.display = 'inline';
+ }else{
+ document.getElementById('new<% $name %>').disabled = true;
+ document.getElementById('new<% $name %>').style.display = 'none';
+ document.getElementById('new<% $name %>Label').style.display = 'none';
+ document.getElementById('new<% $name %>T').disabled = true;
+ document.getElementById('new<% $name %>T').style.display = 'none';
+ document.getElementById('new<% $name %>TLabel').style.display = 'none';
+ }
+
+%}
+
+ }
+</SCRIPT>
+
+<TR>
+ <TD ALIGN="right">Reason</TD>
+ <TD>
+ <SELECT id="<% $name %>" name="<% $name %>" onFocus="sh_add<% $name %>()" onChange="sh_add<% $name %>()">
+% my @reasons = qsearch( { table =>'reason',
+% hashref => {},
+% extra_sql => $extra_sql,
+% addl_from => 'LEFT JOIN reason_type ON reason_type.typenum = reason.reason_type',
+% });
+% foreach my $reason (@reasons) {
+ <OPTION VALUE="<% $reason->reasonnum %>" <% ($init_reason == $reason->reasonnum) ? 'SELECTED' : '' %>><% $reason->reason %></OPTION>
+% }
+% if ($curuser->access_right($access_right)) {
+ <OPTION VALUE="-1" <% ($init_reason == -1) ? 'SELECTED' : '' %>>Add new reason</OPTION>
+% }
+%
+ </SELECT>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">
+ <P id="new<% $name %>TLabel" style="display:<% $display %>">Reason Type</P>
+ </TD>
+ <TD>
+ <SELECT id="new<% $name %>T" name="new<% $name %>T" disabled="<% $disabled %>" style="display:<% $display %>">
+% for my $type (qsearch( 'reason_type', { 'class' => $class } )){
+ <OPTION VALUE="<% $type->typenum %>" <% ($init_type == $type->typenum) ? 'SELECTED' : '' %>><% $type->type %></OPTION>
+% }
+ </SELECT>
+ </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="right">
+ <P id="new<% $name %>Label" style="display:<% $display %>">New Reason</P>
+ </TD>
+ <TD><INPUT id="new<% $name %>" name="new<% $name %>" type="text" value="<% $init_newreason %>" disabled="<% $disabled %>" style="display:<% $display %>"></TD>
+</TR>
+
+<%init>
+my($name, $class, $init_reason, $init_type, $init_newreason) = @_;
+my($extra_sql, $curuser, $access_right, $display, $disabled);
+
+if ($class eq 'C') {
+ $access_right='Add on-the-fly cancel reason';
+}elsif ($class eq 'S') {
+ $access_right='Add on-the-fly suspend reason';
+}else{
+ print "illegal class: $class";
+}
+
+if ($init_reason == -1){
+ $display = 'inline';
+ $disabled = 'false';
+}else{
+ $display = 'none';
+ $disabled = 'true';
+}
+
+$extra_sql = "WHERE class = '$class' ORDER BY reason_type";
+$curuser = $FS::CurrentUser::CurrentUser;
+
+</%init>
+
diff --git a/httemplate/misc/cancel_pkg.cgi b/httemplate/misc/cancel_pkg.cgi
deleted file mode 100755
index 00b421f..0000000
--- a/httemplate/misc/cancel_pkg.cgi
+++ /dev/null
@@ -1,16 +0,0 @@
-%
-%
-%#untaint pkgnum
-%my($query) = $cgi->keywords;
-%$query =~ /^(\d+)$/ || die "Illegal pkgnum";
-%my $pkgnum = $1;
-%
-%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
-%
-%my $error = $cust_pkg->cancel;
-%eidiot($error) if $error;
-%
-%print $cgi->redirect($p. "view/cust_main.cgi?".$cust_pkg->getfield('custnum'));
-%
-%
-
diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html
new file mode 100755
index 0000000..bfb0893
--- /dev/null
+++ b/httemplate/misc/cancel_pkg.html
@@ -0,0 +1,95 @@
+%# if ( $link eq 'popup' ) {
+ <% include('/elements/header-popup.html', $title ) %>
+%# } else {
+%# <% include("/elements/header.html", $title, '') %>
+%# }
+
+<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
+<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
+
+% if ( $cgi->param('error') ) {
+ <FONT SIZE="+1" COLOR="#ff0000">Error: <% $cgi->param('error') %></FONT>
+ <BR><BR>
+% }
+
+<FORM NAME="sc_popup" ACTION="<% popurl(1) %>process/cancel_pkg.html" METHOD=POST>
+<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
+<INPUT TYPE="hidden" NAME="method" VALUE="<% $method %>">
+
+
+<BR><BR>
+<% ucfirst($method) . " $pkgnum: " .$part_pkg->pkg. ' - ' .$part_pkg->comment %>
+<% ntable("#cccccc", 2) %>
+
+% if ($method eq 'expire') {
+<TR>
+ <TD>Cancel package on </TD>
+ <TD><INPUT TYPE="text" NAME="date" ID="expire_date" VALUE="<% $date %>">
+ <IMG SRC="<% $p %>images/calendar.png" ID="expire_button" STYLE="cursor:pointer" TITLE="Select date">
+ <BR><I>m/d/y</I>
+ </TD>
+</TR>
+<SCRIPT TYPE="text/javascript">
+ Calendar.setup({
+ inputField: "expire_date",
+ ifFormat: "%m/%d/%Y",
+ button: "expire_button",
+ align: "BR"
+ });
+</SCRIPT>
+%}
+%
+
+<% include('/elements/tr-select-reason.html', 'reasonnum', $class) %>
+
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="<% $submit %>">
+
+</FORM>
+</BODY>
+</HTML>
+
+<%init>
+my($method, $pkgnum, $reasonnum, $submit, $cust_pkg, $part_pkg,
+ $date, $curuser, $class);
+$date = time2str("%m/%d/%Y", time);
+if ( $cgi->param('error') ) {
+ $method = $cgi->param('method');
+ $pkgnum = $cgi->param('pkgnum');
+ $reasonnum = $cgi->param('reasonnum');
+ $date = $cgi->param('date');
+} elsif ( $cgi->param('pkgnum') =~ /^(\d+)$/ ) {
+ $pkgnum = $1;
+} else {
+ die "illegal query ". $cgi->keywords;
+}
+
+$method = $cgi->param('method');
+if ($method eq 'cancel') {
+ $class = 'C';
+ $submit = "Cancel Now";
+}elsif ($method eq 'expire') {
+ $class = 'C';
+ $submit = "Cancel Later";
+}elsif ($method eq 'suspend') {
+ $class = 'S';
+ $submit = "Suspend";
+}else{
+ die "illegal query ". $cgi->keywords;
+}
+
+my $title = ucfirst($method) . ' Package';
+
+$cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum});
+die "No such package: $pkgnum" unless $cust_pkg;
+
+$part_pkg = $cust_pkg->part_pkg;
+
+$curuser = $FS::CurrentUser::CurrentUser;
+
+</%init>
+
diff --git a/httemplate/misc/expire_pkg.cgi b/httemplate/misc/expire_pkg.cgi
deleted file mode 100755
index 55364c6..0000000
--- a/httemplate/misc/expire_pkg.cgi
+++ /dev/null
@@ -1,56 +0,0 @@
-<!-- mason kludge -->
-%
-%
-%my($query) = $cgi->keywords;
-%$query =~ /^(\d+)$/;
-%my $pkgnum = $1;
-%
-%#get package record
-%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
-%die "Unknown pkgnum $pkgnum" unless $cust_pkg;
-%my $part_pkg = $cust_pkg->part_pkg;
-%
-%my $custnum = $cust_pkg->getfield('custnum');
-%
-%my $date = $cust_pkg->expire ? time2str('%D', $cust_pkg->expire) : '';
-%
-%
-
-
-<% include("/elements/header.html",'Expire package', menubar(
- "View this customer (#$custnum)" => "${p}view/cust_main.cgi?$custnum",
- 'Main Menu' => popurl(2)
-)) %>
-
-<LINK REL="stylesheet" TYPE="text/css" HREF="../elements/calendar-win2k-2.css" TITLE="win2k-2">
-<SCRIPT TYPE="text/javascript" SRC="../elements/calendar_stripped.js"></SCRIPT>
-<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-en.js"></SCRIPT>
-<SCRIPT TYPE="text/javascript" SRC="../elements/calendar-setup.js"></SCRIPT>
-
-<% $pkgnum %>: <% $part_pkg->pkg. ' - '. $part_pkg->comment %>
-
-<FORM NAME="formname" ACTION="process/expire_pkg.cgi" METHOD="post">
-<INPUT TYPE="hidden" NAME="pkgnum" VALUE="<% $pkgnum %>">
-<TABLE>
- <TR>
- <TD>Cancel package on </TD>
- <TD><INPUT TYPE="text" NAME="date" ID="expire_date" VALUE="<% $date %>">
- <IMG SRC="<% $p %>images/calendar.png" ID="expire_button" STYLE="cursor:pointer" TITLE="Select date">
- <BR><I>m/d/y</I>
- </TD>
- </TR>
-</TABLE>
-
-<SCRIPT TYPE="text/javascript">
- Calendar.setup({
- inputField: "expire_date",
- ifFormat: "%m/%d/%Y",
- button: "expire_button",
- align: "BR"
- });
-</SCRIPT>
-
-<INPUT TYPE="submit" VALUE="Cancel later">
-</FORM>
-</BODY>
-</HTML>
diff --git a/httemplate/misc/process/cancel_pkg.html b/httemplate/misc/process/cancel_pkg.html
new file mode 100755
index 0000000..b538098
--- /dev/null
+++ b/httemplate/misc/process/cancel_pkg.html
@@ -0,0 +1,94 @@
+%
+%
+%#untaint method
+%my $method = $cgi->param('method');
+%$method =~ /^(cancel|expire|suspend)$/ || die "Illegal method";
+%$method = $1;
+
+%#untaint pkgnum
+%my $pkgnum = $cgi->param('pkgnum');
+%$pkgnum =~ /^(\d+)$/ || die "Illegal pkgnum";
+%$pkgnum = $1;
+%
+%#untaint reasonnum
+%my $reasonnum = $cgi->param('reasonnum');
+%$reasonnum =~ /^(-?\d+)$/ || die "Illegal reasonnum";
+%$reasonnum = $1;
+%
+%my $date = time;
+%if ($method eq 'expire'){
+% #untaint date
+% $date = $cgi->param('date');
+% str2time($cgi->param('date')) =~ /^(\d+)$/ || die "Illegal date";
+% $date = $1;
+%}
+%
+%my $cust_pkg = qsearchs( 'cust_pkg', {'pkgnum'=>$pkgnum} );
+%
+%
+%my $oldAutoCommit = $FS::UID::AutoCommit;
+%local $FS::UID::AutoCommit = 0;
+%my $dbh = dbh;
+%
+%my $otaker = $FS::CurrentUser::CurrentUser->name;
+%$otaker = $FS::CurrentUser::CurrentUser->username
+% if ($otaker eq "User, Legacy");
+%
+%my $error;
+%if ($reasonnum == -1) {
+% #untaint new reason
+% my $nr = $cgi->param('newreasonnum');
+% $nr =~ /^([\w\s]+)$/ || die "Illegal new reason";
+% $nr = $1;
+%
+% #untaint new reason type
+% my $nrtype = $cgi->param('newreasonnumT');
+% $nrtype =~ /^(\d+)$/ || die "Illegal new reason type";
+% $nrtype = $1;
+%
+% my $reason = new FS::reason({ 'reason_type' => $nrtype,
+% 'reason' => $nr,
+% });
+% $error = $reason->insert;
+% $reasonnum = $reason->reasonnum
+% unless $error;
+%}
+%
+%unless ($error) {
+% my $cust_pkg_reason = new FS::cust_pkg_reason({ 'pkgnum' => $pkgnum,
+% 'reasonnum' => $reasonnum,
+% 'otaker' => $otaker,
+% 'date' => $date,
+% });
+% $error = $cust_pkg_reason->insert;
+%}
+%
+%unless ($error) {
+% if ($method eq 'expire'){
+% my %hash = $cust_pkg->hash;
+% $hash{'expire'}=$date;
+% my $new = new FS::cust_pkg (\%hash);
+% $error = $new->replace($cust_pkg);
+% }else{
+% $error = $cust_pkg->$method
+% }
+%}
+%
+%if ($error) {
+% $cgi->param('error', $error);
+% $dbh->rollback if $oldAutoCommit;
+% print $cgi->redirect(popurl(2). "cancel_pkg.html?". $cgi->query_string );
+%}
+%
+%$dbh->commit or die $dbh->errstr if $oldAutoCommit;
+%
+% my %past = ( 'cancel' => 'cancelled',
+% 'expire' => 'expired',
+% 'suspend' => 'suspended',
+% );
+<% header("Package $past{$method}") %>
+ <SCRIPT TYPE="text/javascript">
+ window.top.location.reload();
+ </SCRIPT>
+ </BODY></HTML>
+
diff --git a/httemplate/misc/process/expire_pkg.cgi b/httemplate/misc/process/expire_pkg.cgi
deleted file mode 100755
index d1963e2..0000000
--- a/httemplate/misc/process/expire_pkg.cgi
+++ /dev/null
@@ -1,26 +0,0 @@
-%
-%
-%#untaint date & pkgnum
-%
-%my $date;
-%if ( $cgi->param('date') ) {
-% str2time($cgi->param('date')) =~ /^(\d+)$/ or die "Illegal date";
-% $date=$1;
-%} else {
-% $date='';
-%}
-%
-%$cgi->param('pkgnum') =~ /^(\d+)$/ or die "Illegal pkgnum";
-%my $pkgnum = $1;
-%
-%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
-%my %hash = $cust_pkg->hash;
-%$hash{expire}=$date;
-%my $new = new FS::cust_pkg ( \%hash );
-%my $error = $new->replace($cust_pkg);
-%&eidiot($error) if $error;
-%
-%print $cgi->redirect(popurl(3). "view/cust_main.cgi?".$cust_pkg->getfield('custnum'));
-%
-%
-
diff --git a/httemplate/misc/susp_pkg.cgi b/httemplate/misc/susp_pkg.cgi
deleted file mode 100755
index ea9edc7..0000000
--- a/httemplate/misc/susp_pkg.cgi
+++ /dev/null
@@ -1,16 +0,0 @@
-%
-%
-%#untaint pkgnum
-%my ($query) = $cgi->keywords;
-%$query =~ /^(\d+)$/ || die "Illegal pkgnum";
-%my $pkgnum = $1;
-%
-%my $cust_pkg = qsearchs('cust_pkg',{'pkgnum'=>$pkgnum});
-%
-%my $error = $cust_pkg->suspend;
-%&eidiot($error) if $error;
-%
-%print $cgi->redirect(popurl(2). "view/cust_main.cgi?".$cust_pkg->getfield('custnum'));
-%
-%
-