suspension and cancellation reasons
authorjeff <jeff>
Thu, 19 Oct 2006 14:29:27 +0000 (14:29 +0000)
committerjeff <jeff>
Thu, 19 Oct 2006 14:29:27 +0000 (14:29 +0000)
21 files changed:
FS/FS/cancel_reason.pm [deleted file]
FS/FS/cust_pkg_reason.pm [new file with mode: 0644]
FS/FS/reason.pm [new file with mode: 0644]
FS/FS/reason_type.pm [new file with mode: 0644]
FS/t/cancel_reason.t [deleted file]
FS/t/cust_pkg_reason.t [new file with mode: 0644]
FS/t/reason.t [new file with mode: 0644]
FS/t/reason_type.t [new file with mode: 0644]
httemplate/browse/reason.html [new file with mode: 0644]
httemplate/browse/reason_type.html [new file with mode: 0644]
httemplate/edit/process/reason.html [new file with mode: 0644]
httemplate/edit/process/reason_type.html [new file with mode: 0644]
httemplate/edit/reason.html [new file with mode: 0644]
httemplate/edit/reason_type.html [new file with mode: 0644]
httemplate/elements/tr-select-reason.html [new file with mode: 0755]
httemplate/misc/cancel_pkg.cgi [deleted file]
httemplate/misc/cancel_pkg.html [new file with mode: 0755]
httemplate/misc/expire_pkg.cgi [deleted file]
httemplate/misc/process/cancel_pkg.html [new file with mode: 0755]
httemplate/misc/process/expire_pkg.cgi [deleted file]
httemplate/misc/susp_pkg.cgi [deleted file]

diff --git a/FS/FS/cancel_reason.pm b/FS/FS/cancel_reason.pm
deleted file mode 100644 (file)
index 19cc721..0000000
+++ /dev/null
@@ -1,123 +0,0 @@
-package FS::cancel_reason;
-
-use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
-
-@ISA = qw(FS::Record);
-
-=head1 NAME
-
-FS::cancel_reason - Object methods for cancel_reason records
-
-=head1 SYNOPSIS
-
-  use FS::cancel_reason;
-
-  $record = new FS::cancel_reason \%hash;
-  $record = new FS::cancel_reason { 'column' => 'value' };
-
-  $error = $record->insert;
-
-  $error = $new_record->replace($old_record);
-
-  $error = $record->delete;
-
-  $error = $record->check;
-
-=head1 DESCRIPTION
-
-An FS::cancel_reason object represents an cancellation reason.
-FS::cancel_reason inherits from FS::Record.  The following fields are
-currently supported:
-
-=over 4
-
-=item reasonnum - primary key
-
-=item reason - 
-
-=item disabled - empty or "Y"
-
-=back
-
-=head1 METHODS
-
-=over 4
-
-=item new HASHREF
-
-Creates a new cancellation reason.  To add the reason 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'; }
-
-=item insert
-
-Adds this record to the database.  If there is an error, returns the error,
-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,
-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
-an error, returns the error, otherwise returns false.  Called by the insert
-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;
-}
-
-=back
-
-=head1 BUGS
-
-=head1 SEE ALSO
-
-L<FS::Record>, schema.html from the base documentation.
-
-=cut
-
-1;
-
diff --git a/FS/FS/cust_pkg_reason.pm b/FS/FS/cust_pkg_reason.pm
new file mode 100644 (file)
index 0000000..2f92740
--- /dev/null
@@ -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/reason.pm b/FS/FS/reason.pm
new file mode 100644 (file)
index 0000000..b2d2e9d
--- /dev/null
@@ -0,0 +1,124 @@
+package FS::reason;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::reason - Object methods for reason records
+
+=head1 SYNOPSIS
+
+  use FS::reason;
+
+  $record = new FS::reason \%hash;
+  $record = new FS::reason { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+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_type - index into FS::reason_type
+
+=item reason - text of the reason
+
+=item disabled - 'Y' or ''
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+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
+
+sub table { '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 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('reasonnum')
+    || $self->ut_text('reason')
+  ;
+  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.
+
+=cut
+
+1;
+
diff --git a/FS/FS/reason_type.pm b/FS/FS/reason_type.pm
new file mode 100644 (file)
index 0000000..89278d0
--- /dev/null
@@ -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/cancel_reason.t b/FS/t/cancel_reason.t
deleted file mode 100644 (file)
index a5948f6..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-BEGIN { $| = 1; print "1..1\n" }
-END {print "not ok 1\n" unless $loaded;}
-use FS::cancel_reason;
-$loaded=1;
-print "ok 1\n";
diff --git a/FS/t/cust_pkg_reason.t b/FS/t/cust_pkg_reason.t
new file mode 100644 (file)
index 0000000..2f0a4fa
--- /dev/null
@@ -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/reason.t b/FS/t/reason.t
new file mode 100644 (file)
index 0000000..d5e4dc9
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+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 (file)
index 0000000..279d5b9
--- /dev/null
@@ -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 (file)
index 0000000..e666142
--- /dev/null
@@ -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 (file)
index 0000000..a8ccbdc
--- /dev/null
@@ -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 (file)
index 0000000..55c1ea9
--- /dev/null
@@ -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 (file)
index 0000000..4ccccad
--- /dev/null
@@ -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 (file)
index 0000000..2f59328
--- /dev/null
@@ -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 (file)
index 0000000..970529e
--- /dev/null
@@ -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 (executable)
index 0000000..6c66e81
--- /dev/null
@@ -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 (executable)
index 00b421f..0000000
+++ /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 (executable)
index 0000000..bfb0893
--- /dev/null
@@ -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 (executable)
index 55364c6..0000000
+++ /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 (executable)
index 0000000..b538098
--- /dev/null
@@ -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 (executable)
index d1963e2..0000000
+++ /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 (executable)
index ea9edc7..0000000
+++ /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'));
-%
-%
-